Skip to content

Conversation

@SidestreamStrongStrawberry
Copy link
Collaborator

Description

This spell contents are based on Executive Sheet

Contribution Checklist

  • PR title starts with (PE-<TICKET_NUMBER>)
  • Code approved
  • Tests approved
  • CI Tests pass

Checklist

  • Every contract variable/method declared as public/external private/internal
  • Consider if this PR needs the officeHours modifier override
  • Verify expiration (30 days unless otherwise specified)
  • Verify hash in the description matches here
  • Validate all addresses used are in changelog or known
  • Notify any external teams affected by the spell so they have the opportunity to review
  • Deploy spell ETH_GAS_LIMIT="XXX" ETH_GAS_PRICE="YYY" make deploy
  • Verify mainnet contract on etherscan
  • Change test to use mainnet spell address and deploy timestamp
  • Run make archive-spell or make date="YYYY-MM-DD" archive-spell to make an archive directory and copy DssSpell.sol, DssSpell.t.sol, DssSpell.t.base.sol, and DssSpellCollateralOnboarding.sol
  • squash and merge this PR

@coderabbitai
Copy link

coderabbitai bot commented Nov 11, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2025-11-17

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@SidestreamBurningBanana
Copy link
Contributor

Good to deploy

Mainnet Executive Spell Review Checklist

Repo: https://github.com/sky-ecosystem/spells-mainnet

