Skip to content

Mainnet spell 2026-04-09#539

Merged
0xLaz3r merged 72 commits intomasterfrom
2026-04-09
Apr 10, 2026
Merged

Mainnet spell 2026-04-09#539
0xLaz3r merged 72 commits intomasterfrom
2026-04-09

Conversation

@0xLaz3r
Copy link
Copy Markdown
Contributor

@0xLaz3r 0xLaz3r commented Apr 3, 2026

Description

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

Comment thread src/DssSpell.t.sol Outdated
assertEq(keccak256(sendAndCallOptions), keccak256(expectedOptions), "TestError/usds-oft-send-and-call-enforced-options-mismatch");
}

function testGovernanceRelayAvalancheHappyPath() public {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is not yet end-to-end test. As it ends at calling L1 function, it doesn't verify that endpoint is correctly configured to relay to the correct destination chain and the destination to have matching configuration. So in my opinion, the test have to (at least) additionally 1) collect data emitted by the endpoint 2) switch to the destination chain, 3) relay the message emitted on mainnet 4) verify the effects. For the inspiration (or even a complete implementation), you can have look at the Spark's xchain-helpers test suite.

(The same end-to-end approach should be applied for testing OFT transfers to the destination and back)

Comment thread src/DssSpell.t.sol Outdated
Comment on lines +1601 to +1604
address ETH_LZ_ENDPOINT = 0x1a44076050125825900e736c501f859c50fE728c;
address ETH_LZ_SEND_302 = 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1;
bytes32 AVAX_GOV_RECEIVER = bytes32(uint256(uint160(0x6fdd46947ca6903c8c159d1dF2012Bc7fC5cEeec)));
bytes32 AVAX_L2_GOV_RELAY = bytes32(uint256(uint160(0xe928885BCe799Ed933651715608155F01abA23cA)));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please move those address to the addresses_mainnet.sol file

Comment thread src/DssSpell.t.sol

// SPELL-SPECIFIC TESTS GO BELOW

function testWireLzGovSenderAvalanche() public {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

FYI: you currently only test the contracts and endpoint configuration on L1, but have to do the same for L2 contracts and the endpoint

Comment thread src/test/config.sol
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please add a new file addresses_avalanche.sol and populate it with avalanche addresses (based on the existing pattern of prefixing them with L2_)

Comment thread src/DssSpell.sol Outdated
Comment on lines +100 to +101
// Note: Generated with OptionsBuilder.addExecutorLzReceiveOption(gas: 130_000, value: 0)
bytes internal constant ENFORCED_OPTIONS_DATA = hex"0003010011010000000000000000000000000001fbd0";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Q: why is this has to be hardcoded in this harder-to-review format instead of being encoded in place?

Comment thread src/DssSpell.sol
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

sUSDS-related actions are missing at the moment, as far as I can see

Comment thread src/test/addresses_avalanche.sol Outdated
Comment thread src/DssSpell.sol Outdated
Comment thread src/DssSpell.sol Outdated
Comment thread src/DssSpell.sol
Comment thread src/DssSpell.sol Outdated
Comment thread src/DssSpell.sol
Comment thread src/DssSpell.sol Outdated
Comment thread src/DssSpell.sol Outdated
Comment thread src/DssSpell.sol Outdated
Comment thread src/DssSpell.sol Outdated
Comment thread src/test/LZLayerZeroBridgeTesting.t.sol
Comment thread src/DssSpell.t.sol Outdated
Comment thread src/test/LZLayerZeroBridgeTesting.t.sol
Comment thread src/DssSpell.t.sol Outdated
Comment thread src/DssSpell.t.sol Outdated
Comment thread src/DssSpell.t.sol Outdated
Comment thread src/DssSpell.t.sol Outdated
Comment thread src/DssSpell.t.sol Outdated
Comment thread src/DssSpell.t.sol Outdated
Comment thread src/DssSpell.t.sol Outdated
Comment thread src/DssSpell.t.sol
Comment thread src/DssSpell.t.sol
Comment thread src/DssSpell.t.sol
Comment thread src/test/helpers/LZLaneTesting.sol
Comment thread src/test/helpers/LZLaneTesting.sol Outdated
Comment thread src/test/helpers/LZLaneTesting.sol Outdated
Comment thread src/test/addresses_avalanche.sol Outdated
Comment thread src/DssSpell.t.sol Outdated
Comment thread src/DssSpell.t.sol
Comment thread src/DssSpell.t.sol
Comment thread src/DssSpell.t.sol Outdated
Comment thread src/test/helpers/LZLaneTesting.sol
Comment thread src/test/addresses_avalanche.sol Outdated
Comment thread src/test/addresses_avalanche.sol Outdated
Copy link
Copy Markdown
Contributor

@amusingaxl amusingaxl left a comment

Choose a reason for hiding this comment

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

TL;DR: good to deploy :shipit:

Development Stage

  • Install stable Foundry version
    • Install the stable version of Foundry via foundryup --install stable
      Document the installation logs containing installed versions below:
      forge Version: 1.5.1-stable
      Commit SHA: b0a9dd9ceda36f63e2326ce530c10e6916f4b8a2
      Build Timestamp: 2025-12-22T11:39:01.425730780Z
      Build Profile: maxperf
      
  • 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)
  • make safeharbor-generate output matches the instructions on the Exec Sheet
    • IF there is a mismatch, notify Governance Facilitators
  • 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 spell intentionally compresses some adjacent instruction comments due to the size and complexity of the Avalanche SkyLink section; this is acceptable for the current spell and does not reduce reviewability.
    • 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)
    • SUSDS_OFT
      • Source code is verified on etherscan
      • Compilation optimizations match deployment settings defined in the source code repo
        • ✅ Etherscan metadata: OptimizationUsed = 1, Runs = 20000
        • sky-oapp-oft@5ad5cb6/foundry.toml: optimizer = true, optimizer_runs = 20_000
      • GNU AGPLv3 license
        • ⚠️ deployed source and pinned upstream source both declare MIT, not GNU AGPLv3
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        • token = 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD (SUSDS)
        • endpoint = 0x1a44076050125825900e736c501f859c50fE728c (LZ_ENDPOINT)
        • delegate = 0xBE8E3e3618f7474F8cB1d074A26afFef007E98FB (MCD_PAUSE_PROXY)
      • 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)
          • ℹ️ SUSDS_OFT uses owner-based access control, not wards. Ownership checking is done with LZLaneTesting.assertOwner().
        • 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
        ⚠️ this is coincidental because the same deployer was used in the past for other modules.
        • ✅ on-chain creator: 0x89aAB8CAeEf8d25051cA6E534C6944e51f15DAd2
        • ✅ present in src/test/addresses_deployers.sol
  • [Not part of the checklist scope yet] IF new contracts in other chains are present in the spell
    • ℹ️ these contracts are not in the mainnet chainlog because they live on Avalanche Mainnet, but analogous verification was still performed
    • L2_AVALANCHE_LZ_GOV_RECEIVER (GovernanceOAppReceiver)
      • Source code is verified on etherscan
      • Compilation optimizations match deployment settings defined in the source code repo
        • ✅ Routescan metadata: OptimizationUsed = 1, Runs = 20000
        • sky-oapp-oft@5ad5cb6/foundry.toml: optimizer = true, optimizer_runs = 20_000
      • GNU AGPLv3 license
        • ⚠️ pinned source file declares Apache-2.0, not GNU AGPLv3
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        • l1Eid = 30101
        • sender = 0x00000000000000000000000027fc1dd771817b53bE48Dc28789533BEa53C9CCA
        • endpoint = 0x1a44076050125825900e736c501f859c50fE728c
        • owner = 0x48c4dba0833748e576ad60e12a3c01c5785b09ab
      • 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)
          • GovernanceOAppReceiver is owner-based, not wards-based
        • 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
        • ℹ️ the mainnet deployer registry is not applicable to Avalanche Mainnet contracts
    • L2_AVALANCHE_LZ_GOV_RELAY (L2GovernanceRelay)
      • Source code is verified on etherscan
      • Compilation optimizations match deployment settings defined in the source code repo
        • ✅ Routescan metadata: OptimizationUsed = 1, Runs = 200
        • lz-governance-relay@d3e3df4/foundry.toml: optimizer = true, optimizer_runs = 200
      • GNU AGPLv3 license
        • ✅ pinned source file declares AGPL-3.0-or-later
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        • l1Eid = 30101
        • l2Oapp = 0x6fdd46947ca6903c8c159d1dF2012Bc7fC5cEeec
        • l1GovernanceRelay = 0x2beBFe397D497b66cB14461cB6ee467b4C3B7D61
      • 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)
          • L2GovernanceRelay is not wards-based
        • 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
        • ℹ️ the mainnet deployer registry is not applicable to Avalanche Mainnet contracts
    • L2_AVALANCHE_USDS_IMP (USDS implementation)
      • Source code is verified on etherscan
      • Compilation optimizations match deployment settings defined in the source code repo
        • ✅ Routescan metadata: OptimizationUsed = 1, Runs = 200
        • usds@45bf759/foundry.toml: optimizer = true, optimizer_runs = 200
      • GNU AGPLv3 license
        • ✅ pinned source file declares AGPL-3.0-or-later
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        • ℹ️ the implementation contract has no constructor arguments
      • IF new contract have concept of wards or access control
        • Ensure PAUSE_PROXY address was relied (wards(PAUSE_PROXY) is 1)
          • ℹ️ this is the implementation contract; it calls _disableInitializers() in the constructor, so live authorization is expected on the proxy, not on the implementation storage
        • Ensure that contract deployer address was denied (wards(deployer) is 0)
          • creator 0x48c4dba0833748e576ad60e12a3c01c5785b09ab
          • wards(creator) = 0
        • Ensure that there are no other Rely events except for PAUSE_PROXY (using a block explorer like etherscan)
          • ✅ no Rely(address) events were found for the implementation address
      • 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
        • ℹ️ the mainnet deployer registry is not applicable to Avalanche Mainnet contracts
    • L2_AVALANCHE_USDS (USDS proxy)
      • Source code is verified on etherscan
      • Compilation optimizations match deployment settings defined in the source code repo
        • ✅ Routescan metadata: OptimizationUsed = 1, Runs = 200
        • ✅ verified source bundle settings also show optimizer.enabled = true, runs = 200
      • GNU AGPLv3 license
        • ⚠️ the verified ERC1967Proxy.sol source declares MIT, not GNU AGPLv3
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        • implementation = 0xB5bc5dFe65a9ec30738DB3a0b592B8a18A191300
        • data = 0x8129fc1c
      • IF new contract have concept of wards or access control
        • Ensure PAUSE_PROXY address was relied (wards(PAUSE_PROXY) is 1)
          • ℹ️ Avalanche proxy authorization is held by L2_AVALANCHE_LZ_GOV_RELAY and L2_AVALANCHE_USDS_OFT, not PAUSE_PROXY
          • wards(L2_AVALANCHE_LZ_GOV_RELAY) = 1 and wards(L2_AVALANCHE_USDS_OFT) = 1
        • Ensure that contract deployer address was denied (wards(deployer) is 0)
          • creator 0x48c4dba0833748e576ad60e12a3c01c5785b09ab
          • wards(creator) = 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
        • ℹ️ the mainnet deployer registry is not applicable to Avalanche Mainnet contracts
    • L2_AVALANCHE_USDS_OFT (SkyOFTAdapterMintBurn(USDS))
      • Source code is verified on etherscan
      • Compilation optimizations match deployment settings defined in the source code repo
        • ✅ Routescan metadata: OptimizationUsed = 1, Runs = 20000
        • sky-oapp-oft@5ad5cb6/foundry.toml: optimizer = true, optimizer_runs = 20_000
      • GNU AGPLv3 license
        • ⚠️ pinned source file declares MIT, not GNU AGPLv3
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        • token = 0x86Ff09db814ac346a7C6FE2Cd648F27706D1D470
        • endpoint = 0x1a44076050125825900e736c501f859c50fE728c
        • delegate = 0x48c4dba0833748e576ad60e12a3c01c5785b09ab
      • 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)
          • SkyOFTAdapterMintBurn(USDS) is owner-based, not wards-based
        • 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
        • ℹ️ the mainnet deployer registry is not applicable to Avalanche Mainnet contracts
    • L2_AVALANCHE_SUSDS_IMP (sUSDS implementation)
      • Source code is verified on etherscan
      • Compilation optimizations match deployment settings defined in the source code repo
        • ✅ Routescan metadata: OptimizationUsed = 1, Runs = 200
        • sdai@e1d160a/foundry.toml: optimizer = true, optimizer_runs = 200
      • GNU AGPLv3 license
        • ✅ pinned source file declares AGPL-3.0-or-later
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        • ℹ️ the implementation contract has no constructor arguments
      • IF new contract have concept of wards or access control
        • Ensure PAUSE_PROXY address was relied (wards(PAUSE_PROXY) is 1)
          • ℹ️ this is the implementation contract; it calls _disableInitializers() in the constructor, so live authorization is expected on the proxy, not on the implementation storage
        • Ensure that contract deployer address was denied (wards(deployer) is 0)
          • creator 0x48c4dba0833748e576ad60e12a3c01c5785b09ab
          • wards(creator) = 0
        • Ensure that there are no other Rely events except for PAUSE_PROXY (using a block explorer like etherscan)
          • ✅ no Rely(address) events were found for the implementation address
      • 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
        • ℹ️ the mainnet deployer registry is not applicable to Avalanche Mainnet contracts
    • L2_AVALANCHE_SUSDS (sUSDS proxy)
      • Source code is verified on etherscan
      • Compilation optimizations match deployment settings defined in the source code repo
        • ✅ Routescan metadata: OptimizationUsed = 1, Runs = 200
        • ✅ verified source bundle settings also show optimizer.enabled = true, runs = 200
      • GNU AGPLv3 license
        • ⚠️ the verified ERC1967Proxy.sol source declares MIT, not GNU AGPLv3
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        • implementation = 0xc8dB83458e8593Ed9a2D81DC29068B12D330729a
        • data = 0x8129fc1c
      • IF new contract have concept of wards or access control
        • Ensure PAUSE_PROXY address was relied (wards(PAUSE_PROXY) is 1)
          • ℹ️ Avalanche proxy authorization is held by L2_AVALANCHE_LZ_GOV_RELAY and L2_AVALANCHE_SUSDS_OFT, not PAUSE_PROXY
          • wards(L2_AVALANCHE_LZ_GOV_RELAY) = 1 and wards(L2_AVALANCHE_SUSDS_OFT) = 1
        • Ensure that contract deployer address was denied (wards(deployer) is 0)
          • creator 0x48c4dba0833748e576ad60e12a3c01c5785b09ab
          • wards(creator) = 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
        • ℹ️ the mainnet deployer registry is not applicable to Avalanche Mainnet contracts
    • L2_AVALANCHE_SUSDS_OFT (SkyOFTAdapterMintBurn(sUSDS))
      • Source code is verified on etherscan
      • Compilation optimizations match deployment settings defined in the source code repo
        • ✅ Routescan metadata: OptimizationUsed = 1, Runs = 20000
        • sky-oapp-oft@5ad5cb6/foundry.toml: optimizer = true, optimizer_runs = 20_000
      • GNU AGPLv3 license
        • ⚠️ pinned source file declares MIT, not GNU AGPLv3
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        • token = 0xb94D9613C7aAB11E548a327154Cc80eCa911B5c1
        • endpoint = 0x1a44076050125825900e736c501f859c50fE728c
        • delegate = 0x48c4dba0833748e576ad60e12a3c01c5785b09ab
      • 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)
          • SkyOFTAdapterMintBurn(sUSDS) is owner-based, not wards-based
          • owner() = 0xe928885BCe799Ed933651715608155F01abA23cA
        • 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
        • ℹ️ the mainnet deployer registry is not applicable to Avalanche Mainnet contracts
  • 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
      • ℹ️ ChainSecurity report references endgame-toolkit@4f238f9 for the updateFarmVest review batch
      • Each contract/library exactly matches (i.e. diff check) the source code of the latest audited version
        • ✅ exact SHA-256 matches verified for:
        • src/dependencies/endgame-toolkit/StakingRewardsInit.sol <-> script/dependencies/StakingRewardsInit.sol
        • src/dependencies/endgame-toolkit/VestInit.sol <-> script/dependencies/VestInit.sol
        • src/dependencies/endgame-toolkit/VestedRewardsDistributionInit.sol <-> script/dependencies/VestedRewardsDistributionInit.sol
        • src/dependencies/endgame-toolkit/treasury-funded-farms/TreasuryFundedFarmingInit.sol <-> script/dependencies/treasury-funded-farms/TreasuryFundedFarmingInit.sol
    • 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
        • GROVE_SUBPROXY resolves to 0x1369f7b2b38c76B6478c0f0E66D94923421891Ba in src/test/addresses_mainnet.sol
      • Recipient address matches Exec Sheet
        • ✅ Exec Sheet instruction is Transfer 20,797,477 USDS to the GROVE_SUBPROXY
      • Recipient address variable name matches one found in addresses_wallets.sol
        • ✅ repository lookup is in src/test/addresses_mainnet.sol, where GROVE_SUBPROXY is defined with the same address
      • Transfer amount matches Exec Sheet
        • ✅ spell calls _transferUsds(GROVE_SUBPROXY, 20_797_477 * WAD)
      • The transfers are tested via testPayments test
        • testPayments() includes Payee(address(usds), addr.addr("GROVE_SUBPROXY"), 20_797_477 ether)
      • Sum of all USDS transfers tested in testPayments matches number in the Exec Sheet
        • expectedTotalPayments.usds = 20_797_477 ether, matching the Exec Sheet checksum row
    • IF DAI / SKY / USDS / SPK streams (DssVest) are created
      • VestAbstract interface is imported from dss-interfaces/dss/VestAbstract.sol
        • ℹ️ the spell updates the vest through audited helper TreasuryFundedFarmingInit.updateFarmVest(), which calls VestInit.create() internally
      • restrict is used for each stream, UNLESS otherwise explicitly stated in the Exec Sheet
        • VestInit.create() calls DssVestLike(vest).restrict(vestId)
      • usr (Vest recipient address) matches Exec Sheet
        • ✅ Exec Sheet dist is 0x675671A8756dDb69F7254AFB030865388Ef699Ee, matching REWARDS_DIST_LSSKY_SKY
      • usr address in the instruction is in the checksummed format
        • REWARDS_DIST_LSSKY_SKY resolves to 0x675671A8756dDb69F7254AFB030865388Ef699Ee in src/test/addresses_mainnet.sol
      • usr address variable name match one found in addresses_wallets.sol
        • ✅ repository lookup is in src/test/addresses_mainnet.sol, where REWARDS_DIST_LSSKY_SKY is defined with the same address
      • tot (Total stream amount) matches Exec Sheet
        • ✅ spell sets vestTot: 192_110_322 * WAD
      • 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
        • ✅ spell sets vestBgn: block.timestamp
      • 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)
        • ✅ spell sets vestTau: 90 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)
        • ✅ helper calls VestInit.create(... eta: 0)
      • IF mgr (Vest manager address) is specified in the Exec Sheet, matches the value, OTHERWISE matches address(0)
        • VestInit.create() passes address(0) as mgr
      • 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)
        • 110 * vestTot / (100 * vestTau) = 27176100077160497152, which is below afterSpell.vest_sky_cap = 70730452674897125376
      • 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)
          • ✅ helper adjusts allowance by currAllowance + p.vestTot - (prevVestTot - prevVestRxd), and _checkVest() verifies the post-spell allowance delta
        • 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
        • ✅ for this spell, the applicable coverage is testVestSky
    • IF DAI / SKY / USDS / SPK vest termination (Yank) is present
      • Yanked stream ID matches Exec Sheet
        • ℹ️ the Exec Sheet specifies the vest update parameters, but does not name the superseded vest stream id
      • MCD_VEST_SKY_TREASURY chainlog address is used for SKY stream yank
        • ℹ️ the yank happens inside TreasuryFundedFarmingInit.updateFarmVest() through dist.dssVest(), rather than by directly fetching the chainlog key in spell code
      • 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
        • ✅ for this spell, the applicable coverage is testVestSky
    • IF SKY / SPK vest rewards distribution is present
      • Rewards distribution contract address matches Exec Sheet
        • ✅ Exec Sheet dist is 0x675671A8756dDb69F7254AFB030865388Ef699Ee, matching REWARDS_DIST_LSSKY_SKY
      • To prevent front-running DoS, the distribute() call is placed inside if block that checks whether the vesting stream’s unpaid amount is greater than 0
        • TreasuryFundedFarmingInit.updateFarmVest() guards both dist.distribute() calls with if (unpaid > 0)
      • Tested via testVestedRewardsDist
  • IF content related to a Prime Agent is present
    • IF Prime Agent spell is provided
      • Handover message matches XXX spell YYY-MM-DD deployed to 0x… with hash 0x…, direct execution: yes / no template
        • ℹ️ confirmed on Discord
      • IF direct execution is no
        • The Prime Agent spell is plotted using StarGuardLike(XXX_STARGUARD).plot(XXX_SPELL, XXX_SPELL_HASH)
        • XXX in XXX_STARGUARD matches the name of the Prime Agent
        • XXX_STARGUARD is fetched from chainlog
        • The test ensures the XXX_SPELL Prime Agent spell is executable via StarGuardLike(XXX_STARGUARD).exec() before XXX_STARGUARD.maxDelay
          • testPrimeAgentSpellExecutions() covers both SPARK_STARGUARD and GROVE_STARGUARD through _testStarGuardExecution(...)
        • IF plotted but not yet executed spell is still present in the XXX_STARGUARD, Governance Facilitators are aware or already notified
          • ✅ live spellData() is empty for both SPARK_STARGUARD and GROVE_STARGUARD (address(0), bytes32(0), 0), so there is no preexisting plotted spell
      • IF direct execution is yes
        • Provided mandatory explanation of why direct execution is required makes sense on the technical level
        • The hash is checked via require(XXX_SPELL.codehash == XXX_SPELL_HASH, "XXX_SPELL/wrong-codehash"); inside the Core spell
        • The Prime Agent spell is executed via ProxyLike(XXX_PROXY).exec(XXX_SPELL, abi.encodeWithSignature("execute()"));
        • XXX in XXX_PROXY matches the name of the Prime Agent
        • XXX_PROXY is fetched from chainlog
      • Prime Agent spell address (XXX_SPELL) matches Exec Sheet
        • ✅ Spark 0xFa5fc020311fCC1A467FEC5886640c7dD746deAa and Grove 0x679eD4739c71300f7d78102AE5eE17EF8b8b2162 match the Exec Sheet and Exec Doc
      • Prime Agent spell hash (XXX_SPELL_HASH) matches Exec Sheet
        • ✅ Spark 0x2572a97846f7a6f9f159a9a69c2707cfa4186c061de2a0ec59e7a0d46473c74c and Grove 0x4fa1f743b3d6d2855390724459129186dd684e1c07d59f88925f0059ba1e6c84 match the Exec Sheet and Exec Doc
  • IF external contracts calls are present (Not Prime Agents, 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 bug bounty registry updates are present
    • Run make safeharbor-generate
      • Verify that the generated code exactly matches the code in the spell
      • Verify that output matches the instructions provided by Governance Facilitators
      • Ensure that the script does not output any warnings, which are indicated by ⚠️
        • ✅ generator completed successfully and reproduced the two calldatas[...] payloads plus _updateSafeHarbor(calldatas);
    • Ensure that agreement address is fetched from the Chainlog
      • SAFE_HARBOR_AGREEMENT is fetched via DssExecLib.getChangelogAddress("SAFE_HARBOR_AGREEMENT")
    • Ensure that the helper function to perform the call is present and is implemented using the established archive pattern
      • _updateSafeHarbor(bytes[] memory calldatas) iterates the generated payloads and requires each call to succeed
  • 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.++)
      • ✅ the spell adds SUSDS_OFT and bumps the ChainLog version to 1.20.15, which is a patch-level update
    • New addresses are added to the addresses_mainnet.sol
      • SUSDS_OFT and SUSDS_OFT_PAUSER are present in src/test/addresses_mainnet.sol
    • Changes are tested via testChainlogIntegrity, testChainlogValues, testAddedChainlogKeys and testRemovedChainlogKeys
      • ⚠️ testRemovedChainlogKeys is intentionally skipped because this spell does not remove ChainLog keys
  • 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())
      • DAI uses DssExecLib.dai() and the remaining dynamic addresses use DssExecLib.getChangelogAddress(...)
    • 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 inside foundry.toml is not being modified by the spell PR
      • ✅ no diff is present in foundry.toml
    • Check all CI tests are passing as at the latest commit
      174a65ee7f15ea630ac0af0125c762062b5fbae7
    • 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
        • ✅ active spell-specific tests such as testVestSky, testPayments, testPrimeAgentSpellExecutions, testUpdateSafeHarborAddedAccounts, testSafeHarborAvalancheOnboarding, and the Avalanche LayerZero suite are not skipped
    • Ensure each spell action has sufficient test coverage
      • ✅ Launch Avalanche SkyLink: testWireLzGovSenderAvalanche, testWireUsdsOftAvalanche, testWireSUsdsOftAvalanche, testUsdsOftAvalancheRateLimits, testOftPauseUnpause, testGovernanceRelayAvalancheE2E, testUsdsOftAvalancheE2E, testSUsdsOftAvalancheRateLimitBlocked
      • ✅ Staking Rewards Update: testVestSky, testVestedRewardsDist
      • ✅ Grove Genesis Capital Transfer: testPayments
      • ✅ Safe Harbor Update: testUpdateSafeHarborAddedAccounts, testSafeHarborAvalancheOnboarding
      • ✅ Prime Agent Proxy Spells: testPrimeAgentSpellExecutions
      • ✅ ChainLog update: testChainlogIntegrity, testChainlogValues, testAddedChainlogKeys
    • Ensure that any other env variable does not affect execution of the tests (for example, by inspecting the output of printenv | grep "FOUNDRY_\|DAPP_")
      • printenv | grep "FOUNDRY_\\|DAPP_" returned no matches
    • 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.
./scripts/test-dssspell-forge.sh no-match="" match="" block=""
No files changed, compilation skipped

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

Ran 60 tests for src/DssSpell.t.sol:DssSpellTest
[PASS] testAddedChainlogKeys() (gas: 3081613)
[SKIP] testAllocatorIntegration() (gas: 0)
[SKIP] testArbitrumGovRelay() (gas: 0)
[SKIP] testBaseGovRelay() (gas: 0)
[SKIP] testBytecodeMatches() (gas: 0)
[PASS] testCastCost() (gas: 3070311)
[PASS] testCastOnTime() (gas: 3065994)
[PASS] testChainlogIntegrity() (gas: 8452389)
[PASS] testChainlogValues() (gas: 13901299)
[SKIP] testCollateralIntegrations() (gas: 0)
[PASS] testContractSize() (gas: 15854)
[SKIP] testDaoResolutions() (gas: 0)
[PASS] testDeployCost() (gas: 5441968)
[SKIP] testEsmAuth() (gas: 0)
[PASS] testGeneral() (gas: 22287046)
[PASS] testGovernanceRelayAvalancheE2E() (gas: 4383721)
[SKIP] testIlkClipper() (gas: 0)
[SKIP] testL2ArbitrumSpell() (gas: 0)
[SKIP] testL2OptimismSpell() (gas: 0)
[SKIP] testLerpSurplusBuffer() (gas: 0)
[PASS] testLitePSMs() (gas: 4322390)
[SKIP] testLockstakeIlkIntegration() (gas: 0)
[SKIP] testMedianReaders() (gas: 0)
[SKIP] testMonthlySettlementCycleInflows() (gas: 0)
[SKIP] testNewAuthorizations() (gas: 0)
[SKIP] testNewCronJobs() (gas: 0)
[SKIP] testNewLineMomIlks() (gas: 0)
[PASS] testNextCastTime() (gas: 392563)
[SKIP] testOffboardings() (gas: 0)
[PASS] testOfficeHours() (gas: 439373)
[PASS] testOftPauseUnpause() (gas: 3183857)
[SKIP] testOptimismGovRelay() (gas: 0)
[SKIP] testOracleList() (gas: 0)
[SKIP] testOsmReaders() (gas: 0)
[PASS] testPSMs() (gas: 4409724)
[PASS] testPayments() (gas: 3183325)
[PASS] testPrimeAgentSpellExecutions() (gas: 10008891)
[SKIP] testRemovedChainlogKeys() (gas: 0)
[PASS] testRevertIfNotScheduled() (gas: 17530)
[PASS] testSPBEAMTauAndBudValues() (gas: 3082963)
[PASS] testSUsdsOftAvalancheRateLimitBlocked() (gas: 3315092)
[PASS] testSafeHarborAvalancheOnboarding() (gas: 7091005)
[PASS] testSplitter() (gas: 3588732)
[SKIP] testStarGuardInitialization() (gas: 0)
[PASS] testSystemTokens() (gas: 4060790)
[SKIP] testUnichainGovRelay() (gas: 0)
[PASS] testUpdateSafeHarborAddedAccounts() (gas: 7979889)
[PASS] testUsdsOftAvalancheE2E() (gas: 4792103)
[PASS] testUsdsOftAvalancheRateLimits() (gas: 3094675)
[PASS] testUseEta() (gas: 289131)
[SKIP] testVestDai() (gas: 0)
[SKIP] testVestMkr() (gas: 0)
[PASS] testVestSky() (gas: 3757829)
[SKIP] testVestSkyMint() (gas: 0)
[SKIP] testVestSpk() (gas: 0)
[SKIP] testVestUsds() (gas: 0)
[PASS] testVestedRewardsDist() (gas: 6136250)
[PASS] testWireLzGovSenderAvalanche() (gas: 3294287)
[PASS] testWireSUsdsOftAvalanche() (gas: 3441502)
[PASS] testWireUsdsOftAvalanche() (gas: 3413594)
Suite result: ok. 31 passed; 0 failed; 29 skipped; finished in 182.58s (1508.35s CPU time)

Ran 2 test suites in 182.83s (220.50s CPU time): 33 tests passed, 0 failed, 29 skipped (62 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/)
      • ✅ path prefix is 2026/, matching target date 2026-04-09
    • Exec Doc file name follows the format executive-vote-YYYY-MM-DD-optional-description.md
      • executive-vote-2026-04-09-launch-avalanche-skylink-staking-rewards-update.md
    • Extract permanent URL to the raw markdown file and paste it below
      https://raw.githubusercontent.com/sky-ecosystem/executive-votes/656d5a6d9e8041203d98823248e64d87771681d3/2026/executive-vote-2026-04-09-launch-avalanche-skylink-staking-rewards-update.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>"
        
        • ✅ GitHub commit history for the file shows latest content-changing commit 656d5a6d9e8041203d98823248e64d87771681d3
    • Using Exec Doc URL from the above and the TARGET_DATE, generate Exec Doc Hash via make exec-hash date=$TARGET_DATE $URL
      0x0f87466f280de3544ae715fb2463152d7959c37902926f2069aaaccf10cef550
      • make exec-hash date=2026-04-09 returned the same hash and the same raw GitHub URL
    • Using Exec Doc URL from the above, generate Exec Doc Hash via cast keccak -- "$(curl '$URL' -o - 2>/dev/null)"
      0x0f87466f280de3544ae715fb2463152d7959c37902926f2069aaaccf10cef550
    • 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
      • Launch Avalanche SkyLink
      • LSSKY->SKY Staking Rewards Update
      • Grove Genesis Capital Transfer
      • Safe Harbor Update
      • Spark Proxy Spell
      • Grove Proxy Spell
    • Office hours value in the Exec Doc matches the spell
      • ✅ Exec Doc says the spell can only be executed between 14:00 and 21:00 UTC, Monday - Friday; officeHours() returns true
    • Sum of all payments in the Exec Doc matches the tests
      • ✅ Exec Doc announces 20,797,477 USDS to GROVE_SUBPROXY; testPayments checks expectedTotalPayments.usds = 20_797_477 ether
    • Exec Doc URL in the spell comment matches your Raw Exec Doc URL above
      • src/DssSpell.sol:84 uses the same raw URL with commit 656d5a6d9e8041203d98823248e64d87771681d3
    • 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

@SidestreamStrongStrawberry
Copy link
Copy Markdown
Collaborator

Good to deploy:

Development Stage

  • Install stable Foundry version
    • Install the stable version of Foundry via foundryup --install stable
      foundryup: installing foundry (version stable, tag stable)
      foundryup: checking if forge, cast, anvil, and chisel for stable version are already installed
      ######################################################################################################################################################################################################################################################### 100.0%
      foundryup: found attestation for stable version, downloading attestation artifact, checking...
      ######################################################################################################################################################################################################################################################### 100.0%
      foundryup: version stable already installed and verified, activating...
      foundryup: use - forge 1.5.1-stable (b0a9dd9ced 2025-12-22T11:41:09.812070000Z)
      foundryup: use - cast 1.5.1-stable (b0a9dd9ced 2025-12-22T11:41:09.812070000Z)
      foundryup: use - anvil 1.5.1-stable (b0a9dd9ced 2025-12-22T11:41:09.812070000Z)
      foundryup: use - chisel 1.5.1-stable (b0a9dd9ced 2025-12-22T11:41:09.812070000Z)
      
  • Preparation
    • Exec Sheet for the specified date is found in the "Executive Vote Implementation Process" google sheet
      Insert URL to the specific sheet here
      https://docs.google.com/spreadsheets/d/1w_z5WpqxzwreCcaveB2Ye1PP5B8QAHDglzyxKHG3CHw/edit?gid=320756284#gid=320756284
    • Using Exec Sheet URL from the above, read spell instructions from the Exec Sheet and list them below
      List all instructions announced in the Exec Sheet
      • Launch Avalanche SkyLink

        • Wire LZ_GOV_SENDER on Ethereum Mainnet with Avalanche Mainnet
          • Set GovernanceOAppReceiver as a peer on Avalanche by calling LZ_GOV_SENDER.setPeer with:
            • LZ_GOV_SENDER being the address from chainlog
            • uint32 _eid being 30106
            • bytes32 _peer being 0x6fdd46947ca6903c8c159d1dF2012Bc7fC5cEeec padded with zeros
          • Set Oapp SendLibrary for Avalanche by calling EndpointV2.setSendLibrary with:
          • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being LZ_GOV_SENDER from chainlog
            • uint32 _eid being 30106
            • address _newLib being 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1
          • Configure Oapp SendLibrary for Avalanche by calling EndpointV2.setConfig with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being LZ_GOV_SENDER from chainlog
            • address _lib being 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1
          • SetConfigParam[] _params being an array with two items:
            • First item: Executor parameters
              • uint32 eid being 30106
              • uint32 configType being 1
              • bytes config being encoded ExecutorConfig with:
                • maxMessageSize being 10_000
                • executor being 0x173272739Bd7Aa6e4e214714048a9fE699453059
            • Second item: ULN parameters
              • uint32 eid being 30106
              • uint32 configType being 2
              • bytes config being encoded UlnConfig with:
                • uint64 confirmations being 15
                • uint8 requiredDVNCount being 255 (meaning NONE)
                • uint8 optionalDVNCount being 7
                • uint8 optionalDVNThreshold being 4
                • address[] requiredDVNs being an array with 0 addresses
                • address[] optionalDVNs being an array with 7 addresses: [0x589dEDbD617e0CBcB916A9223F4d1300c294236b (LayerZero Labs), 0xa59BA433ac34D2927232918Ef5B2eaAfcF130BA5 (Nethermind), 0xa4fE5A5B9A846458a70Cd0748228aED3bF65c2cd (Canary),0x373a6E5c0C4E89E24819f00AA37ea370917AAfF4 (Deutsche Telekom), 0x06559EE34D85a88317Bf0bfE307444116c631b67 (P2P), 0x380275805876Ff19055EA900CDb2B46a94ecF20D (Horizen), 0x58249a2Ec05c1978bF21DF1f5eC1847e42455CF4 (Luganodes)]
        • Allow LZ_GOV_SENDER to send messages to Avalanche
          • Call LZ_GOV_SENDER.setCanCallTarget with:
          • LZ_GOV_SENDER being the address from chainlog
          • address _srcSender being LZ_GOV_RELAY from chainlog
          • uint32 _dstEid being 30106
          • bytes32 _dstTarget being 0xe928885BCe799Ed933651715608155F01abA23cA padded with zeros
          • bool _canCall being true
        • Wire USDS_OFT on Ethereum Mainnet with Avalanche Mainnet
          • Set SkyOFTAdapterMintBurn(USDS) as a peer on Avalanche by calling USDS_OFT.setPeer with:
            • USDS_OFT being the address from chainlog
            • uint32 eid being 30106
            • bytes32 _peer being 0x4fec40719fD9a8AE3F8E20531669DEC5962D2619 padded with zeros
          • Set OFT SendLibrary for Avalanche by calling EndpointV2.setSendLibrary with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being USDS_OFT from chainlog
            • uint32 _eid being 30106
            • address _newLib being 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1
          • Set OFT ReceiveLibrary for Avalanche by calling EndpointV2.setReceiveLibrary with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being USDS_OFT from chainlog
            • uint32 _eid being 30106
            • address _newLib being 0xc02Ab410f0734EFa3F14628780e6e695156024C2
            • _gracePeriod being 0
          • Configure OFT SendLibrary for Avalanche by calling EndpointV2.setConfig with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being USDS_OFT from chainlog
            • address _lib being 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1
            • SetConfigParam[] _params being an array with two items:
            • First item: Executor parameters
              • uint32 eid being 30106
              • uint32 configType being 1
              • bytes config being encoded ExecutorConfig with:
                • maxMessageSize being 10_000
                • executor being 0x173272739Bd7Aa6e4e214714048a9fE699453059
            • Second item: ULN parameters
              • uint32 eid being 30106
              • uint32 configType being 2
              • bytes config being encoded UlnConfig with:
                • uint64 confirmations being 15
                • uint8 requiredDVNCount being 2
                • uint8 optionalDVNCount being 0
                • uint8 optionalDVNThreshold being 0
                • address[] requiredDVNs being an array with 2 addresses: [0x589dEDbD617e0CBcB916A9223F4d1300c294236b (LayerZero Labs), 0xa59BA433ac34D2927232918Ef5B2eaAfcF130BA5 (Nethermind)]
                • address[] optionalDVNs being an array with 0 addresses
          • Configure OFT ReceiveLibrary for Avalanche by calling EndpointV2.setConfig with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being USDS_OFT from chainlog
            • address _lib being 0xc02Ab410f0734EFa3F14628780e6e695156024C2
            • SetConfigParam[] _params being an array with one item:
              • uint32 eid being 30106
              • uint32 configType being 2
              • bytes config being encoded UlnConfig with:
              • uint64 confirmations being 12 (default configuration from source)
              • uint8 requiredDVNCount being 2
              • uint8 optionalDVNCount being 0
              • uint8 optionalDVNThreshold being 0
              • address[] requiredDVNs being an array with 2 addresses: [0x589dEDbD617e0CBcB916A9223F4d1300c294236b (LayerZero Labs), 0xa59BA433ac34D2927232918Ef5B2eaAfcF130BA5 (Nethermind)]
              • address[] optionalDVNs being an array with 0 addresses
          • Set OFT enforced options for Avalanche by calling USDS_OFT.setEnforcedOptions with:
            • USDS_OFT being the address from chainlog
            • EnforcedOptionParam[] _enforcedOptions being an array with 2 items:
              • SendOption (generated with OptionsBuilder.addExecutorLzReceiveOption)
                • uint32 eid being 30106
                • uint16 msgType being 1 (Meaning SEND)
                • bytes options being encoded:
                  • uint128 _gas being 130_000
                  • uint128 _value being 0
              • SendAndCallOption (generated with OptionsBuilder.addExecutorLzReceiveOption)
                • uint32 eid being 30106
                • uint16 msgType being 2 (Meaning SEND_AND_CALL)
                • bytes options being encoded:
                  • uint128 _gas being 130_000
                  • uint128 _value being 0
        • Set USDS rate limits for Avalanche
          • USDS_OFT being the address from chainlog
          • RateLimitConfig[] _rateLimitConfigsInbound being an array with one item:
          • uint32 eid being 30106
          • uint48 window being 86,400s (1 day)
          • uint256 limit being 5,000,000 USDS
          • RateLimitConfig[] _rateLimitConfigsOutbound being an array with one item:
          • uint32 eid being 30106
          • uint48 window being 86,400s (1 day)
          • uint256 limit being 5 million USDS
        • Add 0x85A3FE4DA2a6cB98A5bdF62458B0dB8471B9f0f1 to chainlog as SUSDS_OFT
        • Wire SUSDS_OFT on Ethereum Mainnet with Avalanche Mainnet
          • Set pauser by calling SUSDS_OFT.setPauser with:
            • address _pauser being 0x38d1114b4cE3e079CC0f627df6aC2776B5887776
            • bool _canPause being true
          • Set SkyOFTAdapterMintBurn(sUSDS) as a peer on Avalanche by calling SUSDS_OFT.setPeer with:
            • SUSDS_OFT being the address from chainlog
            • uint32 eid being 30106
            • bytes32 _peer being SkyOFTAdapterMintBurn(sUSDS)
          • Set OFT SendLibrary for Avalanche by calling EndpointV2.setSendLibrary with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being SUSDS_OFT from chainlog
            • uint32 _eid being 30106
            • address _newLib being 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1
          • Set OFT ReceiveLibrary for Avalanche by calling EndpointV2.setReceiveLibrary with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being SUSDS_OFT from chainlog
            • uint32 _eid being 30106
            • address _newLib being 0xc02Ab410f0734EFa3F14628780e6e695156024C2
            • _gracePeriod being 0
          • Configure OFT SendLibrary for Avalanche by calling EndpointV2.setConfig with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being SUSDS_OFT from chainlog
            • address _lib being 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1
            • SetConfigParam[] _params being an array with two items:
            • First item: Executor parameters
              • uint32 eid being 30106
              • uint32 configType being 1
              • bytes config being encoded ExecutorConfig with:
                • maxMessageSize being 10_000
                • executor being 0x173272739Bd7Aa6e4e214714048a9fE699453059
            • Second item: ULN parameters
              • uint32 eid being 30106
              • uint32 configType being 2
              • bytes config being encoded UlnConfig with:
                • uint64 confirmations being 15
                • uint8 requiredDVNCount being 2
                • uint8 optionalDVNCount being 0
                • uint8 optionalDVNThreshold being 0
              • address[] requiredDVNs being an array with 2 addresses: [0x589dEDbD617e0CBcB916A9223F4d1300c294236b, 0xa59BA433ac34D2927232918Ef5B2eaAfcF130BA5]
              • address[] optionalDVNs being an array with 0 addresses
          • Configure OFT ReceiveLibrary for Avalanche by calling EndpointV2.setConfig with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being SUSDS_OFT from chainlog
            • address _lib being 0xc02Ab410f0734EFa3F14628780e6e695156024C2
            • SetConfigParam[] _params being an array with one item:
              • uint32 eid being 30106
              • uint32 configType being 2
              • bytes config being encoded UlnConfig with:
                • uint64 confirmations being 12
                • uint8 requiredDVNCount being 2
                • uint8 optionalDVNCount being 0
                • uint8 optionalDVNThreshold being 0
                • address[] requiredDVNs being an array with 2 addresses: [0x589dEDbD617e0CBcB916A9223F4d1300c294236b, 0xa59BA433ac34D2927232918Ef5B2eaAfcF130BA5]
                • address[] optionalDVNs being an array with 0 addresses
        • Set OFT enforced options for Avalanche by calling SUSDS_OFT.setEnforcedOptions with:
          • SUSDS_OFT being the address from chainlog
          • EnforcedOptionParam[] _enforcedOptions being an array with 2 items:
            • SendOption
              • uint32 eid being 30106
              • uint16 msgType being 1 (Meaning SEND)
              • bytes options being encoded:
                • uint128 _gas being 130_000
                • uint128 _value being 0
            • SendAndCallOption
              • uint32 eid being 30106
              • uint16 msgType being 2 (Meaning SEND_AND_CALL)
              • bytes options being encoded:
                • uint128 _gas being 130_000
                • uint128 _value being 0
      • Staking Rewards Update

        • Update LSSKY->SKY Farm vest by calling TreasuryFundedFarmingInit.updateFarmVest() with params:
        • dist: 0x675671A8756dDb69F7254AFB030865388Ef699Ee
        • vestTot: 192,110,322 SKY
        • vestBgn: block.timestamp
        • vestTau: 90 days
      • Grove Genesis Capital Transfer

        • Transfer 20,797,477 USDS to the GROVE_SUBPROXY
      • Safe Harbor Update
        "// ---------- Bug Bounty Updates ----------
        bytes[] memory calldatas = new bytes;

        // Add new eip155:43114 with recovery address 0xe928885BCe799Ed933651715608155F01abA23cA and accounts: 0x6fdd46947ca6903c8c159d1dF2012Bc7fC5cEeec, 0xe928885BCe799Ed933651715608155F01abA23cA, 0xB5bc5dFe65a9ec30738DB3a0b592B8a18A191300, 0x86Ff09db814ac346a7C6FE2Cd648F27706D1D470, 0x4fec40719fD9a8AE3F8E20531669DEC5962D2619, 0xc8dB83458e8593Ed9a2D81DC29068B12D330729a, 0xb94D9613C7aAB11E548a327154Cc80eCa911B5c1, 0x7297D4811f088FC26bC5475681405B99b41E1FF9
        calldatas[0] = hex'be4a94ba000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000006e0000000000000000000000000000000000000000000000000000000000000002a307865393238383835424365373939456439333336353137313536303831353546303161624132336341000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000000000000000000000000000000000000000056000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078366664643436393437636136393033633863313539643164463230313242633766433563456565630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078653932383838354243653739394564393333363531373135363038313535463031616241323363410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078423562633564466536356139656333303733384442336130623539324238613138413139313330300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078383646663039646238313461633334366137433646453243643634384632373730364431443437300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078346665633430373139664439613841453346384532303533313636394445433539363244323631390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078633864423833343538653835393345643961324438314443323930363842313244333330373239610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078623934443936313343376141423131453534386133323731353443633830654361393131423563310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a30783732393744343831316630383846433236624335343735363831343035423939623431453146463900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c6569703135353a34333131340000000000000000000000000000000000000000';

        // Add accounts to eip155:1 chain: 0x85A3FE4DA2a6cB98A5bdF62458B0dB8471B9f0f1
        calldatas[1] = hex'46c2b7340000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000086569703135353a310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a30783835413346453444413261366342393841356264463632343538423064423834373142396630663100000000000000000000000000000000000000000000';

        _updateSafeHarbor(calldatas);"

      • Spark Proxy Spell

        • Whitelist Spark spell with address 0xFa5fc020311fCC1A467FEC5886640c7dD746deAa and codehash 0x2572a97846f7a6f9f159a9a69c2707cfa4186c061de2a0ec59e7a0d46473c74c in SPARK_STARGUARD, direct execution: No
      • Grove Proxy Spell

        • Whitelist Grove spell with address 0x679eD4739c71300f7d78102AE5eE17EF8b8b2162 and codehash 0x4fa1f743b3d6d2855390724459129186dd684e1c07d59f88925f0059ba1e6c84 in GROVE_STARGUARD, direct execution: No
  • 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)
  • make safeharbor-generate output matches the instructions on the Exec Sheet
    • IF there is a mismatch, notify Governance Facilitators
  • 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 is not kept in some places to improve 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
      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)
    • SUSDS_OFT on Ethereum - 0x85A3FE4DA2a6cB98A5bdF62458B0dB8471B9f0f1
      • Source code is verified on etherscan
      • Compilation optimizations match deployment settings defined in the source code repo
        ℹ️ deployment settings source code
      • GNU AGPLv3 license
        ⚠️ MIT license is used, but this is expected as the license is defined in the audited contract code. MIT license was used for previously deployed contract such as USDS proxy
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
      • IF new contract have concept of wards or access control
        ℹ️ The new contract has owner and delegate, both are set to PAUSE_PROXY
        • 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
    • GOV_RECEIVER on Avalanche - 0x6fdd46947ca6903c8c159d1dF2012Bc7fC5cEeec
      • Source code is verified on etherscan
        ℹ️ source code is verified on snowscan which is a block explorer for avalanche
      • Compilation optimizations match deployment settings defined in the source code repo
        ℹ️ deployment settings source code
      • GNU AGPLv3 license
        ⚠️ Apache license is used, but this is expected as the license is defined in the audited contract code. Apache license was used for previously deployed contract such as LZ_GOV_SENDER
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        • _governanceOAppSenderAddress: 0x00000000000000000000000027fc1dd771817b53be48dc28789533bea53c9cca (0 padded LZ_GOV_SENDER)
      • IF new contract have concept of wards or access control
        ℹ️ The new contract has owner and delegate, both are set to L2_GOV_RELAY on Avalanche
        • 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
        ℹ️ Test doesn't check deployer ownership in other domains.
    • L2_GOV_RELAY on Avalanche - 0xe928885BCe799Ed933651715608155F01abA23cA
      • Source code is verified on etherscan
        ℹ️ source code is verified on snowscan which is a block explorer for avalanche
      • Compilation optimizations match deployment settings defined in the source code repo
        ℹ️ deployment settings source code
      • GNU AGPLv3 license
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        • _l2Oapp: matches GOV_RECEIVER on Avalanche
        • _l1GovernanceRelay: matches LZ_GOV_RELAY in chainlog
      • 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
        ℹ️ Current test with addresses_deployers.sol only checks Ethereum mainnet
    • USDS_OFT on Avalanche - 0x4fec40719fD9a8AE3F8E20531669DEC5962D2619
      • Source code is verified on etherscan
        ℹ️ source code is verified on snowscan which is a block explorer for avalanche
      • Compilation optimizations match deployment settings defined in the source code repo
        ℹ️ deployment settings source code
      • GNU AGPLv3 license
        ⚠️ MIT license is used, but this is expected as the license is defined in the audited contract code. MIT license was used for previously deployed contract such as USDS proxy
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        • _token: USDS on Avalanche
      • IF new contract have concept of wards or access control
        ℹ️ The new contract has owner and delegate, both are set to L2_GOV_RELAY on Avalanche
        • 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
        ℹ️ Current test with addresses_deployers.sol only checks Ethereum mainnet
    • SUSDS Implementation on Avalanche - 0xc8dB83458e8593Ed9a2D81DC29068B12D330729a
      • Source code is verified on etherscan
        ℹ️ source code is verified on snowscan which is a block explorer for avalanche
      • Compilation optimizations match deployment settings defined in the source code repo
        ℹ️ deployment settings source code
      • 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
        ℹ️ Current test with addresses_deployers.sol only checks Ethereum mainnet
    • SUSDS Proxy on Avalanche - 0xb94D9613C7aAB11E548a327154Cc80eCa911B5c1
      • Source code is verified on etherscan
        ℹ️ source code is verified on snowscan which is a block explorer for avalanche
      • Compilation optimizations match deployment settings defined in the source code repo
        ℹ️ deployment settings source code
      • GNU AGPLv3 license
        ⚠️ MIT license is used, but this is expected as the contract is developed by openzeppelin. MIT license was used for previously deployed contract such as USDS proxy
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
        • implementation: SUSDS Implementation on Avalanche
      • IF new contract have concept of wards or access control
        • Ensure PAUSE_PROXY address was relied (wards(PAUSE_PROXY) is 1)
          ⚠️ As there is no PAUSE_PROXY on Avalanche, L2_GOV_RELAY is used as auth address (relays message from Ethereum governance)
        • 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)
          ⚠️ As there is no PAUSE_PROXY on Avalanche, L2_GOV_RELAY is used as auth address (relays message from Ethereum governance) and SUSDS_OFT on Avalanche relied to mint and burn token while token is bridged
      • 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
        ℹ️ Current test with addresses_deployers.sol only checks Ethereum mainnet
    • SUSDS_OFT on Avalanche - 0x7297D4811f088FC26bC5475681405B99b41E1FF9
      • Source code is verified on etherscan
        ℹ️ source code is verified on snowscan which is a block explorer for avalanche
      • Compilation optimizations match deployment settings defined in the source code repo
        ℹ️ deployment settings source code
      • GNU AGPLv3 license
        ⚠️ MIT license is used, but this is expected as the license is defined in the audited contract code. MIT license was used for previously deployed contract such as USDS proxy
      • Every protocol-related constructor argument matches chainlog (e.g. vat, dai, dog, ...)
      • IF new contract have concept of wards or access control
        ℹ️ The new contract has owner and delegate, both are set to L2_GOV_RELAY on Avalanche
        • 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
        ℹ️ Current test with addresses_deployers.sol only checks Ethereum mainnet
  • 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
      • ✅ src/dependencies/endgame-toolkit/treasury-funded-farms/TreasuryFundedFarmingInit.sol (source)
      • ✅ src/dependencies/endgame-toolkit/StakingRewardsInit.sol (source)
      • ✅ src/dependencies/endgame-toolkit/VestedRewardsDistributionInit.sol (source)
      • ✅ src/dependencies/endgame-toolkit/VestInit.sol (source)
    • 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
        ⚠️ Chainlog key was given instead of recipient address
      • Recipient address matches Exec Sheet
        ⚠️ Recipient address is fetched from chainlog and the used chainlog key 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
        ⚠️ Executive sheet doesn't specify the stream id to yank, but the old vestId in the VestedRewardsDistribution was yanked as expected
      • 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 SKY / SPK vest rewards distribution is present
      • Rewards distribution contract address matches Exec Sheet
      • To prevent front-running DoS, the distribute() call is placed inside if block that checks whether the vesting stream’s unpaid amount is greater than 0
      • Tested via testVestedRewardsDist
  • IF content related to a Prime Agent is present
    • IF Prime Agent spell is provided
      • Spark
        • Handover message matches XXX spell YYYY-MM-DD deployed to 0x… with hash 0x…, direct execution: yes / no template
        • IF direct execution is no
          • The Prime Agent spell is plotted using StarGuardLike(XXX_STARGUARD).plot(XXX_SPELL, XXX_SPELL_HASH)
          • XXX in XXX_STARGUARD matches the name of the Prime Agent
          • XXX_STARGUARD is fetched from chainlog
          • The test ensures the XXX_SPELL Prime Agent spell is executable via StarGuardLike(XXX_STARGUARD).exec() before XXX_STARGUARD.maxDelay
          • IF plotted but not yet executed spell is still present in the XXX_STARGUARD, Governance Facilitators are aware or already notified
        • IF direct execution is yes
          • Provided mandatory explanation of why direct execution is required makes sense on the technical level
          • The hash is checked via require(XXX_SPELL.codehash == XXX_SPELL_HASH, "XXX_SPELL/wrong-codehash"); inside the Core spell
          • The Prime Agent spell is executed via ProxyLike(XXX_PROXY).exec(XXX_SPELL, abi.encodeWithSignature("execute()"));
          • XXX in XXX_PROXY matches the name of the Prime Agent
          • XXX_PROXY is fetched from chainlog
        • Prime Agent spell address (XXX_SPELL) matches Exec Sheet
        • Prime Agent spell hash (XXX_SPELL_HASH) matches Exec Sheet
      • Grove
        • Handover message matches XXX spell YYYY-MM-DD deployed to 0x… with hash 0x…, direct execution: yes / no template
        • IF direct execution is no
          • The Prime Agent spell is plotted using StarGuardLike(XXX_STARGUARD).plot(XXX_SPELL, XXX_SPELL_HASH)
          • XXX in XXX_STARGUARD matches the name of the Prime Agent
          • XXX_STARGUARD is fetched from chainlog
          • The test ensures the XXX_SPELL Prime Agent spell is executable via StarGuardLike(XXX_STARGUARD).exec() before XXX_STARGUARD.maxDelay
          • IF plotted but not yet executed spell is still present in the XXX_STARGUARD, Governance Facilitators are aware or already notified
        • IF direct execution is yes
          • Provided mandatory explanation of why direct execution is required makes sense on the technical level
          • The hash is checked via require(XXX_SPELL.codehash == XXX_SPELL_HASH, "XXX_SPELL/wrong-codehash"); inside the Core spell
          • The Prime Agent spell is executed via ProxyLike(XXX_PROXY).exec(XXX_SPELL, abi.encodeWithSignature("execute()"));
          • XXX in XXX_PROXY matches the name of the Prime Agent
          • XXX_PROXY is fetched from chainlog
        • Prime Agent spell address (XXX_SPELL) matches Exec Sheet
        • Prime Agent spell hash (XXX_SPELL_HASH) matches Exec Sheet
  • IF external contracts calls are present (Not Prime Agents, 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 bug bounty registry updates are present
    • Run make safeharbor-generate
      • Verify that the generated code exactly matches the code in the spell
      • Verify that output matches the instructions provided by Governance Facilitators
      • Ensure that the script does not output any warnings, which are indicated by ⚠️
    • Ensure that agreement address is fetched from the Chainlog
    • Ensure that the helper function to perform the call is present and is implemented using the established archive pattern
  • 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)
  • Tests
    • Ensure that the DssExecLib address inside foundry.toml 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
      174a65e
    • 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
      ⚠️ Recovery address on the new chain(avalanche) for safe harbor was not tested, but the address was double checked manually. End-to-end tests for recovery are not present, but L2GovernanceRelay is tested from testGovernanceRelayAvalancheE2E.
      List actions for which coverage was checked here
      • Launch Avalanche SkyLink
        • testWireLzGovSenderAvalanche: check wiring configuration of LzGovSender (on Ethereum) and LzGovReceiver (on Avalanche)
        • testWireUsdsOftAvalanche: check wiring configuration of UsdsOFTAdatper on Ethereum and Avalanche
        • testWireSUsdsOftAvalanche: check wiring configuration of sUsdsOFTAdatper on Ethereum and Avalanche
        • testUsdsOftAvalancheRateLimits: check usdsOFT rate limit on ethereum
          • rate limits for usdsOFT rate limit on Avalanche was checked before spell execution and also confirmed by Cantina
        • testOftPauseUnpause: check if pauser can pause and owner can unpause for usds oft adapter and susds oft adapter on ethereum and avalanche
        • testGovernanceRelayAvalancheE2E: simulate bridging l2 spell to address rate limits from ethereum to avalanche and check updated state on avalanche
        • testUsdsOftAvalancheE2E: simulate bridging usds from ethereum to avalanche and back
        • testSUsdsOftAvalancheRateLimitBlocked: check bridge susds is blocked with rate limit as expected
        • testChainlogIntegrity, testChainlogValues and testAddedChainlogKeys: check chainlog key (newly added key: SUSDS_OFT)
          Apart from the tests above, an extra test was checked to ensure sUSDS bridge (Etherum <> Avalanche) is possible after updating the rateLimits
      • Staking Rewards Update
        • testVestSky: check new vest stream and yanked vest stream
        • testVestedRewardsDist: check the previous vest was paid and new vestId is updated to VestRewardsDistribution contract.
      • Grove Genesis Capital Transfer
        • testPayments: test individual payment and token totalSupply amount update
      • Safe Harbor Update
        • testUpdateSafeHarborAddedAccounts: newly added address on ethereum
        • testSafeHarborAvalancheOnboarding: check new chain (avalanche) is added with correct accounts (newly onboarded lz contracts)
          Apart from the tests above, assetRecoveryAddress for avalanche chain was checked to ensure it is correctly set to L2_AVALANCHE_LZ_GOV_RELAY
      • Spark Proxy Spell
        • testPrimeAgentSpellExecutions: test prime agent spell plot and execution
      • Grove Proxy Spell
        • testPrimeAgentSpellExecutions: test prime agent spell plot and execution
    • 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
        ⚠️ OApp contracts deployed on Avalanche (L2_AVALANCHE_GOV_RECEIVER, L2_AVALANCHE_USDS_OFT, and L2_AVALANCHE_SUSDS_OFT) had deployer as _delegate for manual wiring and updated it to L2_AVALANCHE_GOV_RELAY. This was checked as correct delegate and owners are set at this point.
      • 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=""
[⠊] Compiling...
No files changed, compilation skipped

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

Ran 60 tests for src/DssSpell.t.sol:DssSpellTest
[PASS] testAddedChainlogKeys() (gas: 3081399)
[SKIP] testAllocatorIntegration() (gas: 0)
[SKIP] testArbitrumGovRelay() (gas: 0)
[SKIP] testBaseGovRelay() (gas: 0)
[SKIP] testBytecodeMatches() (gas: 0)
[PASS] testCastCost() (gas: 3070097)
[PASS] testCastOnTime() (gas: 3065780)
[PASS] testChainlogIntegrity() (gas: 8452175)
[PASS] testChainlogValues() (gas: 13901085)
[SKIP] testCollateralIntegrations() (gas: 0)
[PASS] testContractSize() (gas: 15854)
[SKIP] testDaoResolutions() (gas: 0)
[PASS] testDeployCost() (gas: 5441968)
[SKIP] testEsmAuth() (gas: 0)
[PASS] testGeneral() (gas: 22286832)
[PASS] testGovernanceRelayAvalancheE2E() (gas: 4383507)
[SKIP] testIlkClipper() (gas: 0)
[SKIP] testL2ArbitrumSpell() (gas: 0)
[SKIP] testL2OptimismSpell() (gas: 0)
[SKIP] testLerpSurplusBuffer() (gas: 0)
[PASS] testLitePSMs() (gas: 4279228)
[SKIP] testLockstakeIlkIntegration() (gas: 0)
[SKIP] testMedianReaders() (gas: 0)
[SKIP] testMonthlySettlementCycleInflows() (gas: 0)
[SKIP] testNewAuthorizations() (gas: 0)
[SKIP] testNewCronJobs() (gas: 0)
[SKIP] testNewLineMomIlks() (gas: 0)
[PASS] testNextCastTime() (gas: 392563)
[SKIP] testOffboardings() (gas: 0)
[PASS] testOfficeHours() (gas: 438064)
[PASS] testOftPauseUnpause() (gas: 3183643)
[SKIP] testOptimismGovRelay() (gas: 0)
[SKIP] testOracleList() (gas: 0)
[SKIP] testOsmReaders() (gas: 0)
[PASS] testPSMs() (gas: 4409510)
[PASS] testPayments() (gas: 3183810)
[PASS] testPrimeAgentSpellExecutions() (gas: 10008452)
[SKIP] testRemovedChainlogKeys() (gas: 0)
[PASS] testRevertIfNotScheduled() (gas: 17530)
[PASS] testSPBEAMTauAndBudValues() (gas: 3082749)
[PASS] testSUsdsOftAvalancheRateLimitBlocked() (gas: 3314878)
[PASS] testSafeHarborAvalancheOnboarding() (gas: 7090791)
[PASS] testSplitter() (gas: 3588518)
[SKIP] testStarGuardInitialization() (gas: 0)
[PASS] testSystemTokens() (gas: 4061166)
[SKIP] testUnichainGovRelay() (gas: 0)
[PASS] testUpdateSafeHarborAddedAccounts() (gas: 7979675)
[PASS] testUsdsOftAvalancheE2E() (gas: 4791889)
[PASS] testUsdsOftAvalancheRateLimits() (gas: 3094461)
[PASS] testUseEta() (gas: 289131)
[SKIP] testVestDai() (gas: 0)
[SKIP] testVestMkr() (gas: 0)
[PASS] testVestSky() (gas: 3757719)
[SKIP] testVestSkyMint() (gas: 0)
[SKIP] testVestSpk() (gas: 0)
[SKIP] testVestUsds() (gas: 0)
[PASS] testVestedRewardsDist() (gas: 6135822)
[PASS] testWireLzGovSenderAvalanche() (gas: 3294073)
[PASS] testWireSUsdsOftAvalanche() (gas: 3441288)
[PASS] testWireUsdsOftAvalanche() (gas: 3413380)
Suite result: ok. 31 passed; 0 failed; 29 skipped; finished in 47.24s (267.91s CPU time)

