Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions src/shutter/proposals/178-brainbot-grant/calldataCheck.t.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
13 changes: 13 additions & 0 deletions src/shutter/proposals/178-brainbot-grant/proposalCalldata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"proposalId": "178",
"transactionHash": "0x90e266d5e1b1193a275473847f0ad57e597b3798cd3c0784481033ec06d787a8",
"blockNumber": 25522491,
"proposer": "0x0f853a4c50763e0553Ac44E2546C0178B417c0Ba",
"executableCalls": [
{
"target": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"calldata": "0xa9059cbb000000000000000000000000b75e2fb01521bd7b6369f831414840a54a3c723400000000000000000000000000000000000000000000000000000018e9b26e00",
"value": "0"
}
]
}
13 changes: 13 additions & 0 deletions src/shutter/proposals/178-brainbot-grant/proposalDescription.md
Original file line number Diff line number Diff line change
@@ -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)"}
```
9 changes: 9 additions & 0 deletions src/shutter/shutter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading