From e31c5fb200ca3bb6d6402f544d29cd604a5c97b3 Mon Sep 17 00:00:00 2001 From: blockfulintern <166863475+blockfulintern@users.noreply.github.com> Date: Wed, 15 Jul 2026 20:32:01 +0000 Subject: [PATCH] [verified] test(shutter): validate proposal 178 brainbot grant --- .../178-brainbot-grant/calldataCheck.t.sol | 91 +++++++++++++++++++ .../178-brainbot-grant/proposalCalldata.json | 13 +++ .../178-brainbot-grant/proposalDescription.md | 13 +++ src/shutter/shutter.t.sol | 9 ++ 4 files changed, 126 insertions(+) create mode 100644 src/shutter/proposals/178-brainbot-grant/calldataCheck.t.sol create mode 100644 src/shutter/proposals/178-brainbot-grant/proposalCalldata.json create mode 100644 src/shutter/proposals/178-brainbot-grant/proposalDescription.md diff --git a/src/shutter/proposals/178-brainbot-grant/calldataCheck.t.sol b/src/shutter/proposals/178-brainbot-grant/calldataCheck.t.sol new file mode 100644 index 0000000..d057eb3 --- /dev/null +++ b/src/shutter/proposals/178-brainbot-grant/calldataCheck.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.25 <0.9.0; + +import { CalldataComparison } from "@contracts/base/CalldataComparison.sol"; +import { Shutter_Governance } from "@shutter/shutter.t.sol"; +import { IAzorius } from "@shutter/interfaces/IAzorius.sol"; +import { IERC20 } from "@contracts/utils/interfaces/IERC20.sol"; + +contract Proposal_Shutter_178_Brainbot_Grant_Test is Shutter_Governance, CalldataComparison { + uint32 internal constant EXPECTED_PROPOSAL_ID = 178; + uint256 internal constant GRANT_AMOUNT = 107_000 * 10 ** 6; + + IERC20 internal constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + address internal constant BRAINBOT = 0xb75E2fB01521Bd7b6369F831414840A54a3C7234; + address internal constant LIVE_PROPOSER = 0x0f853a4c50763e0553Ac44E2546C0178B417c0Ba; + + uint256 internal treasuryBalanceBefore; + uint256 internal brainbotBalanceBefore; + + function setUp() public override { + super.setUp(); + vm.label(address(USDC), "USDC"); + vm.label(BRAINBOT, "brainbot gmbh"); + } + + function _selectFork() public override { + vm.createSelectFork({ blockNumber: 25_522_491, urlOrAlias: "mainnet" }); + } + + function _proposer() public pure override returns (address) { + return LIVE_PROPOSER; + } + + function _metadata() public pure override returns (string memory) { + return string.concat( + '{"title":"Provide a grant to brainbot gmbh (July 2026) ",', + '"description":"https://shutternetwork.discourse.group/t/provide-a-grant-to-brainbot-gmbh-july-2026/895', + "\\n\\nProvide a one time grant of 107,000 USDC to brainbot gmbh (July 2026)\"}" + ); + } + + function _isProposalSubmitted() public pure override returns (bool) { + return true; + } + + function _beforeProposal() public override { + assertEq(Azorius.totalProposalCount() - 1, EXPECTED_PROPOSAL_ID, "Unexpected latest proposal ID"); + assertEq( + uint8(Azorius.proposalState(EXPECTED_PROPOSAL_ID)), + uint8(IAzorius.ProposalState.ACTIVE), + "Proposal 178 should be active at the fork block" + ); + + treasuryBalanceBefore = USDC.balanceOf(ShutterGnosis); + brainbotBalanceBefore = USDC.balanceOf(BRAINBOT); + + assertGe(treasuryBalanceBefore, GRANT_AMOUNT, "Treasury cannot fund the grant"); + } + + function _prepareTransactions() internal pure override returns (IAzorius.Transaction[] memory transactions) { + transactions = new IAzorius.Transaction[](1); + transactions[0] = IAzorius.Transaction({ + to: address(USDC), + value: 0, + data: abi.encodeWithSelector(IERC20.transfer.selector, BRAINBOT, GRANT_AMOUNT), + operation: IAzorius.Operation.Call + }); + } + + function _afterExecution() public view override { + assertEq( + USDC.balanceOf(ShutterGnosis), + treasuryBalanceBefore - GRANT_AMOUNT, + "Treasury USDC decrease does not equal the grant" + ); + assertEq( + USDC.balanceOf(BRAINBOT), + brainbotBalanceBefore + GRANT_AMOUNT, + "brainbot USDC increase does not equal the grant" + ); + } + + function test_liveCalldataMatchesManualDerivation() public { + IAzorius.Transaction[] memory transactions = _prepareTransactions(); + (address[] memory targets, uint256[] memory values, bytes[] memory data,) = + _prepareTransactionsForExecution(transactions); + + string memory jsonContent = vm.readFile("src/shutter/proposals/178-brainbot-grant/proposalCalldata.json"); + _compareLiveCalldata(jsonContent, targets, values, data); + } +} diff --git a/src/shutter/proposals/178-brainbot-grant/proposalCalldata.json b/src/shutter/proposals/178-brainbot-grant/proposalCalldata.json new file mode 100644 index 0000000..3b21764 --- /dev/null +++ b/src/shutter/proposals/178-brainbot-grant/proposalCalldata.json @@ -0,0 +1,13 @@ +{ + "proposalId": "178", + "transactionHash": "0x90e266d5e1b1193a275473847f0ad57e597b3798cd3c0784481033ec06d787a8", + "blockNumber": 25522491, + "proposer": "0x0f853a4c50763e0553Ac44E2546C0178B417c0Ba", + "executableCalls": [ + { + "target": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "calldata": "0xa9059cbb000000000000000000000000b75e2fb01521bd7b6369f831414840a54a3c723400000000000000000000000000000000000000000000000000000018e9b26e00", + "value": "0" + } + ] +} diff --git a/src/shutter/proposals/178-brainbot-grant/proposalDescription.md b/src/shutter/proposals/178-brainbot-grant/proposalDescription.md new file mode 100644 index 0000000..90571c8 --- /dev/null +++ b/src/shutter/proposals/178-brainbot-grant/proposalDescription.md @@ -0,0 +1,13 @@ +# Provide a grant to brainbot gmbh (July 2026) + +Forum specification: https://shutternetwork.discourse.group/t/provide-a-grant-to-brainbot-gmbh-july-2026/895 + +Provide a one-time grant of 107,000 USDC to brainbot gmbh. + +Recipient: `0xb75E2fB01521Bd7b6369F831414840A54a3C7234` + +Exact on-chain Azorius metadata: + +```json +{"title":"Provide a grant to brainbot gmbh (July 2026) ","description":"https://shutternetwork.discourse.group/t/provide-a-grant-to-brainbot-gmbh-july-2026/895\n\nProvide a one time grant of 107,000 USDC to brainbot gmbh (July 2026)"} +``` diff --git a/src/shutter/shutter.t.sol b/src/shutter/shutter.t.sol index a345181..c082082 100644 --- a/src/shutter/shutter.t.sol +++ b/src/shutter/shutter.t.sol @@ -95,6 +95,15 @@ abstract contract Shutter_Governance is Test { bool passed = LinearERC20Voting.isPassed(proposalId); assertTrue(passed, "Proposal did not pass"); + // Advance through the Azorius timelock into the execution window. + (,, uint32 timelockPeriod,,) = Azorius.getProposal(proposalId); + vm.roll(block.number + timelockPeriod); + assertEq( + uint8(Azorius.proposalState(proposalId)), + uint8(IAzorius.ProposalState.EXECUTABLE), + "Proposal is not executable after timelock" + ); + // Execute the proposal _executeProposal(proposalId, transactions);