Ran 2 test suites in 47.46s (57.52s CPU time): 33 tests passed, 0 failed, 29 skipped (62 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
    • 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/656d5a6d9e8041203d98823248e64d87771681d3/2026/executive-vote-2026-04-09-launch-avalanche-skylink-staking-rewards-update.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>"
        
        656d5a6d9e8041203d98823248e64d87771681d3
    • 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
      https://raw.githubusercontent.com/sky-ecosystem/executive-votes/656d5a6d9e8041203d98823248e64d87771681d3/2026/executive-vote-2026-04-09-launch-avalanche-skylink-staking-rewards-update.md
    • 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
      0x0f87466f280de3544ae715fb2463152d7959c37902926f2069aaaccf10cef550
    • 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
      • Wire LZ_GOV_SENDER on Ethereum Mainnet with Avalanche Mainnet
        • Set GovernanceOAppReceiver as a peer on Avalanche by calling LZ_GOV_SENDER.setPeer with:
          • LZ_GOV_SENDER being the address from chainlog
          • uint32 _eid being 30106
          • bytes32 _peer being the address of the new GovernanceOAppReceiver at 0x6fdd46947ca6903c8c159d1dF2012Bc7fC5cEeec padded with zeros
        • Set Oapp SendLibrary for Avalanche by calling EndpointV2.setSendLibrary with:
          • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
          • address _oapp being LZ_GOV_SENDER from chainlog
          • uint32 _eid being 30106
          • address _newLib being 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1
        • Configure Oapp SendLibrary for Avalanche by calling EndpointV2.setConfig with:
          • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
          • address _oapp being LZ_GOV_SENDER from chainlog
          • address _lib being 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1
          • SetConfigParam[] _params being an array with two items:
            • First item: Executor parameters
              • uint32 eid being 30106
              • uint32 configType being 1
              • bytes config being encoded ExecutorConfig with:
                • maxMessageSize being 10_000
                • executor being 0x173272739Bd7Aa6e4e214714048a9fE699453059
            • Second item: ULN parameters
              • uint32 eid being 30106
              • uint32 configType being 2
              • bytes config being encoded UlnConfig with:
                • uint64 confirmations being 15
                • uint8 requiredDVNCount being 255 (meaning NONE)
                • uint8 optionalDVNCount being 7
                • uint8 optionalDVNThreshold being 4
                • address[] requiredDVNs being an array with 0 addresses
                • address[] optionalDVNs being an array with 7 addresses: [0x589dEDbD617e0CBcB916A9223F4d1300c294236b (LayerZero Labs), 0xa59BA433ac34D2927232918Ef5B2eaAfcF130BA5 (Nethermind), 0xa4fE5A5B9A846458a70Cd0748228aED3bF65c2cd (Canary), 0x373a6E5c0C4E89E24819f00AA37ea370917AAfF4 (Deutsche Telekom), 0x06559EE34D85a88317Bf0bfE307444116c631b67 (P2P), 0x380275805876Ff19055EA900CDb2B46a94ecF20D (Horizen), 0x58249a2Ec05c1978bF21DF1f5eC1847e42455CF4 (Luganodes)]
        • Allow LZ_GOV_SENDER to send messages to Avalanche
          • Call LZ_GOV_SENDER.setCanCallTarget with:
            • LZ_GOV_SENDER being the address from chainlog
            • address _srcSender being LZ_GOV_RELAY from chainlog
            • uint32 _dstEid being 30106
            • bytes32 _dstTarget being 0xe928885BCe799Ed933651715608155F01abA23cA padded with zeros
            • bool _canCall being true
        • Wire USDS_OFT on Ethereum Mainnet with Avalanche Mainnet
          • Set SkyOFTAdapterMintBurn(USDS) as a peer on Avalanche by calling USDS_OFT.setPeer with:
            • USDS_OFT being the address from chainlog
            • uint32 eid being 30106
            • bytes32 _peer being 0x4fec40719fD9a8AE3F8E20531669DEC5962D2619 padded with zeros
          • Set OFT SendLibrary for Avalanche by calling EndpointV2.setSendLibrary with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being USDS_OFT from chainlog
            • uint32 _eid being 30106
            • address _newLib being 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1
          • Set OFT ReceiveLibrary for Avalanche by calling EndpointV2.setReceiveLibrary with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being USDS_OFT from chainlog
            • uint32 _eid being 30106
            • address _newLib being 0xc02Ab410f0734EFa3F14628780e6e695156024C2
            • _gracePeriod being 0
          • Configure OFT SendLibrary for Avalanche by calling EndpointV2.setConfig with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being USDS_OFT from chainlog
            • address _lib being 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1
            • SetConfigParam[] _params being an array with two items:
              • First item: Executor parameters
                • uint32 _eid being 30106
                • uint32 configType being 1
                • bytes config being encoded ExecutorConfig with:
                  • maxMessageSize being 10_000
                  • executor being 0x173272739Bd7Aa6e4e214714048a9fE699453059
              • Second item: ULN parameters
                • uint32 _eid being 30106
                • uint32 configType being 2
                • bytes config being encoded UlnConfig with:
                  • uint64 confirmations being 15
                  • uint8 requiredDVNCount being 2
                  • uint8 optionalDVNCount being 0
                  • uint8 optionalDVNThreshold being 0
                  • address[] requiredDVNs being an array with 2 addresses: [0x589dEDbD617e0CBcB916A9223F4d1300c294236b (LayerZero Labs), 0xa59BA433ac34D2927232918Ef5B2eaAfcF130BA5 (Nethermind)]
                  • address[] optionalDVNs being an array with 0 addresses
          • Configure OFT ReceiveLibrary for Avalanche by calling EndpointV2.setConfig with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being USDS_OFT from chainlog
            • address _lib being 0xc02Ab410f0734EFa3F14628780e6e695156024C2
            • SetConfigParam[] _params being an array with one item:
              • uint32 _eid being 30106
              • uint32 configType being 2
              • bytes config being encoded UlnConfig with:
                • uint64 confirmations being 12
                • uint8 requiredDVNCount being 2
                • uint8 optionalDVNCount being 0
                • uint8 optionalDVNThreshold being 0
                • address[] requiredDVNs being an array with 2 addresses: [0x589dEDbD617e0CBcB916A9223F4d1300c294236b (LayerZero Labs), 0xa59BA433ac34D2927232918Ef5B2eaAfcF130BA5 (Nethermind)]
                • address[] optionalDVNs being an array with 0 addresses
          • Set OFT enforced options for Avalanche by calling USDS_OFT.setEnforcedOptions with:
            • USDS_OFT being the address from chainlog
            • EnforcedOptionParam[] _enforcedOptions being an array with 2 items:
              • SendOption
                • uint32 eid being 30106
                • uint16 msgType being 1 (Meaning SEND)
                • bytes options being encoded:
                  • uint128 _gas being 130_000
                  • uint128 _value being 0
              • SendAndCallOption
                • uint32 eid being 30106
                • uint16 msgType being 2 (Meaning SEND_AND_CALL)
                • bytes options being encoded:
                  • uint128 _gas being 130_000
                  • uint128 _value being 0
        • Set USDS rate limits for Avalanche
          • USDS_OFT being the address from chainlog
          • RateLimitConfig[] _rateLimitConfigsInbound being an array with one item:
            • uint32 eid being 30106
            • uint48 window being 86,400s (1 day)
            • uint256 limit being 5,000,000 USDS
          • RateLimitConfig[] _rateLimitConfigsOutbound being an array with one item:
            • uint32 eid being 30106
            • uint48 window being 86,400s (1 day)
            • uint256 limit being 5,000,000 USDS
        • Add 0x85A3FE4DA2a6cB98A5bdF62458B0dB8471B9f0f1 to chainlog as SUSDS_OFT
        • Wire SUSDS_OFT on Ethereum Mainnet with Avalanche Mainnet
          • Set pauser by calling SUSDS_OFT.setPauser with:
            • address _pauser being 0x38d1114b4cE3e079CC0f627df6aC2776B5887776
            • bool _canPause being true
          • Set SkyOFTAdapterMintBurn(sUSDS) as a peer on Avalanche by calling SUSDS_OFT.setPeer with:
            • SUSDS_OFT being the address from chainlog
            • uint32 eid being 30106
            • bytes32 _peer being SkyOFTAdapterMintBurn(sUSDS) on Avalanche at 0x7297D4811f088FC26bC5475681405B99b41E1FF9 padded with zeros
          • Set OFT SendLibrary for Avalanche by calling EndpointV2.setSendLibrary with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being SUSDS_OFT from chainlog
            • uint32 _eid being 30106
            • address _newLib being 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1
          • Set OFT ReceiveLibrary for Avalanche by calling EndpointV2.setReceiveLibrary with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being SUSDS_OFT from chainlog
            • uint32 _eid being 30106
            • address _newLib being 0xc02Ab410f0734EFa3F14628780e6e695156024C2
            • _gracePeriod being 0
          • Configure OFT SendLibrary for Avalanche by calling EndpointV2.setConfig with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being SUSDS_OFT from chainlog
            • address _lib being 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1
            • SetConfigParam[] _params being an array with two items:
              • First item: Executor parameters
                • uint32 _eid being 30106
                • uint32 configType being 1
                • bytes config being encoded ExecutorConfig with:
                  • maxMessageSize being 10_000
                  • executor being 0x173272739Bd7Aa6e4e214714048a9fE699453059
              • Second item: ULN parameters
                • uint32 _eid being 30106
                • uint32 configType being 2
                • bytes config being encoded UlnConfig with:
                  • uint64 confirmations being 15
                  • uint8 requiredDVNCount being 2
                  • uint8 optionalDVNCount being 0
                  • uint8 optionalDVNThreshold being 0
                  • address[] requiredDVNs being an array with 2 addresses: [0x589dEDbD617e0CBcB916A9223F4d1300c294236b (LayerZero Labs), 0xa59BA433ac34D2927232918Ef5B2eaAfcF130BA5 (Nethermind)]
                  • address[] optionalDVNs being an array with 0 addresses
          • Configure OFT ReceiveLibrary for Avalanche by calling EndpointV2.setConfig with:
            • EndpointV2 being 0x1a44076050125825900e736c501f859c50fE728c
            • address _oapp being SUSDS_OFT from chainlog
            • address _lib being 0xc02Ab410f0734EFa3F14628780e6e695156024C2
            • SetConfigParam[] _params being an array with one item:
              • uint32 _eid being 30106
              • uint32 configType being 2
              • bytes config being encoded UlnConfig with:
                • uint64 confirmations being 12
                • uint8 requiredDVNCount being 2
                • uint8 optionalDVNCount being 0
                • uint8 optionalDVNThreshold being 0
                • address[] requiredDVNs being an array with 2 addresses: [0x589dEDbD617e0CBcB916A9223F4d1300c294236b (LayerZero Labs), 0xa59BA433ac34D2927232918Ef5B2eaAfcF130BA5 (Nethermind)]
                • address[] optionalDVNs being an array with 0 addresses
          • Set OFT enforced options for Avalanche by calling SUSDS_OFT.setEnforcedOptions with:
            • SUSDS_OFT being the address from chainlog
            • EnforcedOptionParam[] _enforcedOptions being an array with 2 items:
              • SendOption
                • uint32 eid being 30106
                • uint16 msgType being 1 (Meaning SEND)
                • bytes options being encoded:
                  • uint128 _gas being 130_000
                  • uint128 _value being 0
              • SendAndCallOption
                • uint32 eid being 30106
                • uint16 msgType being 2 (Meaning SEND_AND_CALL)
                • bytes options being encoded:
                  • uint128 _gas being 130_000
                  • uint128 _value being 0
      • LSSKY->SKY Staking Rewards Update
        • Call TreasuryFundedFarmingInit.updateFarmVest() with the following parameters:
          • dist: 0x675671A8756dDb69F7254AFB030865388Ef699Ee
          • vestTot: 192,110,322 SKY
          • vestBgn: block.timestamp
          • vestTau: 90 days
      • Grove Genesis Capital Transfer
        • 20,797,477 USDS will be transferred to the Grove SubProxy as part of their Genesis Capital allocation.
      • Safe Harbor Update
        • Tthe Safe Harbor agreement will be updated to include the Avalanche chain along with the new GovernanceOAppReceiver on Avalanche, L2GovernanceRelay on Avalanche, USDS proxy and implementation on Avalanche, sUSDS proxy and implementation on Avalanche, SkyOFTAdapterMintBurn(USDS) on Avalanche, SkyOFTAdapter(sUSDS) on mainnet, and SkyOFTAdapterMintBurn(sUSDS) on Avalanche added by this spell.
      • Prime Agent Proxy Spells
        • A Spark proxy spell with address 0xFa5fc020311fCC1A467FEC5886640c7dD746deAa and codehash 0x2572a97846f7a6f9f159a9a69c2707cfa4186c061de2a0ec59e7a0d46473c74c will be whitelisted in the Spark StarGuard.
        • A Grove proxy spell with address 0x679eD4739c71300f7d78102AE5eE17EF8b8b2162 and codehash 0x4fa1f743b3d6d2855390724459129186dd684e1c07d59f88925f0059ba1e6c84 will be whitelisted in the Grove StarGuard.
    • 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
      ⚠️ Chainlog bump was not in the exect doc, but it is expected to updated after adding SUSDS_OFT (new address) to chainlog
    • 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

@0xLaz3r
Copy link
Copy Markdown
Contributor Author

0xLaz3r commented Apr 10, 2026

Spell has been deployed to 0x70Da14478667C08320Ef65506063abba84B6990f
Tenderly testnet: https://dashboard.tenderly.co/explorer/vnet/3ab4e80e-a6b6-457f-9d36-f887b7a65843

Foundry logs:

foundryup: use - forge 1.5.1-stable (b0a9dd9ced 2025-12-22T11:41:09.812070000Z)
foundryup: use - cast 1.5.1-stable (b0a9dd9ced 2025-12-22T11:41:09.812070000Z)
foundryup: use - anvil 1.5.1-stable (b0a9dd9ced 2025-12-22T11:41:09.812070000Z)
foundryup: use - chisel 1.5.1-stable (b0a9dd9ced 2025-12-22T11:41:09.812070000Z)

@SidestreamStrongStrawberry
Copy link
Copy Markdown
Collaborator

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
      ⚠️ Etherescan doesn't recognised the license correctly, but manual check on the contract shows license as AGPL-3.0-or-later which matches with GNU AGPLv3
  • 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
        ⚠️ Etherescan doesn't recognised the license correctly, but manual check on the contract shows license as AGPL-3.0-or-later which matches with GNU AGPLv3
      • Version matches
      • Optimizations are disabled
      • dss-exec-lib library address used (under 'Libraries Used') matches the hardcoded local DssExecLib address inside foundry.toml
      • 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 inside foundry.toml
      • 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)
        ⚠️ The newer commit is used, but this doesn't include breaking changes and has been known issue: diff can be found here
  • Tenderly Testnet checks
    maunally check usds oft send function from anvil fork of tenderly testnet
    • 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
    • make safeharbor-generate against the testnet returns "no updates"
      • IF the script outputs a warning indicated by ⚠️ ❗, notify Governance Facilitators
  • 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 inside foundry.toml 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
      e8c1c27
    • 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=""