Development Stage

  • Install stable Foundry version

    • Install the stable version of Foundry via foundryup --install stable
      foundryup: version stable already installed and verified, activating...
      foundryup: use - forge 1.4.4-stable (05794498bf 2025-11-03T23:44:21.031788094Z)
      foundryup: use - cast 1.4.4-stable (05794498bf 2025-11-03T23:44:21.031788094Z)
      foundryup: use - anvil 1.4.4-stable (05794498bf 2025-11-03T23:44:21.031788094Z)
      foundryup: use - chisel 1.4.4-stable (05794498bf 2025-11-03T23:44:21.031788094Z)
      
  • Preparation

  • Base checks

    • Current solc version 0.8.16
    • Office hours is true IF spell introduces a major change that can affect external parties (e.g.: keepers are affected in case of collateral offboarding) OTHERWISE explicitly set to false
    • Office hours value matches the Exec Sheet
    • 30 days spell expiry set in the constructor (block.timestamp + 30 days)
  • Spell description

    • Description follows the format TARGET_DATE MakerDAO Executive Spell | Hash: EXEC_DOC_HASH
    • TARGET_DATE in the description matches the target date
    • Accompanying comment above spell description follows the format // Hash: cast keccak -- "$(wget 'EXEC_DOC_URL' -q -O - 2>/dev/null)"
  • Comments inside the spell

    • Every Section text from the Exec Sheet is copied to the spell code as a comment surrounded by the set of dashes (E.g. // ----- Section text -----)
    • Every Instruction text from the Exec Sheet is copied to the spell code as // Instruction text
    • Every Instruction text have newline above it
      ⚠️ This formatting rule is not held true for parameter-based instructions, this is done for better readability
    • IF an instruction can not be taken, it should have explanation under the instruction prefixed with // Note: (e.g.: // Note: Payments are skipped on goerli)
    • IF action in the spell doesn't have relevant instruction (e.g.: chainlog version bump), the necessity of it is explained in the comment above prefixed with // Note:
    • Every proof url from the Exec Sheet, such as Reasoning URL and Authority URL:
      • Is present in the spell code under relevant section or instruction (depending on which row the url is present)
      • Has the https scheme
      • Has prefix derived from the url itself
        • // Executive Vote: if URL starts with https://vote.sky.money/executive/
        • // Poll: if URL starts with https://vote.sky.money/polling/
        • // Forum: if URL starts with https://forum.sky.money/t/
        • // MIP: if URL starts with https://mips.makerdao.com/mips/details/
        • // Atlas: if URL starts with https://sky-atlas.powerhouse.io/
  • Dependency checks

    • Reinstall libraries by running rm -rf ./lib && git submodule update --init --recursive
      Submodule path 'lib/dss-exec-lib': checked out '69b658f35d8618272cd139dfc18c5713caf6b96b'
      Submodule path 'lib/dss-exec-lib/lib/dss-interfaces': checked out '9bfd7afadd1f8c217ef05850b2555691786286cb'
      Submodule path 'lib/dss-exec-lib/lib/forge-std': checked out '0aa99eb8456693c015350c5e6c4f442ebe912f77'
      Submodule path 'lib/dss-exec-lib/lib/forge-std/lib/ds-test': checked out 'cd98eff28324bfac652e63a239a60632a761790b'
      Submodule path 'lib/dss-test': checked out '61cf29fc0cf0c177a3b4072b433c43a7326ccd7b'
      Submodule path 'lib/dss-test/lib/dss-interfaces': checked out '9bfd7afadd1f8c217ef05850b2555691786286cb'
      Submodule path 'lib/dss-test/lib/forge-std': checked out 'da591f56d8884c5824c0c1b3103fbcfd81123c4c'
    • IF submodule upgrades are present, make sure dss-exec-lib is synced as well
    • git submodule hash of dss-exec-lib (run git submodule status) matches the latest release version or newer
    • dss-interfaces library used inside lib/dss-exec-lib matches submodule used inside lib/dss-test
  • IF interfaces are present in the spell

    • Interfaces imported from dss-interfaces
      • No unused dss-interfaces
      • Only single import layout is used (e.g. import {VatAbstract} from "dss-interfaces/dss/VatAbstract.sol";)
    • Static Interfaces
      • No unused static interfaces
      • Declared static interface not present in the dss-interfaces, OTHERWISE should be imported from there
      • Interface matches deployed contract using cast interface <contract_address> command
      • Interface naming style should match with Like suffix (e.g. VatLike)
      • Each static interface declare only functions actually used in the spell code
  • IF variable declarations are present in the spell

    • IF precision units are present
      • Precision units used in the spell match their defined values:
        • WAD = 10 ** 18
        • RAY = 10 ** 27
        • RAD = 10 ** 45
      • Precision units match with Numerical Ranges
      • Each variable visibility is declared as internal
      • Each variable state mutability is declared as constant
    • IF math units are present
      • Match their defined values:
        • HUNDRED = 10 ** 2
        • THOUSAND = 10 ** 3
        • MILLION = 10 ** 6
        • BILLION = 10 ** 9
      • Match with config
      • Each variable visibility is declared as internal
      • Each variable state mutability is declared as constant
    • IF rates are present
      • Rates match generated locally via make rates pct=<pct> (e.g. pct=0.75, for 0.75%)
      • Rates match IPFS document
      • Rate variable name conforms to X_PT_Y_Z_PCT_RATE (e.g. ZERO_PT_SEVEN_FIVE_PCT_RATE for 0.75%)
      • Rate variable visibility declared as internal
      • Rate variable state mutability declared as constant
      • Rates are defined in the ascending order (from smallest to largest)
    • IF timestamps are present
      • Comment above timestamp states full date including UTC timezone
      • Timestamp converts back to the correct date
      • Timestamp converts back to the UTC timezone
      • Variable naming matches MMM_DD_YYYY (e.g. JAN_01_2023 for 2023-01-01)
      • Time of day makes logical sense in the context of timestamp usage (i.e. 23:59:59 UTC for the final day of something, 00:00:00 UTC for the first day of something)
      • Each variable visibility is declared as internal
      • Each variable state mutability is declared as constant
  • IF new contract is present in the spell (not yet on chainlog or new to chainlog)

    • SkyOFTAdapter | 0x1e1D42781FC170EF9da004Fb735f56F0276d01B8 | Audit by ChainSecurity | Audit by Cantina | Source

      • Source code is verified on etherscan
      • Compilation optimizations match deployment settings defined in the source code repo
        ℹ️ Yes with 20000 runs
      • GNU AGPLv3 license
        ⚠️ Apache-2.0 license is used instead
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        ℹ️ _token matches USDS from Chainlog
        ℹ️ _lzEndpoint is the LayerZero Endpoint V2 at 0x1a44076050125825900e736c501f859c50fE728c
        ℹ️ _delegate is MCD_PAUSE_PROXY from Chainlog (switched after deployment)
      • IF new contract have concept of wards or access control
        ℹ️ Has the concept of owner, PauseProxy is the owner
        • Ensure PAUSE_PROXY address was relied (wards(PAUSE_PROXY) is 1)
        • Ensure that contract deployer address was denied (wards(deployer) is 0)
        • Ensure that there are no other Rely events except for PAUSE_PROXY (using a block explorer like etherscan)
          ⚠️ Ownership has been transferred after configuration, see additional checks
      • Additional checks for transactions
      • Source code matches corresponding github source code (e.g. diffcheck via vscode code --diff etherscan.sol github.sol)
        ⚠️ There is only a difference in License - Apache 2 is used instead of MIT
        ⚠️ The code was additionally verified using forge verify-bytecode. The fondry.toml had to be adjusted for getting a partial match: solc-version changed to solc_version, evm_version changed to paris
      • Deployer address is included into addresses_deployers.sol
        ℹ️ 0x12E85B7a985283bbFf212A059e2D226397b78F95
    • L1GovernanceRelay | 0x2beBFe397D497b66cB14461cB6ee467b4C3B7D61 | Audit by ChainSecurity | Audit by Cantina | Source

      • Source code is verified on etherscan
      • Compilation optimizations match deployment settings defined in the source code repo
        ℹ️ Yes with 200 runs
      • GNU AGPLv3 license
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        ℹ️ No constructor arguments
      • IF new contract have concept of wards or access control
        • Ensure PAUSE_PROXY address was relied (wards(PAUSE_PROXY) is 1)
        • Ensure that contract deployer address was denied (wards(deployer) is 0)
        • Ensure that there are no other Rely events except for PAUSE_PROXY (using a block explorer like etherscan)
      • Source code matches corresponding github source code (e.g. diffcheck via vscode code --diff etherscan.sol github.sol)
        ℹ️ Code has been additionally checked using forge verify-bytecode
      • Deployer address is included into addresses_deployers.sol
        ℹ️ 0x54eAde20f7DD1A67624626A3DB9408185eD0039e
    • GovernanceOAppSender | 0x27FC1DD771817b53bE48Dc28789533BEa53C9CCA | Audit by ChainSecurity | Audit by Cantina | Source

      • Source code is verified on etherscan
      • Compilation optimizations match deployment settings defined in the source code repo
        ⚠️ The value displayed in Etherscan (Yes with 200 runs) mismatches the value from the repository (Yes with 20000 runs). This discrepancy is not critical
      • GNU AGPLv3 license
        ⚠️ Apache-2.0 license is used instead
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        ℹ️ address _endpoint is the LayerZero Endpoint V2 at 0x1a44076050125825900e736c501f859c50fE728c
        ℹ️ address _owner is MCD_PAUSE_PROXY from Chainlog
      • IF new contract have concept of wards or access control
        ℹ️ Has the concept of owner, PauseProxy is the owner
        • Ensure PAUSE_PROXY address was relied (wards(PAUSE_PROXY) is 1)
        • Ensure that contract deployer address was denied (wards(deployer) is 0)
        • Ensure that there are no other Rely events except for PAUSE_PROXY (using a block explorer like etherscan)
          ⚠️ Ownership has been transferred after configuration, see additional checks
      • Additional checks for transactions
      • Source code matches corresponding github source code (e.g. diffcheck via vscode code --diff etherscan.sol github.sol)
      • Deployer address is included into addresses_deployers.sol
        ℹ️ 0x12E85B7a985283bbFf212A059e2D226397b78F95
  • IF core system parameter changes are present in the instructions

  • IF debt ceiling changes are present in the instructions

  • IF additional dependencies (i.e. ./src/dependencies/ directory) are present:
    ℹ️ dependencies/wh-lz-migration and dependencies/lz-governance-relay

    • IF the dependencies contracts/libraries have been audited
      ℹ️ WH LZ Migration: audit by ChainSecurity, audit by Cantina
      ℹ️ Governance Relay: audit by ChainSecurity, audit by Cantina
      • Each contract/library exactly matches (i.e. diff check) the source code of the latest audited version
    • OTHERWISE obtain the permalink to the relevant repository from a trusted party (i.e. Gov Facilitators)
      • Each contract/library exactly matches (i.e. diff check) the source code from the permalink
  • IF onboarding is present

  • IF PSM migration, onboarding or offboarding is present:

  • IF D3M onboarding is present, insert and follow D3M Checklist

  • IF crypto collateral offboarding is present in the spell

    • 1st stage collateral offboarding
      • Collateral type (ilk) is removed from AutoLine (MCD_IAM_AUTO_LINE) IF currently enabled
      • Collateral debt ceiling (vat.ilk.line) is set to 0
      • Global debt ceiling (vat.Line) decreased by the total amount of offboarded ilks
    • 2nd stage collateral offboarding
      • All actions from the 1st stage offboarding are previously taken (EITHER in the current or past spells – check the archive)
      • Collateral liquidation penalty (chop) is set to 0 IF requested by governance
      • Flat keeper incentive (tip) is set to 0 IF requested by governance
      • Relative keeper incentive (chip) is set to 0 IF requested by governance
      • Max liquidation amount (hole) is adjusted via DssExecLib.setIlkMaxLiquidationAmount(ilk, amount) IF requested by governance
      • Relevant clipper contract (MCD_CLIP_) is active (i.e. stopped is 0)
      • Liquidations are triggered via (depending on governance instruction):
        • EITHER liquidation ratio (spotter.ilk.mat) being set very high in the spell (using DssExecLib.setValue(DssExecLib.spotter(), ilk, "mat", ratio))
        • OR via enabling linear interpolation (DssExecLib.linearInterpolation(name, target, ilk, what, startTime, start, end, duration))
          • Ensure name format matches "XXX-X Offboarding"
          • Ensure target matches DssExecLib.spotter() address
          • Ensure ilk format matches collateral type (ilk) name ("XXX-X")
          • Ensure what matches string "mat"
          • Ensure startTime matches block.timestamp
          • Ensure start uses variable CURRENT_XXX_A_MAT
          • Ensure start matches current spotter.ilk.mat value
          • Ensure end uses variable TARGET_XXX_A_MAT
          • Ensure end value matches the instruction
          • Ensure end allows liquidation of all remaining vaults (end is bigger than collateral_type_collateralization_ratio * risk_multiplier_factor)
          • Ensure duration matches the instruction
      • Spotter price is updated via DssExecLib.updateCollateralPrice(ilk) IF collateral have no running oracle (i.e. relevant PIP_ contract have outdated zzz value)
      • Spotter price is updated after all other actions
      • Offboarding is tested at least via _checkIlkClipper helper
  • IF RWA updates are present

    • Insert and follow the relevant checklists below:
  • IF RWA offboardings are present

  • IF payments are present in the spell

    • IF SKY transfers are present
      • Recipient address in the instruction is in the checksummed format
      • Recipient address matches Exec Sheet
      • Recipient address variable name matches one found in addresses_wallets.sol
      • Transfer amount matches Exec Sheet
      • The transfers are tested via testPayments test
      • Sum of all SKY transfers tested in testPayments matches number in the Exec Sheet
    • IF USDS surplus buffer transfers are present
      • Recipient address in the instruction is in the checksummed format
      • Recipient address matches Exec Sheet
      • Recipient address variable name matches one found in addresses_wallets.sol
      • Transfer amount matches Exec Sheet
      • The transfers are tested via testPayments test
      • Sum of all USDS transfers tested in testPayments matches number in the Exec Sheet
    • IF DAI / SKY / USDS / SPK streams (DssVest) are created
      • VestAbstract interface is imported from dss-interfaces/dss/VestAbstract.sol
      • restrict is used for each stream, UNLESS otherwise explicitly stated in the Exec Sheet
      • usr (Vest recipient address) matches Exec Sheet
      • usr address in the instruction is in the checksummed format
      • usr address variable name match one found in addresses_wallets.sol
      • tot (Total stream amount) matches Exec Sheet
      • IF ether keyword is used, comment is present on the same line // Note: ether is a keyword that represents 10**18, not the ETH token
      • IF vest amount is expressed in 'per year' or similar in the Exec Sheet, account for leap days
      • bgn (Vest start timestamp) matches Exec Sheet
      • tau is expressed as EITHER:
        • fin - bgn (i.e. MONTH_DD_YYYY - MONTH_DD_YYYY)
          • fin (Vest end timestamp) matches Exec Sheet
        • time interval (e.g. 365 days)
      • eta (Vest cliff duration) matches the following logic
        • IF eta is explicitly specified in the Exec Sheet, then the values match
        • IF eta and clf (Cliff end timestamp) are not specified in the Exec Sheet, then eta is 0
        • IF clf is specified, but clf <= bgn, then eta is 0
        • IF clf is specified and clf > bgn, eta is expressed as clf - bgn (i.e. MONTH_DD_YYYY - MONTH_DD_YYYY)
      • IF mgr (Vest manager address) is specified in the Exec Sheet, matches the value, OTHERWISE matches address(0)
      • Ensure that max vesting rate (cap) is enough for the new streams
        • The maximum vesting rate (tot divided by tau) <= the maximum vest streaming rate (cap)
        • The maximum vesting rate (tot divided by tau) > the maximum vest streaming rate (cap)
        • Calculate new cap value equal to 10% greater than the new maximum vesting rate, then round new cap up with 2 significant figure precision (i.e. 2446 becomes 2500)
      • IF max vesting rate (cap) is changed in the spell
        • Governance facilitators were notified
        • Exec Sheet contains explicit instruction
        • Exec Doc contains explicit instruction
      • IF new SKY streams (DssVestTransferrable) are present
        • Vest contract's SKY allowance increased by the cumulative total (the sum of all tot values)
        • Ensure allowance increase follows archive patterns
      • IF new SPK streams (DssVestTransferrable) are present
        • Vest contract's SPK allowance increased by the cumulative total (the sum of all tot values)
        • Ensure allowance increase follows archive patterns
      • Tested via:
        • testVestDai
        • testVestSky
        • testVestSkyMint
        • testVestUsds
        • testVestSpk
    • IF DAI / SKY / USDS / SPK vest termination (Yank) is present
      • Yanked stream ID matches Exec Sheet
      • MCD_VEST_SKY_TREASURY chainlog address is used for SKY stream yank
      • MCD_VEST_SPK_TREASURY chainlog address is used for SPK stream yank
      • MCD_VEST_DAI chainlog address is used for DAI stream yank
      • MCD_VEST_USDS chainlog address is used for USDS stream yank
      • Tested via:
        • testVestDai
        • testVestSky
        • testVestSkyMint
        • testVestUsds
        • testVestSpk
  • IF SubDAO-related content is present

    • IF SubDAO provides SubProxy spell address
      • SubDAO spell address matches Exec Sheet
      • Executed via ProxyLike(SUBDAO_PROXY).exec(SUBDAO_SPELL, abi.encodeWithSignature("execute()"));
      • Execution is NOT delegate call
      • IF SubDAO spell deployer is a smart contract (e.g. multisig or factory), ensure the deployer address is in addresses_deployers.sol as an entry
      • Ensure that SubDAO spell have enough gas and does not revert with "out of gas" error inside simulation. Note: low level call gas estimation is not done by our scripts
    • IF SubDAO provides instructions to be executed by the main spell (i.e. that will operate within Pause Proxy DelegateCall context)
      • No SubDAO contract being interacted with is authed on a core contract like vat, etc. (Check comprehensively where the risk is high)
      • SubDAO contract licensing and optimizations generally do not matter (except where they pose a security risk)
      • SubDAO contracts and all libraries / dependencies have verified source code (Blocking)
      • Upgradable SubDAO contracts
        • Upgradable contracts have the PAUSE_PROXY as their admin (i.e. the party that can upgrade)
        • Any upgradable SubDAO contracts with an admin that is not PAUSE_PROXY are not authed on any core contracts (Blocking)
      • All SubDAO content addresses (i.e. provided contract addresses or EOAs) present in the Maker Core spell are present in the Exec Sheet and are correct. SubDAO addresses being authed or given any permissions MUST be in the Exec Sheet. SubDAO addresses being called must be confirmed by the SubDAO spell team.
      • IF addresses not PR'ed in by the SubDAO team (use git blame for example), SubDAO content addresses all have inline comment for provenance or source being OKed by SubDAO
      • SubDAO actions match Exec Sheet (only where inline with main spell code) and do not affect core contracts
      • Core contract knock-on actions (such as offboarding or setting DC to 0) are present in the exec and match the code
      • External calls for SubDAO content are NOT delegate call
      • Code does not have untoward behavior within the scope of Maker Core Contracts (e.g. up to the SubDAO proxy)
  • IF external contracts calls are present (Not SubDAOs, e.g. Starknet)
    ℹ️ NTT_MANAGER, USDS_OFT, LZ_GOV_RELAY

    • Target Contract doesn't block spell execution
    • External call is NOT delegatecall
    • Target Contract doesn't have permissions on the Vat
    • Target Contract doesn't do anything untoward (e.g. interacting with unsafe contracts)
    • Contracts deployed via CREATE2 (e.g. if it looks like a vanity address) do not have selfdestruct in their code
      ℹ️ No CREATE2-deployed contracts
    • MCD Pause Proxy doesn't give any approvals
    • All possible actions of the Target Contract are documented
    • Target contract is not upgradable
      ⚠️ The NttManager contract is upgradable only by its owner (MCD_PAUSE_PROXY)
    • Target Contract is included in the ChainLog
      ⚠️ The NTT_MANAGER contract is not included in the chainlog. The reasoning is that this contract is only meant for a transition from Wormhole to LayerZero infrastructure; the token transfer functionality has been removed from its implementation, limiting its usability by design.
    • Test Coverage is comprehensive
  • IF spell interacts with ChainLog

    • ChainLog version is incremented based on update type
      • Major -> New Vat (++.0.0)
      • Minor -> Core Module (DSS) Update (e.g. Flapper) (0.++.0)
      • Patch -> Collateral addition or addition/modification (0.0.++)
    • New addresses are added to the addresses_mainnet.sol
      ℹ️ USDS_OFT, LZ_GOV_SENDER, LZ_GOV_RELAY
    • Changes are tested via testChainlogIntegrity, testChainlogValues, testAddedChainlogKeys and testRemovedChainlogKeys
  • Ensure every spell variable is declared as public/internal

  • Ensure immutable visibility is only used when fetching addresses from the ChainLog via DssExecLib.getChangelogAddress(key) and constant is used instead for static addresses
    ℹ️ No Chainlog addresses are fetched

    • Fetch addresses as type address and wrap with Like suffix interfaces inline (when making calls), UNLESS archive patterns permit otherwise (such as SKY)
    • Use the DssExecLib Core Address Helpers where possible (e.g. DssExecLib.vat())
    • Where addresses are fetched from the ChainLog, the variable name must match the value of the ChainLog key for that address (e.g. MCD_VAT rather than vat)
  • Tests

    • Ensure that the DssExecLib.address file is not being modified by the spell PR
    • Check all CI tests are passing as at the latest commit
      ℹ️ e0c1a0c
    • Ensure every test function is declared as public
      • IF the test needs to run, it MUST NOT have the skipped modifier; OTHERWISE, it MUST have the skipped modifier
    • Ensure each spell action has sufficient test coverage
    • Ensure that any other env variable does not affect execution of the tests (for example, by inspecting the output of printenv | grep "FOUNDRY_\|DAPP_")
    • IF a new module is initialized via the spell, the tests must include
      ℹ️ USDS_OFT, LZ_GOV_SENDER, LZ_GOV_RELAY
      • Sanity checks of the constructor arguments
        ℹ️ testLayerZeroContractsSanity
      • Sanity checks of all values added/updated by the spell function
        ℹ️testMigrationStep1, testGovernanceRelayInit
      • End-to-end "happy path" interaction with the module
    • Check all tests are passing locally using make test
      • Ensure every test listed in the coverage item above is present in the logs and with the [PASS] prefix.
./scripts/test-dssspell-forge.sh no-match="" match="" block=""
Using DssExecLib at: 0x8De6DDbCd5053d32292AAA0D2105A32d108484a6
[⠊] Compiling...
[⠒] Compiling 1 files with Solc 0.8.16
[⠑] Solc 0.8.16 finished in 1.50s
Compiler run successful!

Ran 2 tests for src/test/starknet.t.sol:StarknetTests
[PASS] testStarknet() (gas: 968934)
[PASS] testStarknetSpell() (gas: 2391)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.74s (1.42s CPU time)

Ran 49 tests for src/DssSpell.t.sol:DssSpellTest
[PASS] testAddedChainlogKeys() (gas: 838453)
[SKIP] testAllocatorIntegration() (gas: 0)
[SKIP] testBloomSpellIsExecuted() (gas: 0)
[SKIP] testBytecodeMatches() (gas: 0)
[PASS] testCastCost() (gas: 807346)
[PASS] testCastOnTime() (gas: 794982)
[PASS] testChainlogIntegrity() (gas: 6166582)
[PASS] testChainlogValues() (gas: 10980161)
[SKIP] testCollateralIntegrations() (gas: 0)
[PASS] testContractSize() (gas: 15810)
[SKIP] testDaoResolutions() (gas: 0)
[PASS] testDeployCost() (gas: 3686996)
[SKIP] testEsmAuth() (gas: 0)
[PASS] testGeneral() (gas: 17451182)
[PASS] testGovernanceRelayInit() (gas: 1555305)
[SKIP] testIlkClipper() (gas: 0)
[SKIP] testL2ArbitrumSpell() (gas: 0)
[SKIP] testL2OptimismSpell() (gas: 0)
[PASS] testLayerZeroContractsSanity() (gas: 76603)
[SKIP] testLerpSurplusBuffer() (gas: 0)
[PASS] testLitePSMs() (gas: 2015341)
[SKIP] testLockstakeIlkIntegration() (gas: 0)
[SKIP] testMedianReaders() (gas: 0)
[PASS] testMigrationStep1() (gas: 1560483)
[SKIP] testMonthlySettlementCycleInflows() (gas: 0)
[SKIP] testNewAuthorizations() (gas: 0)
[SKIP] testNewCronJobs() (gas: 0)
[PASS] testNextCastTime() (gas: 341748)
[SKIP] testNovaSpellIsExecuted() (gas: 0)
[SKIP] testObexSpellIsExecuted() (gas: 0)
[SKIP] testOffboardings() (gas: 0)
[PASS] testOfficeHours() (gas: 388643)
[SKIP] testOracleList() (gas: 0)
[SKIP] testOsmReaders() (gas: 0)
[PASS] testPSMs() (gas: 2231467)
[SKIP] testPayments() (gas: 0)
[SKIP] testRemovedChainlogKeys() (gas: 0)
[PASS] testRevertIfNotScheduled() (gas: 17530)
[PASS] testSPBEAMTauAndBudValues() (gas: 811862)
[SKIP] testSparkSpellIsExecuted() (gas: 0)
[PASS] testSplitter() (gas: 1353431)
[PASS] testSystemTokens() (gas: 1900194)
[PASS] testUseEta() (gas: 238316)
[SKIP] testVestDai() (gas: 0)
[SKIP] testVestMkr() (gas: 0)
[SKIP] testVestSky() (gas: 0)
[SKIP] testVestSkyMint() (gas: 0)
[SKIP] testVestSpk() (gas: 0)
[SKIP] testVestUsds() (gas: 0)
Suite result: ok. 20 passed; 0 failed; 29 skipped; finished in 28.74s (92.91s CPU time)

Ran 2 test suites in 28.83s (34.47s CPU time): 22 tests passed, 0 failed, 29 skipped (51 total tests)

Pre-Deployment Stage

  • Wait till the Exec Doc is merged
  • Exec Doc checks
    • Exec Doc for the specified date is found in the sky-ecosystem/executive-votes GitHub repo

    • Exec Doc is located in the directory matching the target spell date year (YYYY/)

    • Exec Doc file name follows the format executive-vote-YYYY-MM-DD-optional-description.md
      ⚠️ oos- prefix is added for an Out-of-Schedule spell, the rest of the name follows the format

    • Extract permanent URL to the raw markdown file and paste it below
      ℹ️ https://raw.githubusercontent.com/sky-ecosystem/executive-votes/9c58a42c41808d17531aa56eeaa9bbe1799fd0f5/2025/oos-executive-vote-2025-11-17-solana-bridge-migration.md

    • Ensure the URL uses commit hash that introduced last change to the Exec Doc, NOT merge commit

      • IF there is no local copy of sky-ecosystem/executive-votes GitHub repo, run:
        git clone https://github.com/sky-ecosystem/executive-votes
        
      • OTHERWISE, ensure it is pointing to the latest commit on main:
        git switch main && git pull origin main
        
      • Get the latest commit hash for the exec doc:
        ℹ️ 9c58a42c41808d17531aa56eeaa9bbe1799fd0f5
        git log --pretty=oneline -1 -- "<LOCAL_PATH_TO_EXEC_DOC>"
        
    • Using Exec Doc URL from the above and the TARGET_DATE, generate Exec Doc Hash via make exec-hash date=$TARGET_DATE $URL
      ℹ️ 0x6e17f84690e8e325c78ff9fae8ead5fefcd9480d1d46358990e8baf4285ffda7

    • Using Exec Doc URL from the above, generate Exec Doc Hash via cast keccak -- "$(curl '$URL' -o - 2>/dev/null)"
      ℹ️ 0x6e17f84690e8e325c78ff9fae8ead5fefcd9480d1d46358990e8baf4285ffda7

    • Make sure that hash above doesn't match keccak hash of the empty string (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470)

    • Using Exec Doc URL from the above, read spell instructions from the Exec Doc and list them below

      • Solana Bridge Migration

        • Call MigrationInit.initMigrationStep1 with the following parameters:
          • oftAdapter: 0x1e1D42781FC170EF9da004Fb735f56F0276d01B8
          • oftPeer: BEvTHkTyXooyaJzP8egDUC7WQK8cyRrq5WvERZNWhuah
          • govOapp: 0x27FC1DD771817b53bE48Dc28789533BEa53C9CCA
          • govPeer: 8vXXGiaXFrKFUDw21H5Z57ex552Lh8WP9rVd2ktzmcCy
          • rl.outboundWindow: outbound duration of the rate limiting window:
            • 1 days (as specified in the Atlas edit)
          • rl.outboundLimit: outbound maximum allowed amount within a given window:
            • 10_000_000 USDS (as specified in the Atlas edit)
          • rl.inboundWindow: inbound duration of the rate limiting window:
            • 1 days (as specified in the Atlas edit)
          • rl.inboundLimit: inbound maximum allowed amount within a given window:
            • 10_000_000 USDS (as specified in the Atlas edit)
          • rl.rlAccountingType: accounting type (Net or Gross):
            • 0 meaning Net (as specified in the Atlas edit)
          • maxFee: expected to be 0 (unless Wormhole.messageFee() returns non-zero value)
          • transferMintAuthPayload: expected to contain cross-chain message sent via Wormhole.publishMessage that will transfer mint authority to the new oftPeer:
            • Generated by the ntt-transfer-mint-authority script
            • The actual payload can be found on GitHub
          • transferFreezeAuthPayload: expected to contain cross-chain message sent via Wormhole.publishMessage that will transfer freeze authority to the new govPeer:
            • Generated by the set-token-freeze-authority script
            • The actual payload found on GitHub
          • transferMetadataUpdateAuthPayload: expected to contain cross-chain message sent via Wormhole.publishMessage that will transfer “metadata update” authority to the new govPeer:
            • Generated by the update-mpl-metadata-authority script
            • The actual payload found on GitHub
        • Call GovernanceRelayInit.init with the following parameters:
          • l1GovernanceRelay: 0x2beBFe397D497b66cB14461cB6ee467b4C3B7D61
          • l1Oapp: 0x27FC1DD771817b53bE48Dc28789533BEa53C9CCA
      • Chainlog Additions

        • The following additions to the Chainlog will be made:
          • Add new SkyOFTAdapter (0x1e1D42781FC170EF9da004Fb735f56F0276d01B8) to the Chainlog as USDS_OFT
          • Add new GovernanceOAppSender (0x27FC1DD771817b53bE48Dc28789533BEa53C9CCA) to the Chainlog as LZ_GOV_SENDER
    • Office hours value in the Exec Doc matches the spell
      ℹ️ Yes

    • Sum of all payments in the Exec Doc matches the tests
      ℹ️ No payments

    • Exec Doc URL in the spell comment matches your Raw Exec Doc URL above

    • Exec Doc URL in the spell comment refers to the https://github.com/sky-ecosystem/executive-votes repository

    • Every action present in the spell code is present in the Exec Doc

    • Every action in the Exec Doc is present in the spell code

  • IF new commits are present in the spell
    • Copy relevant checklist items from the above and redo them
    • Ensure newly added code is covered by tests
    • Check if chainlog needs to be updated
    • Copy over and redo "Tests" section from the above
  • IF all checks pass, make sure to include explicit "Good to deploy" comment

@oddaf
Copy link
Member

oddaf commented Nov 14, 2025

Good to deploy:

Mainnet Executive Spell Review Checklist

Repo: https://github.com/sky-ecosystem/spells-mainnet

Development Stage

  • Install stable Foundry version
    • Install the stable version of Foundry via foundryup --install stable
      Document the installation logs containing installed versions below:
      
      foundryup: use - forge 1.4.4-stable (05794498bf 2025-11-03T23:47:15.213010000Z)
      foundryup: use - cast 1.4.4-stable (05794498bf 2025-11-03T23:47:15.213010000Z)
      foundryup: use - anvil 1.4.4-stable (05794498bf 2025-11-03T23:47:15.213010000Z)
      foundryup: use - chisel 1.4.4-stable (05794498bf 2025-11-03T23:47:15.213010000Z)    
      
  • Preparation
  • Base checks
    • Current solc version 0.8.16
    • Office hours is true IF spell introduces a major change that can affect external parties (e.g.: keepers are affected in case of collateral offboarding) OTHERWISE explicitly set to false
    • Office hours value matches the Exec Sheet
    • 30 days spell expiry set in the constructor (block.timestamp + 30 days)
  • Spell description
    • Description follows the format TARGET_DATE MakerDAO Executive Spell | Hash: EXEC_DOC_HASH
    • TARGET_DATE in the description matches the target date
    • Accompanying comment above spell description follows the format // Hash: cast keccak -- "$(wget 'EXEC_DOC_URL' -q -O - 2>/dev/null)"
  • Comments inside the spell
    • Every Section text from the Exec Sheet is copied to the spell code as a comment surrounded by the set of dashes (E.g. // ----- Section text -----)
    • Every Instruction text from the Exec Sheet is copied to the spell code as // Instruction text
    • Every Instruction text have newline above it
    • IF an instruction can not be taken, it should have explanation under the instruction prefixed with // Note: (e.g.: // Note: Payments are skipped on goerli)
    • IF action in the spell doesn't have relevant instruction (e.g.: chainlog version bump), the necessity of it is explained in the comment above prefixed with // Note:
    • Every proof url from the Exec Sheet, such as Reasoning URL and Authority URL:
      • Is present in the spell code under relevant section or instruction (depending on which row the url is present)
      • Has the https scheme
      • Has prefix derived from the url itself
        • // Executive Vote: if URL starts with https://vote.sky.money/executive/
        • // Poll: if URL starts with https://vote.sky.money/polling/
        • // Forum: if URL starts with https://forum.sky.money/t/
        • // MIP: if URL starts with https://mips.makerdao.com/mips/details/
        • // Atlas: if URL starts with https://sky-atlas.powerhouse.io/
  • Dependency checks
    • Reinstall libraries by running rm -rf ./lib && git submodule update --init --recursive
      Insert checked out submodule paths here
      Submodule path 'lib/dss-exec-lib': checked out '69b658f35d8618272cd139dfc18c5713caf6b96b'
      Submodule path 'lib/dss-exec-lib/lib/dss-interfaces': checked out '9bfd7afadd1f8c217ef05850b2555691786286cb'
      Submodule path 'lib/dss-exec-lib/lib/forge-std': checked out '0aa99eb8456693c015350c5e6c4f442ebe912f77'
      Submodule path 'lib/dss-exec-lib/lib/forge-std/lib/ds-test': checked out 'cd98eff28324bfac652e63a239a60632a761790b'
      Submodule path 'lib/dss-test': checked out '61cf29fc0cf0c177a3b4072b433c43a7326ccd7b'
      Submodule path 'lib/dss-test/lib/dss-interfaces': checked out '9bfd7afadd1f8c217ef05850b2555691786286cb'
      Submodule path 'lib/dss-test/lib/forge-std': checked out 'da591f56d8884c5824c0c1b3103fbcfd81123c4c'
    • IF submodule upgrades are present, make sure dss-exec-lib is synced as well
    • git submodule hash of dss-exec-lib (run git submodule status) matches the latest release version or newer
    • dss-interfaces library used inside lib/dss-exec-lib matches submodule used inside lib/dss-test
  • IF interfaces are present in the spell
    • Interfaces imported from dss-interfaces
      • No unused dss-interfaces
      • Only single import layout is used (e.g. import {VatAbstract} from "dss-interfaces/dss/VatAbstract.sol";)
    • Static Interfaces
      • No unused static interfaces
      • Declared static interface not present in the dss-interfaces, OTHERWISE should be imported from there
      • Interface matches deployed contract using cast interface <contract_address> command
      • Interface naming style should match with Like suffix (e.g. VatLike)
      • Each static interface declare only functions actually used in the spell code
  • IF variable declarations are present in the spell
    • IF precision units are present
      • Precision units used in the spell match their defined values:
        • WAD = 10 ** 18
        • RAY = 10 ** 27
        • RAD = 10 ** 45
      • Precision units match with Numerical Ranges
      • Each variable visibility is declared as internal
      • Each variable state mutability is declared as constant
    • IF math units are present
      • Match their defined values:
        • HUNDRED = 10 ** 2
        • THOUSAND = 10 ** 3
        • MILLION = 10 ** 6
        • BILLION = 10 ** 9
      • Match with config
      • Each variable visibility is declared as internal
      • Each variable state mutability is declared as constant
    • IF rates are present
      • Rates match generated locally via make rates pct=<pct> (e.g. pct=0.75, for 0.75%)
      • Rates match IPFS document
      • Rate variable name conforms to X_PT_Y_Z_PCT_RATE (e.g. ZERO_PT_SEVEN_FIVE_PCT_RATE for 0.75%)
      • Rate variable visibility declared as internal
      • Rate variable state mutability declared as constant
      • Rates are defined in the ascending order (from smallest to largest)
    • IF timestamps are present
      • Comment above timestamp states full date including UTC timezone
      • Timestamp converts back to the correct date
      • Timestamp converts back to the UTC timezone
      • Variable naming matches MMM_DD_YYYY (e.g. JAN_01_2023 for 2023-01-01)
      • Time of day makes logical sense in the context of timestamp usage (i.e. 23:59:59 UTC for the final day of something, 00:00:00 UTC for the first day of something)
      • Each variable visibility is declared as internal
      • Each variable state mutability is declared as constant
  • IF new contract is present in the spell (not yet on chainlog or new to chainlog)
    • Source code is verified on etherscan
    • Compilation optimizations match deployment settings defined in the source code repo
    • GNU AGPLv3 license
    • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
    • IF new contract have concept of wards or access control
      • Ensure PAUSE_PROXY address was relied (wards(PAUSE_PROXY) is 1)
      • Ensure that contract deployer address was denied (wards(deployer) is 0)
      • Ensure that there are no other Rely events except for PAUSE_PROXY (using a block explorer like etherscan)
    • Source code matches corresponding github source code (e.g. diffcheck via vscode code --diff etherscan.sol github.sol)
    • Deployer address is included into addresses_deployers.sol
    • For detailed list of contracts/checks performed please refer to: https://forum.sky.money/t/solana-bridge-migration/27403/2
  • IF core system parameter changes are present in the instructions
  • IF debt ceiling changes are present in the instructions
  • IF additional dependencies (i.e. ./src/dependencies/ directory) are present:
    • IF the dependencies contracts/libraries have been audited
      • Each contract/library exactly matches (i.e. diff check) the source code of the latest audited version
      • Please refer to the following forum post for the full steps of verification (including matching against audit commits and bytecode verification): https://forum.sky.money/t/solana-bridge-migration/27403/2
    • OTHERWISE obtain the permalink to the relevant repository from a trusted party (i.e. Gov Facilitators)
      • Each contract/library exactly matches (i.e. diff check) the source code from the permalink
  • IF onboarding is present
  • IF PSM migration, onboarding or offboarding is present:
  • IF D3M onboarding is present, insert and follow D3M Checklist
  • IF crypto collateral offboarding is present in the spell
    • 1st stage collateral offboarding
      • Collateral type (ilk) is removed from AutoLine (MCD_IAM_AUTO_LINE) IF currently enabled
      • Collateral debt ceiling (vat.ilk.line) is set to 0
      • Global debt ceiling (vat.Line) decreased by the total amount of offboarded ilks
    • 2nd stage collateral offboarding
      • All actions from the 1st stage offboarding are previously taken (EITHER in the current or past spells – check the archive)
      • Collateral liquidation penalty (chop) is set to 0 IF requested by governance
      • Flat keeper incentive (tip) is set to 0 IF requested by governance
      • Relative keeper incentive (chip) is set to 0 IF requested by governance
      • Max liquidation amount (hole) is adjusted via DssExecLib.setIlkMaxLiquidationAmount(ilk, amount) IF requested by governance
      • Relevant clipper contract (MCD_CLIP_) is active (i.e. stopped is 0)
      • Liquidations are triggered via (depending on governance instruction):
        • EITHER liquidation ratio (spotter.ilk.mat) being set very high in the spell (using DssExecLib.setValue(DssExecLib.spotter(), ilk, "mat", ratio))
        • OR via enabling linear interpolation (DssExecLib.linearInterpolation(name, target, ilk, what, startTime, start, end, duration))
          • Ensure name format matches "XXX-X Offboarding"
          • Ensure target matches DssExecLib.spotter() address
          • Ensure ilk format matches collateral type (ilk) name ("XXX-X")
          • Ensure what matches string "mat"
          • Ensure startTime matches block.timestamp
          • Ensure start uses variable CURRENT_XXX_A_MAT
          • Ensure start matches current spotter.ilk.mat value
          • Ensure end uses variable TARGET_XXX_A_MAT
          • Ensure end value matches the instruction
          • Ensure end allows liquidation of all remaining vaults (end is bigger than collateral_type_collateralization_ratio * risk_multiplier_factor)
          • Ensure duration matches the instruction
      • Spotter price is updated via DssExecLib.updateCollateralPrice(ilk) IF collateral have no running oracle (i.e. relevant PIP_ contract have outdated zzz value)
      • Spotter price is updated after all other actions
      • Offboarding is tested at least via _checkIlkClipper helper
  • IF RWA updates are present
    • Insert and follow the relevant checklists below:
  • IF RWA offboardings are present
  • IF payments are present in the spell
    • IF SKY transfers are present
      • Recipient address in the instruction is in the checksummed format
      • Recipient address matches Exec Sheet
      • Recipient address variable name matches one found in addresses_wallets.sol
      • Transfer amount matches Exec Sheet
      • The transfers are tested via testPayments test
      • Sum of all SKY transfers tested in testPayments matches number in the Exec Sheet
    • IF USDS surplus buffer transfers are present
      • Recipient address in the instruction is in the checksummed format
      • Recipient address matches Exec Sheet
      • Recipient address variable name matches one found in addresses_wallets.sol
      • Transfer amount matches Exec Sheet
      • The transfers are tested via testPayments test
      • Sum of all USDS transfers tested in testPayments matches number in the Exec Sheet
    • IF DAI / SKY / USDS / SPK streams (DssVest) are created
      • VestAbstract interface is imported from dss-interfaces/dss/VestAbstract.sol
      • restrict is used for each stream, UNLESS otherwise explicitly stated in the Exec Sheet
      • usr (Vest recipient address) matches Exec Sheet
      • usr address in the instruction is in the checksummed format
      • usr address variable name match one found in addresses_wallets.sol
      • tot (Total stream amount) matches Exec Sheet
      • IF ether keyword is used, comment is present on the same line // Note: ether is a keyword that represents 10**18, not the ETH token
      • IF vest amount is expressed in 'per year' or similar in the Exec Sheet, account for leap days
      • bgn (Vest start timestamp) matches Exec Sheet
      • tau is expressed as EITHER:
        • fin - bgn (i.e. MONTH_DD_YYYY - MONTH_DD_YYYY)
          • fin (Vest end timestamp) matches Exec Sheet
        • time interval (e.g. 365 days)
      • eta (Vest cliff duration) matches the following logic
        • IF eta is explicitly specified in the Exec Sheet, then the values match
        • IF eta and clf (Cliff end timestamp) are not specified in the Exec Sheet, then eta is 0
        • IF clf is specified, but clf <= bgn, then eta is 0
        • IF clf is specified and clf > bgn, eta is expressed as clf - bgn (i.e. MONTH_DD_YYYY - MONTH_DD_YYYY)
      • IF mgr (Vest manager address) is specified in the Exec Sheet, matches the value, OTHERWISE matches address(0)
      • Ensure that max vesting rate (cap) is enough for the new streams
        • The maximum vesting rate (tot divided by tau) <= the maximum vest streaming rate (cap)
        • The maximum vesting rate (tot divided by tau) > the maximum vest streaming rate (cap)
        • Calculate new cap value equal to 10% greater than the new maximum vesting rate, then round new cap up with 2 significant figure precision (i.e. 2446 becomes 2500)
      • IF max vesting rate (cap) is changed in the spell
        • Governance facilitators were notified
        • Exec Sheet contains explicit instruction
        • Exec Doc contains explicit instruction
      • IF new SKY streams (DssVestTransferrable) are present
        • Vest contract's SKY allowance increased by the cumulative total (the sum of all tot values)
        • Ensure allowance increase follows archive patterns
      • IF new SPK streams (DssVestTransferrable) are present
        • Vest contract's SPK allowance increased by the cumulative total (the sum of all tot values)
        • Ensure allowance increase follows archive patterns
      • Tested via:
        • testVestDai
        • testVestSky
        • testVestSkyMint
        • testVestUsds
        • testVestSpk
    • IF DAI / SKY / USDS / SPK vest termination (Yank) is present
      • Yanked stream ID matches Exec Sheet
      • MCD_VEST_SKY_TREASURY chainlog address is used for SKY stream yank
      • MCD_VEST_SPK_TREASURY chainlog address is used for SPK stream yank
      • MCD_VEST_DAI chainlog address is used for DAI stream yank
      • MCD_VEST_USDS chainlog address is used for USDS stream yank
      • Tested via:
        • testVestDai
        • testVestSky
        • testVestSkyMint
        • testVestUsds
        • testVestSpk
  • IF SubDAO-related content is present
    • IF SubDAO provides SubProxy spell address
      • SubDAO spell address matches Exec Sheet
      • Executed via ProxyLike(SUBDAO_PROXY).exec(SUBDAO_SPELL, abi.encodeWithSignature("execute()"));
      • Execution is NOT delegate call
      • IF SubDAO spell deployer is a smart contract (e.g. multisig or factory), ensure the deployer address is in addresses_deployers.sol as an entry
      • Ensure that SubDAO spell have enough gas and does not revert with "out of gas" error inside simulation. Note: low level call gas estimation is not done by our scripts
    • IF SubDAO provides instructions to be executed by the main spell (i.e. that will operate within Pause Proxy DelegateCall context)
      • No SubDAO contract being interacted with is authed on a core contract like vat, etc. (Check comprehensively where the risk is high)
      • SubDAO contract licensing and optimizations generally do not matter (except where they pose a security risk)
      • SubDAO contracts and all libraries / dependencies have verified source code (Blocking)
      • Upgradable SubDAO contracts
        • Upgradable contracts have the PAUSE_PROXY as their admin (i.e. the party that can upgrade)
        • Any upgradable SubDAO contracts with an admin that is not PAUSE_PROXY are not authed on any core contracts (Blocking)
      • All SubDAO content addresses (i.e. provided contract addresses or EOAs) present in the Maker Core spell are present in the Exec Sheet and are correct. SubDAO addresses being authed or given any permissions MUST be in the Exec Sheet. SubDAO addresses being called must be confirmed by the SubDAO spell team.
      • IF addresses not PR'ed in by the SubDAO team (use git blame for example), SubDAO content addresses all have inline comment for provenance or source being OKed by SubDAO
      • SubDAO actions match Exec Sheet (only where inline with main spell code) and do not affect core contracts
      • Core contract knock-on actions (such as offboarding or setting DC to 0) are present in the exec and match the code
      • External calls for SubDAO content are NOT delegate call
      • Code does not have untoward behavior within the scope of Maker Core Contracts (e.g. up to the SubDAO proxy)
  • IF external contracts calls are present (Not SubDAOs, e.g. Starknet)
    • Target Contract doesn't block spell execution
    • External call is NOT delegatecall
    • Target Contract doesn't have permissions on the Vat
    • Target Contract doesn't do anything untoward (e.g. interacting with unsafe contracts)
    • Contracts deployed via CREATE2 (e.g. if it looks like a vanity address) do not have selfdestruct in their code
    • MCD Pause Proxy doesn't give any approvals
    • All possible actions of the Target Contract are documented
    • Target contract is not upgradable
    • Target Contract is included in the ChainLog
    • Test Coverage is comprehensive
  • IF spell interacts with ChainLog
    • ChainLog version is incremented based on update type
      • Major -> New Vat (++.0.0)
      • Minor -> Core Module (DSS) Update (e.g. Flapper) (0.++.0)
      • Patch -> Collateral addition or addition/modification (0.0.++)
    • New addresses are added to the addresses_mainnet.sol
    • Changes are tested via testChainlogIntegrity, testChainlogValues, testAddedChainlogKeys and testRemovedChainlogKeys
  • Ensure every spell variable is declared as public/internal
  • Ensure immutable visibility is only used when fetching addresses from the ChainLog via DssExecLib.getChangelogAddress(key) and constant is used instead for static addresses
    • Fetch addresses as type address and wrap with Like suffix interfaces inline (when making calls), UNLESS archive patterns permit otherwise (such as SKY)
    • Use the DssExecLib Core Address Helpers where possible (e.g. DssExecLib.vat())
    • Where addresses are fetched from the ChainLog, the variable name must match the value of the ChainLog key for that address (e.g. MCD_VAT rather than vat)

Pre-Deployment Stage

  • Wait till the Exec Doc is merged
  • Exec Doc checks
    • Exec Doc for the specified date is found in the sky-ecosystem/executive-votes GitHub repo
    • Exec Doc is located in the directory matching the target spell date year (YYYY/)
    • Exec Doc file name follows the format executive-vote-YYYY-MM-DD-optional-description.md
    • Extract permanent URL to the raw markdown file and paste it below
      Insert your Raw Exec Doc URL here
      https://raw.githubusercontent.com/sky-ecosystem/executive-votes/9c58a42c41808d17531aa56eeaa9bbe1799fd0f5/2025/oos-executive-vote-2025-11-17-solana-bridge-migration.md
    • Ensure the URL uses commit hash that introduced last change to the Exec Doc, NOT merge commit
      • IF there is no local copy of sky-ecosystem/executive-votes GitHub repo, run:
        git clone https://github.com/sky-ecosystem/executive-votes
        
      • OTHERWISE, ensure it is pointing to the latest commit on main:
        git switch main && git pull origin main
        
      • Get the latest commit hash for the exec doc:
        git log --pretty=oneline -1 -- "<LOCAL_PATH_TO_EXEC_DOC>"
        
    • Using Exec Doc URL from the above and the TARGET_DATE, generate Exec Doc Hash via make exec-hash date=$TARGET_DATE $URL
      Insert your Exec Doc Hash here
      0x6e17f84690e8e325c78ff9fae8ead5fefcd9480d1d46358990e8baf4285ffda7
    • Using Exec Doc URL from the above, generate Exec Doc Hash via cast keccak -- "$(curl '$URL' -o - 2>/dev/null)"
      Insert your Exec Doc Hash here
      0x6e17f84690e8e325c78ff9fae8ead5fefcd9480d1d46358990e8baf4285ffda7
    • Make sure that hash above doesn't match keccak hash of the empty string (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470)
    • Using Exec Doc URL from the above, read spell instructions from the Exec Doc and list them below
      List all instructions announced in the Exec Doc
- The final stage of the Solana Bridge Migration will be executed.
  • Office hours value in the Exec Doc matches the spell
  • Sum of all payments in the Exec Doc matches the tests
  • Exec Doc URL in the spell comment matches your Raw Exec Doc URL above
  • Exec Doc URL in the spell comment refers to the https://github.com/sky-ecosystem/executive-votes repository
  • Every action present in the spell code is present in the Exec Doc
  • Every action in the Exec Doc is present in the spell code
  • IF new commits are present in the spell
    • Copy relevant checklist items from the above and redo them
    • Ensure newly added code is covered by tests
    • Check if chainlog needs to be updated
    • Copy over and redo "Tests" section from the above
  • Tests
    • Ensure that the DssExecLib.address file is not being modified by the spell PR
    • Check all CI tests are passing as at the latest commit
      Insert most recent commit hash where CI was passing
      e0c1a0c
    • Ensure every test function is declared as public
      • IF the test needs to run, it MUST NOT have the skipped modifier; OTHERWISE, it MUST have the skipped modifier
    • Ensure each spell action has sufficient test coverage
      List actions for which coverage was checked here
      • Solana Bridge Migration: testMigrationStep1, testLayerZeroContractsSanity
      • Call GovernanceRelayInit.init...: testGovernanceRelayInit
      • Update Chainlog: testChainlogValues and testChainlogIntegrity
    • Ensure that any other env variable does not affect execution of the tests (for example, by inspecting the output of printenv | grep "FOUNDRY_\|DAPP_")
    • IF a new module is initialized via the spell, the tests must include
      • Sanity checks of the constructor arguments
      • Sanity checks of all values added/updated by the spell function
      • End-to-end "happy path" interaction with the module
    • Check all tests are passing locally using make test
      • Ensure every test listed in the coverage item above is present in the logs and with the [PASS] prefix.
_Insert your local test logs here_

./scripts/test-dssspell-forge.sh no-match="" match="" block=""
Using DssExecLib at: 0x8De6DDbCd5053d32292AAA0D2105A32d108484a6
[⠊] Compiling...
No files changed, compilation skipped

Ran 2 tests for src/test/starknet.t.sol:StarknetTests
[PASS] testStarknet() (gas: 968934)
[PASS] testStarknetSpell() (gas: 2391)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 30.90s (8.40s CPU time)

Ran 49 tests for src/DssSpell.t.sol:DssSpellTest
[PASS] testAddedChainlogKeys() (gas: 838453)
[SKIP] testAllocatorIntegration() (gas: 0)
[SKIP] testBloomSpellIsExecuted() (gas: 0)
[SKIP] testBytecodeMatches() (gas: 0)
[PASS] testCastCost() (gas: 807346)
[PASS] testCastOnTime() (gas: 794982)
[PASS] testChainlogIntegrity() (gas: 6166582)
[PASS] testChainlogValues() (gas: 10980161)
[SKIP] testCollateralIntegrations() (gas: 0)
[PASS] testContractSize() (gas: 15810)
[SKIP] testDaoResolutions() (gas: 0)
[PASS] testDeployCost() (gas: 3686996)
[SKIP] testEsmAuth() (gas: 0)
[PASS] testGeneral() (gas: 17451182)
[PASS] testGovernanceRelayInit() (gas: 1555305)
[SKIP] testIlkClipper() (gas: 0)
[SKIP] testL2ArbitrumSpell() (gas: 0)
[SKIP] testL2OptimismSpell() (gas: 0)
[PASS] testLayerZeroContractsSanity() (gas: 76603)
[SKIP] testLerpSurplusBuffer() (gas: 0)
[PASS] testLitePSMs() (gas: 2075589)
[SKIP] testLockstakeIlkIntegration() (gas: 0)
[SKIP] testMedianReaders() (gas: 0)
[PASS] testMigrationStep1() (gas: 1560483)
[SKIP] testMonthlySettlementCycleInflows() (gas: 0)
[SKIP] testNewAuthorizations() (gas: 0)
[SKIP] testNewCronJobs() (gas: 0)
[PASS] testNextCastTime() (gas: 341748)
[SKIP] testNovaSpellIsExecuted() (gas: 0)
[SKIP] testObexSpellIsExecuted() (gas: 0)
[SKIP] testOffboardings() (gas: 0)
[PASS] testOfficeHours() (gas: 388643)
[SKIP] testOracleList() (gas: 0)
[SKIP] testOsmReaders() (gas: 0)
[PASS] testPSMs() (gas: 2231467)
[SKIP] testPayments() (gas: 0)
[SKIP] testRemovedChainlogKeys() (gas: 0)
[PASS] testRevertIfNotScheduled() (gas: 17530)
[PASS] testSPBEAMTauAndBudValues() (gas: 811862)
[SKIP] testSparkSpellIsExecuted() (gas: 0)
[PASS] testSplitter() (gas: 1353431)
[PASS] testSystemTokens() (gas: 1900384)
[PASS] testUseEta() (gas: 238316)
[SKIP] testVestDai() (gas: 0)
[SKIP] testVestMkr() (gas: 0)
[SKIP] testVestSky() (gas: 0)
[SKIP] testVestSkyMint() (gas: 0)
[SKIP] testVestSpk() (gas: 0)
[SKIP] testVestUsds() (gas: 0)
Suite result: ok. 20 passed; 0 failed; 29 skipped; finished in 157.32s (343.18s CPU time)

Ran 2 test suites in 158.02s (188.22s CPU time): 22 tests passed, 0 failed, 29 skipped (51 total tests)
  • IF all checks pass, make sure to include explicit "Good to deploy" comment

@SidestreamStrongStrawberry
Copy link
Collaborator Author

  • Foundry installation logs
foundryup: no attestation found for these binaries, skipping SHA verification for downloaded binaries
foundryup: use - forge 1.0.0-v1.0.0 (8692e92619 2025-02-10T09:05:50.693568910Z)
foundryup: use - cast 1.0.0-v1.0.0 (8692e92619 2025-02-10T09:05:50.693568910Z)
foundryup: use - anvil 1.0.0-v1.0.0 (8692e92619 2025-02-10T09:05:50.693568910Z)
foundryup: use - chisel 1.0.0-v1.0.0 (8692e92619 2025-02-10T09:05:50.693568910Z)

@SidestreamBurningBanana
Copy link
Contributor

Good to handover

Deployed Stage

  • Crafter's comment in the PR
    • Contains relevant Foundry installation logs
    • Contains a URL to the deployed spell
      • URL matches the spell address declared in config.sol
    • Contains a URL to the Tenderly Testnet
  • Source code settings
    • Deployed spell is verified on etherscan
    • Optimization enabled: false UNLESS the contract size is too big AND all mitigation strategies (i.e.: removing revert strings) have failed
    • Default evmVersion
    • GNU AGPLv3 license
  • Source code validity
    • Deployed spell code matches source on github. (can be checked via make diff-deployed-spell or manually)
    • No new changes are made after previously given "good to deploy"
      ℹ️ Only spell archiving and adding deployed info
  • Deployed spell Etherscan checks
    • Ensure local code is up-to-date with the remote branch (e.g. git pull)
    • Automated checks via make check-deployed-spell
      • Verified
      • Valid license
      • Version matches
      • Optimizations are disabled
      • dss-exec-lib library address used (under 'Libraries Used') matches the hardcoded local DssExecLib.address file
      • deployed_spell_created matches deployment timestamp
      • deployed_spell_block matches deployment block number
    • Manual checks
      • Ensure make deploy-info tx=<tx> matches config
        • deployed_spell_created timestamp
        • deployed_spell_block block number
      • Check again that the PR did not modify the DssExecLib.address file (e.g. look under the 'Files Changed' PR tab, etc.)
      • Ensure Etherscan Libraries Used matches DssExecLib Latest Release
      • (For your tests to be accurate) git submodule hash matches dss-exec-lib latest release's tag commit and inspect diffs if doesn't match to ensure expected behaviour (Currently Non-Critical pending the next DssExecLib release, double check that the ExecLib used by the contract matches the latest release)
        ⚠️ We are using the newer commit of DssExecLib, this checklist item is outdated
  • Tenderly Testnet checks
    • A testnet with the name matching spell description is found at maker dashboard
    • The testnet name is unique (previous testnets does not have the same name)
    • Cast transaction is set to the correct "receiver" (matches deployed spell address)
    • All actions are executed in the transaction trace
    • No reverts are present that block execution
    • No out-of-gas errors are present
  • Archive checks
    • make diff-archive-spell for current date or make diff-archive-spell date="YYYY-MM-DD"
    • Ensure date corresponds to target Exec Doc date
  • Tests
    • Ensure that the DssExecLib.address file is not being modified by the spell PR
    • Check all CI tests are passing as at the latest commit
      ℹ️ ac066b9
    • Ensure that any other env variable does not affect execution of the tests (for example, by inspecting the output of printenv | grep "FOUNDRY_\|DAPP_")
    • Check all tests are passing locally using make test
  • Publish an explicit "good to handover" comment
./scripts/test-dssspell-forge.sh no-match="" match="" block=""
Using DssExecLib at: 0x8De6DDbCd5053d32292AAA0D2105A32d108484a6
[⠊] Compiling...
[⠑] Compiling 4 files with Solc 0.8.16
[⠘] Solc 0.8.16 finished in 1.64s
Compiler run successful!

Ran 2 tests for src/test/starknet.t.sol:StarknetTests
[PASS] testStarknet() (gas: 968934)
[PASS] testStarknetSpell() (gas: 2391)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.89s (1.44s CPU time)

Ran 49 tests for src/DssSpell.t.sol:DssSpellTest
[PASS] testAddedChainlogKeys() (gas: 838453)
[SKIP] testAllocatorIntegration() (gas: 0)
[SKIP] testBloomSpellIsExecuted() (gas: 0)
[PASS] testBytecodeMatches() (gas: 3709402)
[PASS] testCastCost() (gas: 807346)
[PASS] testCastOnTime() (gas: 794982)
[PASS] testChainlogIntegrity() (gas: 6166582)
[PASS] testChainlogValues() (gas: 10980161)
[SKIP] testCollateralIntegrations() (gas: 0)
[SKIP] testContractSize() (gas: 0)
[SKIP] testDaoResolutions() (gas: 0)
[SKIP] testDeployCost() (gas: 0)
[SKIP] testEsmAuth() (gas: 0)
[PASS] testGeneral() (gas: 17453279)
[PASS] testGovernanceRelayInit() (gas: 1555305)
[SKIP] testIlkClipper() (gas: 0)
[SKIP] testL2ArbitrumSpell() (gas: 0)
[SKIP] testL2OptimismSpell() (gas: 0)
[PASS] testLayerZeroContractsSanity() (gas: 76603)
[SKIP] testLerpSurplusBuffer() (gas: 0)
[PASS] testLitePSMs() (gas: 2015341)
[SKIP] testLockstakeIlkIntegration() (gas: 0)
[SKIP] testMedianReaders() (gas: 0)
[PASS] testMigrationStep1() (gas: 1560483)
[SKIP] testMonthlySettlementCycleInflows() (gas: 0)
[SKIP] testNewAuthorizations() (gas: 0)
[SKIP] testNewCronJobs() (gas: 0)
[PASS] testNextCastTime() (gas: 341748)
[SKIP] testNovaSpellIsExecuted() (gas: 0)
[SKIP] testObexSpellIsExecuted() (gas: 0)
[SKIP] testOffboardings() (gas: 0)
[PASS] testOfficeHours() (gas: 388643)
[SKIP] testOracleList() (gas: 0)
[SKIP] testOsmReaders() (gas: 0)
[PASS] testPSMs() (gas: 2231467)
[SKIP] testPayments() (gas: 0)
[SKIP] testRemovedChainlogKeys() (gas: 0)
[PASS] testRevertIfNotScheduled() (gas: 17530)
[PASS] testSPBEAMTauAndBudValues() (gas: 811862)
[SKIP] testSparkSpellIsExecuted() (gas: 0)
[PASS] testSplitter() (gas: 1353431)
[PASS] testSystemTokens() (gas: 1900384)
[PASS] testUseEta() (gas: 238316)
[SKIP] testVestDai() (gas: 0)
[SKIP] testVestMkr() (gas: 0)
[SKIP] testVestSky() (gas: 0)
[SKIP] testVestSkyMint() (gas: 0)
[SKIP] testVestSpk() (gas: 0)
[SKIP] testVestUsds() (gas: 0)
Suite result: ok. 19 passed; 0 failed; 30 skipped; finished in 28.47s (91.82s CPU time)

Ran 2 test suites in 28.56s (34.36s CPU time): 21 tests passed, 0 failed, 30 skipped (51 total tests)

@oddaf
Copy link
Member

oddaf commented Nov 14, 2025

Good to handover:

Deployed Stage

  • Crafter's comment in the PR
    • Contains relevant Foundry installation logs
    • Contains a URL to the deployed spell
      • URL matches the spell address declared in config.sol
    • Contains a URL to the Tenderly Testnet
  • Source code settings
    • Deployed spell is verified on etherscan
    • Optimization enabled: false UNLESS the contract size is too big AND all mitigation strategies (i.e.: removing revert strings) have failed
    • Default evmVersion
    • GNU AGPLv3 license
  • Source code validity
    • Deployed spell code matches source on github. (can be checked via make diff-deployed-spell or manually)
    • No new changes are made after previously given "good to deploy"
  • Deployed spell Etherscan checks
    • Ensure local code is up-to-date with the remote branch (e.g. git pull)
    • Automated checks via make check-deployed-spell
      • Verified
      • Valid license
      • Version matches
      • Optimizations are disabled
      • dss-exec-lib library address used (under 'Libraries Used') matches the hardcoded local DssExecLib.address file
      • deployed_spell_created matches deployment timestamp
      • deployed_spell_block matches deployment block number
    • Manual checks
      • Ensure make deploy-info tx=<tx> matches config
        • deployed_spell_created timestamp
        • deployed_spell_block block number
      • Check again that the PR did not modify the DssExecLib.address file (e.g. look under the 'Files Changed' PR tab, etc.)
      • Ensure Etherscan Libraries Used matches DssExecLib Latest Release
      • (For your tests to be accurate) git submodule hash matches dss-exec-lib latest release's tag commit and inspect diffs if doesn't match to ensure expected behaviour (Currently Non-Critical pending the next DssExecLib release, double check that the ExecLib used by the contract matches the latest release)
  • Tenderly Testnet checks
    • A testnet with the name matching spell description is found at maker dashboard
    • The testnet name is unique (previous testnets does not have the same name)
    • Cast transaction is set to the correct "receiver" (matches deployed spell address)
    • All actions are executed in the transaction trace
    • No reverts are present that block execution
    • No out-of-gas errors are present
  • Archive checks
    • make diff-archive-spell for current date or make diff-archive-spell date="YYYY-MM-DD"
    • Ensure date corresponds to target Exec Doc date
  • Tests
    • Ensure that the DssExecLib.address file is not being modified by the spell PR
    • Check all CI tests are passing as at the latest commit
      Insert most recent commit hash where CI was passing
      ac066b9
    • Ensure that any other env variable does not affect execution of the tests (for example, by inspecting the output of printenv | grep "FOUNDRY_\|DAPP_")
    • Check all tests are passing locally using make test
  • Publish an explicit "good to handover" comment
_Insert your local test logs here_

./scripts/test-dssspell-forge.sh no-match="" match="" block=""
Using DssExecLib at: 0x8De6DDbCd5053d32292AAA0D2105A32d108484a6
[⠊] Compiling...
[⠑] Compiling 4 files with Solc 0.8.16
[⠃] Solc 0.8.16 finished in 5.79s
Compiler run successful!

Ran 2 tests for src/test/starknet.t.sol:StarknetTests
[PASS] testStarknet() (gas: 968934)
[PASS] testStarknetSpell() (gas: 2391)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 31.17s (8.25s CPU time)

Ran 49 tests for src/DssSpell.t.sol:DssSpellTest
[PASS] testAddedChainlogKeys() (gas: 838453)
[SKIP] testAllocatorIntegration() (gas: 0)
[SKIP] testBloomSpellIsExecuted() (gas: 0)
[PASS] testBytecodeMatches() (gas: 3709402)
[PASS] testCastCost() (gas: 807346)
[PASS] testCastOnTime() (gas: 794982)
[PASS] testChainlogIntegrity() (gas: 6166582)
[PASS] testChainlogValues() (gas: 10980161)
[SKIP] testCollateralIntegrations() (gas: 0)
[SKIP] testContractSize() (gas: 0)
[SKIP] testDaoResolutions() (gas: 0)
[SKIP] testDeployCost() (gas: 0)
[SKIP] testEsmAuth() (gas: 0)
[PASS] testGeneral() (gas: 17453279)
[PASS] testGovernanceRelayInit() (gas: 1555305)
[SKIP] testIlkClipper() (gas: 0)
[SKIP] testL2ArbitrumSpell() (gas: 0)
[SKIP] testL2OptimismSpell() (gas: 0)
[PASS] testLayerZeroContractsSanity() (gas: 76603)
[SKIP] testLerpSurplusBuffer() (gas: 0)
[PASS] testLitePSMs() (gas: 2015341)
[SKIP] testLockstakeIlkIntegration() (gas: 0)
[SKIP] testMedianReaders() (gas: 0)
[PASS] testMigrationStep1() (gas: 1560483)
[SKIP] testMonthlySettlementCycleInflows() (gas: 0)
[SKIP] testNewAuthorizations() (gas: 0)
[SKIP] testNewCronJobs() (gas: 0)
[PASS] testNextCastTime() (gas: 341748)
[SKIP] testNovaSpellIsExecuted() (gas: 0)
[SKIP] testObexSpellIsExecuted() (gas: 0)
[SKIP] testOffboardings() (gas: 0)
[PASS] testOfficeHours() (gas: 388643)
[SKIP] testOracleList() (gas: 0)
[SKIP] testOsmReaders() (gas: 0)
[PASS] testPSMs() (gas: 2231467)
[SKIP] testPayments() (gas: 0)
[SKIP] testRemovedChainlogKeys() (gas: 0)
[PASS] testRevertIfNotScheduled() (gas: 17530)
[PASS] testSPBEAMTauAndBudValues() (gas: 811862)
[SKIP] testSparkSpellIsExecuted() (gas: 0)
[PASS] testSplitter() (gas: 1353431)
[PASS] testSystemTokens() (gas: 1900194)
[PASS] testUseEta() (gas: 238316)
[SKIP] testVestDai() (gas: 0)
[SKIP] testVestMkr() (gas: 0)
[SKIP] testVestSky() (gas: 0)
[SKIP] testVestSkyMint() (gas: 0)
[SKIP] testVestSpk() (gas: 0)
[SKIP] testVestUsds() (gas: 0)
Suite result: ok. 19 passed; 0 failed; 30 skipped; finished in 152.76s (446.76s CPU time)

Ran 2 test suites in 153.50s (183.93s CPU time): 21 tests passed, 0 failed, 30 skipped (51 total tests)

Copy link
Contributor

@SidestreamBurningBanana SidestreamBurningBanana left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handover and Merge Stage

  • Check that the spell address posted by the crafter in new-spells is correct
  • Confirm the address in the new-spells channel (via a separate "reply to" message, restating the address to avoid edits)
    • Wait until responsible governance facilitator confirms handover in new-spells
  • Ensure that no changes were made to the code since the spell was deployed and archived
  • Approve spell PR for merge via 'Approve' review option

Copy link
Member

@oddaf oddaf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handover and Merge Stage

  • Check that the spell address posted by the crafter in new-spells is correct
  • Confirm the address in the new-spells channel (via a separate "reply to" message, restating the address to avoid edits)
    • Wait until responsible governance facilitator confirms handover in new-spells
  • Ensure that no changes were made to the code since the spell was deployed and archived
  • Approve spell PR for merge via 'Approve' review option

@SidestreamStrongStrawberry SidestreamStrongStrawberry merged commit 3f7bbab into master Nov 17, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants