Skip to content

Latest commit

 

History

History
142 lines (117 loc) · 6.3 KB

File metadata and controls

142 lines (117 loc) · 6.3 KB

DICE — Deployment Runbook

Prerequisites

Variable Meaning
DEPLOYER_PRIVATE_KEY Throwaway key on testnet; hardware-backed on mainnet
WETH_ADDRESS Canonical WETH on the target chain (the vault asset)
ADMIN_MULTISIG Receives DEFAULT_ADMIN on every contract
TREASURY_ADDRESS Protocol fee receiver
RNG_OPERATOR_ADDRESS Hot server key that commits/reveals seed epochs — must be distinct from every admin/ops key
RISK_MANAGER_ADDRESS Bounded-parameter changes + breaker clear
GUARDIAN_ADDRESS Pause-only key
ROBINHOOD_TESTNET_RPC_URL From docs.robinhood.com/chain
BLOCKSCOUT_API_KEY Any non-empty string (Blockscout doesn't require a real key)

1. Local (anvil)

anvil &
FOUNDRY_PROFILE=local forge script script/DeployLocal.s.sol \
  --rpc-url local --broadcast

Deploys MockWETH, all contracts, grants every role to anvil account #0, commits epoch 0 (seed preimage keccak256("local dev server seed 0")), and seeds the vault with 100 WETH.

2. Robinhood Chain testnet

forge build && forge test          # must be green

FOUNDRY_PROFILE=robinhood-testnet forge script script/Deploy.s.sol \
  --rpc-url robinhood-testnet --broadcast \
  --verify --verifier blockscout \
  --verifier-url https://testnet.robinhoodchain.blockscout.com/api/

The script, in order:

  1. Deploys BankrollVault (ERC-4626 over WETH).
  2. Deploys HouseController, points the vault at it.
  3. Deploys DrandRandomSource (primary — drand quicknet verified on-chain, roundDelay = 2, no operator key) and CommitRevealRandom (standby — operator key from env; rotate to it with one multisig dice.setRandomSource call if ever needed).
  4. Deploys DiceGame wired to drand, registers it (per-bet 50 bps / exposure 500 bps / edge floor 100 bps), approves it on both sources.
  5. Deploys ReferralRegistry + PointsLedger, grants the controller the recorder role.
  6. Grants RISK_MANAGER / GUARDIAN, hands DEFAULT_ADMIN everywhere to ADMIN_MULTISIG, and the deployer renounces all admin.

Record the printed addresses in your ops vault.

Post-deploy checklist (testnet + mainnet)

  • vault.controller() == HouseController
  • controller.games(dice).enabled == true
  • dice.randomSource() == DrandRandomSource
  • drand.approvedConsumers(dice) == true and fallbackRng.approvedConsumers(dice) == true
  • Deployer holds no role on any contract (hasRole(0x00, deployer) == false on all six)
  • drand relay keeper is running (see below); place a 1-wei test bet end-to-end: place → wait ~2 rounds (6s) → keeper relays pulse → settlement pays/loses correctly
  • Timeout drill: place a bet, stop the keeper, wait fulfillTimeout (default 1h) past the pinned round, call timeout(requestId) from an unrelated EOA → full wager refunded
  • Standby drill (testnet only): rotate dice.setRandomSource(fallbackRng), commit an epoch, run one bet through commit-reveal, rotate back
  • Seed an initial LP deposit; verify availableLiquidity() and that a bet breaching the 0.5% per-bet cap reverts
  • Withdrawal drill: requestRedeem → wait delay → claimRedeem

Verification (if --verify was skipped)

forge verify-contract <ADDRESS> src/BankrollVault.sol:BankrollVault \
  --verifier blockscout \
  --verifier-url https://testnet.robinhoodchain.blockscout.com/api/ \
  --constructor-args $(cast abi-encode "constructor(address,string,string,address)" \
      $WETH_ADDRESS "DICE WETH Bankroll" "dWETH" <DEPLOYER>)

Repeat per contract (HouseController, CommitRevealRandom, DiceGame, ReferralRegistry, PointsLedger) with their constructor args. Mainnet verifier URL: https://robinhoodchain.blockscout.com/api/.

3. Mainnet

Identical command with FOUNDRY_PROFILE=robinhood and --rpc-url robinhood, plus:

  • ADMIN_MULTISIG must be a real multisig fronted by a timelock (deploy the timelock first; the multisig is the timelock's proposer).
  • DEPLOYER_PRIVATE_KEY from a hardware signer.
  • Run the full post-deploy checklist before announcing; keep the guardian pause key warm during the first week.

Keeper runbook (DrandRandomSource — primary)

The keeper is untrusted — it cannot influence outcomes, only relay them. It must, continuously:

  1. Watch RandomnessRequested(requestId, consumer, betId, targetRound, ...).
  2. When drand publishes targetRound (~3s cadence, GENESIS + (round-1)*3), fetch the pulse: GET https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/<round> (mirrors: drand.cloudflare.com, api.drand.secureweb3.com:6875).
  3. Decompress the 48-byte signature to the 128-byte uncompressed EIP-2537 G1 form (x, y over Fp with 16-byte zero padding; y chosen by the 0x20 sign flag) and call fulfill(requestId, sig128).
  4. Alert if any request is still Pending 10+ minutes past its round — players can self-settle, but the house keeper should always be first.

Failure economics: if the keeper (and everyone else) fails to relay for fulfillTimeout (default 1h), anyone refunds the bet — LPs lose nothing but edge. drand itself is a threshold network run by the League of Entropy; multi-mirror fetching makes beacon unavailability a non-event.

Operator runbook (CommitRevealRandom — standby)

The operator server must, continuously:

  1. Keep ≥ 1 unrevealed committed epoch at all times (commitEpoch ahead of demand) — betting fails closed with NoActiveEpoch otherwise.
  2. Reveal the active epoch within revealTimeout blocks of its first request (default 1800), then fulfill each pending request (both are permissionless — keepers can crank them too).
  3. Rotate: reveal closes an epoch; new bets automatically join the next committed epoch.

Seed hygiene: generate each server seed with a CSPRNG; store until reveal; never reuse; the key that sends commit/reveal txs holds no other privileges. If the key leaks, the attacker can only censor reveals (bets refund via timeout) — outcomes cannot be altered. Rotate by granting OPERATOR_ROLE to the new key and revoking the old (admin multisig).