[⠊] Compiling...
[⠒] Compiling 124 files with Solc 0.8.16
[⠆] Solc 0.8.16 finished in 2.12s
Compiler run successful!

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

Ran 60 tests for src/DssSpell.t.sol:DssSpellTest
[PASS] testAddedChainlogKeys() (gas: 3081399)
[SKIP] testAllocatorIntegration() (gas: 0)
[SKIP] testArbitrumGovRelay() (gas: 0)
[SKIP] testBaseGovRelay() (gas: 0)
[PASS] testBytecodeMatches() (gas: 5469229)
[PASS] testCastCost() (gas: 3070097)
[PASS] testCastOnTime() (gas: 3065780)
[PASS] testChainlogIntegrity() (gas: 8452175)
[PASS] testChainlogValues() (gas: 13901085)
[SKIP] testCollateralIntegrations() (gas: 0)
[SKIP] testContractSize() (gas: 0)
[SKIP] testDaoResolutions() (gas: 0)
[SKIP] testDeployCost() (gas: 0)
[SKIP] testEsmAuth() (gas: 0)
[PASS] testGeneral() (gas: 22288929)
[PASS] testGovernanceRelayAvalancheE2E() (gas: 4383507)
[SKIP] testIlkClipper() (gas: 0)
[SKIP] testL2ArbitrumSpell() (gas: 0)
[SKIP] testL2OptimismSpell() (gas: 0)
[SKIP] testLerpSurplusBuffer() (gas: 0)
[PASS] testLitePSMs() (gas: 4322176)
[SKIP] testLockstakeIlkIntegration() (gas: 0)
[SKIP] testMedianReaders() (gas: 0)
[SKIP] testMonthlySettlementCycleInflows() (gas: 0)
[SKIP] testNewAuthorizations() (gas: 0)
[SKIP] testNewCronJobs() (gas: 0)
[SKIP] testNewLineMomIlks() (gas: 0)
[PASS] testNextCastTime() (gas: 392563)
[SKIP] testOffboardings() (gas: 0)
[PASS] testOfficeHours() (gas: 438064)
[PASS] testOftPauseUnpause() (gas: 3183643)
[SKIP] testOptimismGovRelay() (gas: 0)
[SKIP] testOracleList() (gas: 0)
[SKIP] testOsmReaders() (gas: 0)
[PASS] testPSMs() (gas: 4409510)
[PASS] testPayments() (gas: 3183465)
[PASS] testPrimeAgentSpellExecutions() (gas: 10008452)
[SKIP] testRemovedChainlogKeys() (gas: 0)
[PASS] testRevertIfNotScheduled() (gas: 17530)
[PASS] testSPBEAMTauAndBudValues() (gas: 3082749)
[PASS] testSUsdsOftAvalancheRateLimitBlocked() (gas: 3314878)
[PASS] testSafeHarborAvalancheOnboarding() (gas: 7102644)
[PASS] testSplitter() (gas: 3588518)
[SKIP] testStarGuardInitialization() (gas: 0)
[PASS] testSystemTokens() (gas: 4060571)
[SKIP] testUnichainGovRelay() (gas: 0)
[PASS] testUpdateSafeHarborAddedAccounts() (gas: 7979675)
[PASS] testUsdsOftAvalancheE2E() (gas: 4791889)
[PASS] testUsdsOftAvalancheRateLimits() (gas: 3094461)
[PASS] testUseEta() (gas: 289131)
[SKIP] testVestDai() (gas: 0)
[SKIP] testVestMkr() (gas: 0)
[PASS] testVestSky() (gas: 3757719)
[SKIP] testVestSkyMint() (gas: 0)
[SKIP] testVestSpk() (gas: 0)
[SKIP] testVestUsds() (gas: 0)
[PASS] testVestedRewardsDist() (gas: 6135822)
[PASS] testWireLzGovSenderAvalanche() (gas: 3294073)
[PASS] testWireSUsdsOftAvalanche() (gas: 3441288)
[PASS] testWireUsdsOftAvalanche() (gas: 3413380)
Suite result: ok. 30 passed; 0 failed; 30 skipped; finished in 57.59s (337.89s CPU time)

Ran 2 test suites in 57.80s (69.99s CPU time): 32 tests passed, 0 failed, 30 skipped (62 total tests)

@amusingaxl
Copy link
Copy Markdown
Contributor

TL;DR: 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
      • ✅ confirmed via make check-deployed-spell
    • Optimization enabled: false UNLESS the contract size is too big AND all mitigation strategies (i.e.: removing revert strings) have failed
      • ✅ confirmed via make check-deployed-spell (DssSpell was not compiled with optimizations)
    • Default evmVersion
    • GNU AGPLv3 license
      • ✅ the verified source on Etherscan shows the correct AGPL license
      • ⚠️ the specific Etherscan metadata/license field used by make check-deployed-spell is incorrect or unknown, so the automated Valid license check below still fails
  • Source code validity
    • Deployed spell code matches source on github. (can be checked via make diff-deployed-spell or manually)
      • make diff-deployed-spell spell=0x70Da14478667C08320Ef65506063abba84B6990f exited cleanly with no diff
    • No new changes are made after previously given "good to deploy"
      • make diff-archive-spell date=2026-04-09 returned Spell, tests and base match the archive directory 2026-04-09-DssSpell
      • git diff --name-only origin/2026-04-09 -- foundry.toml src/DssSpell.sol archive/2026-04-09-DssSpell returned no changes
  • Deployed spell Etherscan checks
    • Ensure local code is up-to-date with the remote branch (e.g. git pull)
      • git fetch origin 2026-04-09
      • git rev-parse HEAD = git rev-parse origin/2026-04-09 = e8c1c274b382af64c8c2ab2c58cb93f937196df7
    • Automated checks via make check-deployed-spell
      • Verified
      • Valid license
        • ⚠️ make check-deployed-spell reported DssSpell was verified with an invalid or unknown license
      • Version matches
      • Optimizations are disabled
      • dss-exec-lib library address used (under 'Libraries Used') matches the hardcoded local DssExecLib address inside foundry.toml
      • deployed_spell_created matches deployment timestamp
      • deployed_spell_block matches deployment block number
    • Manual checks
      • Ensure make deploy-info tx=<tx> matches config
        • ✅ deployment tx = 0x9c99bbcbe28186e0e5e6dd74d4fea424a8661f2de536ba7644ea521403812e71
        • deployed_spell_created timestamp
        • deployed_spell_block block number
      • Check again that the PR did not modify the DssExecLib address inside foundry.toml
        • foundry.toml still links DssExecLib at 0x8De6DDbCd5053d32292AAA0D2105A32d108484a6
      • 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)
        ⚠️ the same newer version that is not in the releases tag that has been used in the past 3 years is used.
  • Tenderly Testnet checks
    • A testnet with the name matching spell description is found at maker dashboard
      • ✅ Tenderly Virtual Testnet 242b0777-9849-4d82-aa88-5b8b99f607d1
      • ✅ public explorer https://dashboard.tenderly.co/explorer/vnet/f0e1a493-6ae1-47b2-a680-78e00ae65e9c
      • ✅ display name 2026-04-09 MakerDAO Executive Spell
    • 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)
      • ✅ cast tx 0x686c7228de139e02ff1b9f28f3e13fa908555ef2d7841f0c05bdcd033f016549
      • to = 0x70Da14478667C08320Ef65506063abba84B6990f
      • ✅ schedule tx 0x2a570c3149653d44581571b8c4266f1c5d3c6642280456f33eb71a5947d68515 also targets the spell address
    • All actions are executed in the transaction trace
      • ✅ trace includes the expected major action surfaces, including LZ_GOV_SENDER, LZ_ENDPOINT, USDS_OFT, SUSDS_OFT, CHAINLOG, SAFE_HARBOR_AGREEMENT, reward distribution, and the Spark/Grove spell relays
    • No reverts are present that block execution
      • ✅ cast receipt status 0x1
    • No out-of-gas errors are present
      • ✅ cast tx gas used 2_831_160
      • ✅ receipt status 0x1
    • make safeharbor-generate against the testnet returns "no updates"
      • ✅ output ended with No updates to generate
      • IF the script outputs a warning indicated by ⚠️ ❗, notify Governance Facilitators
  • 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 inside foundry.toml is not being modified by the spell PR
      • git diff --name-only origin/2026-04-09 -- foundry.toml returned no changes
    • Check all CI tests are passing as at the latest commit
      e8c1c27
    • Ensure that any other env variable does not affect execution of the tests (for example, by inspecting the output of printenv | grep "FOUNDRY_\|DAPP_")
      • printenv | rg '^(FOUNDRY_|DAPP_)' returned no matches
    • Check all tests are passing locally using make test
      • make test exited with code 0
  • Publish an explicit "good to handover" comment
Ran 60 tests for src/DssSpell.t.sol:DssSpellTest
[PASS] testAddedChainlogKeys() (gas: 3081399)
[SKIP] testAllocatorIntegration() (gas: 0)
[SKIP] testArbitrumGovRelay() (gas: 0)
[SKIP] testBaseGovRelay() (gas: 0)
[PASS] testBytecodeMatches() (gas: 5469229)
[PASS] testCastCost() (gas: 3070097)
[PASS] testCastOnTime() (gas: 3065780)
[PASS] testChainlogIntegrity() (gas: 8452175)
[PASS] testChainlogValues() (gas: 13901085)
[SKIP] testCollateralIntegrations() (gas: 0)
[SKIP] testContractSize() (gas: 0)
[SKIP] testDaoResolutions() (gas: 0)
[SKIP] testDeployCost() (gas: 0)
[SKIP] testEsmAuth() (gas: 0)
[PASS] testGeneral() (gas: 22288929)
[PASS] testGovernanceRelayAvalancheE2E() (gas: 4383507)
[SKIP] testIlkClipper() (gas: 0)
[SKIP] testL2ArbitrumSpell() (gas: 0)
[SKIP] testL2OptimismSpell() (gas: 0)
[SKIP] testLerpSurplusBuffer() (gas: 0)
[PASS] testLitePSMs() (gas: 4322176)
[SKIP] testLockstakeIlkIntegration() (gas: 0)
[SKIP] testMedianReaders() (gas: 0)
[SKIP] testMonthlySettlementCycleInflows() (gas: 0)
[SKIP] testNewAuthorizations() (gas: 0)
[SKIP] testNewCronJobs() (gas: 0)
[SKIP] testNewLineMomIlks() (gas: 0)
[PASS] testNextCastTime() (gas: 392563)
[SKIP] testOffboardings() (gas: 0)
[PASS] testOfficeHours() (gas: 438064)
[PASS] testOftPauseUnpause() (gas: 3183643)
[SKIP] testOptimismGovRelay() (gas: 0)
[SKIP] testOracleList() (gas: 0)
[SKIP] testOsmReaders() (gas: 0)
[PASS] testPSMs() (gas: 4409510)
[PASS] testPayments() (gas: 3183465)
[PASS] testPrimeAgentSpellExecutions() (gas: 10008452)
[SKIP] testRemovedChainlogKeys() (gas: 0)
[PASS] testRevertIfNotScheduled() (gas: 17530)
[PASS] testSPBEAMTauAndBudValues() (gas: 3082749)
[PASS] testSUsdsOftAvalancheRateLimitBlocked() (gas: 3314878)
[PASS] testSafeHarborAvalancheOnboarding() (gas: 7090791)
[PASS] testSplitter() (gas: 3588518)
[SKIP] testStarGuardInitialization() (gas: 0)
[PASS] testSystemTokens() (gas: 4060452)
[SKIP] testUnichainGovRelay() (gas: 0)
[PASS] testUpdateSafeHarborAddedAccounts() (gas: 7979675)
[PASS] testUsdsOftAvalancheE2E() (gas: 4791889)
[PASS] testUsdsOftAvalancheRateLimits() (gas: 3094461)
[PASS] testUseEta() (gas: 289131)
[SKIP] testVestDai() (gas: 0)
[SKIP] testVestMkr() (gas: 0)
[PASS] testVestSky() (gas: 3757719)
[SKIP] testVestSkyMint() (gas: 0)
[SKIP] testVestSpk() (gas: 0)
[SKIP] testVestUsds() (gas: 0)
[PASS] testVestedRewardsDist() (gas: 6135822)
[PASS] testWireLzGovSenderAvalanche() (gas: 3294073)
[PASS] testWireSUsdsOftAvalanche() (gas: 3441288)
[PASS] testWireUsdsOftAvalanche() (gas: 3413380)
Suite result: ok. 30 passed; 0 failed; 30 skipped; finished in 176.09s (1519.85s CPU time)

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

Ran 2 test suites in 176.33s (213.65s CPU time): 32 tests passed, 0 failed, 30 skipped (62 total tests)

Copy link
Copy Markdown
Collaborator

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
Copy Markdown
Contributor

@amusingaxl amusingaxl 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

@0xLaz3r 0xLaz3r merged commit 4430ef4 into master Apr 10, 2026
3 checks passed
@0xLaz3r 0xLaz3r deleted the 2026-04-09 branch April 10, 2026 14:28
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