|
| 1 | +// SPDX-License-Identifier: UNLICENSED |
| 2 | +pragma solidity >=0.8.27; |
| 3 | + |
| 4 | +import {GovernanceProposerBase} from "../Base.t.sol"; |
| 5 | +import {GovernanceProposer} from "@aztec/governance/proposer/GovernanceProposer.sol"; |
| 6 | + |
| 7 | +import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; |
| 8 | +import {Slot, Timestamp} from "@aztec/core/libraries/TimeLib.sol"; |
| 9 | +import {Fakerollup} from "../mocks/Fakerollup.sol"; |
| 10 | +import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; |
| 11 | +import {Signature} from "@aztec/shared/libraries/SignatureLib.sol"; |
| 12 | +import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol"; |
| 13 | +import {Errors} from "@aztec/governance/libraries/Errors.sol"; |
| 14 | + |
| 15 | +contract MaliciousRollup is Fakerollup { |
| 16 | + uint256 public numberOfRounds; |
| 17 | + GovernanceProposer public governanceProposer; |
| 18 | + IPayload public proposal; |
| 19 | + |
| 20 | + function maliciousValues(uint256 _numberOfRounds, GovernanceProposer _governanceProposer, IPayload _proposal) |
| 21 | + external |
| 22 | + { |
| 23 | + numberOfRounds = _numberOfRounds; |
| 24 | + governanceProposer = _governanceProposer; |
| 25 | + proposal = _proposal; |
| 26 | + } |
| 27 | + |
| 28 | + function commenceAttack() external { |
| 29 | + governanceProposer.signal(proposal); |
| 30 | + } |
| 31 | + |
| 32 | + function getCurrentProposer() external override returns (address) { |
| 33 | + if (numberOfRounds <= 1) { |
| 34 | + return address(this); |
| 35 | + } |
| 36 | + |
| 37 | + numberOfRounds--; |
| 38 | + |
| 39 | + governanceProposer.signal(proposal); |
| 40 | + |
| 41 | + return address(this); |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +// https://linear.app/aztec-labs/issue/TMNT-150/spearbit-gov-finding-2-potential-reentrancy-with-malicious-rollup |
| 46 | +contract TestTmnt150 is GovernanceProposerBase { |
| 47 | + using MessageHashUtils for bytes32; |
| 48 | + |
| 49 | + IPayload internal proposal = IPayload(address(0xdeadbeef)); |
| 50 | + address internal proposer = address(0); |
| 51 | + MaliciousRollup internal rollup1; |
| 52 | + |
| 53 | + uint256 internal privateKey = 0x1234567890; |
| 54 | + Signature internal signature; |
| 55 | + |
| 56 | + function setUp() public override { |
| 57 | + super.setUp(); |
| 58 | + } |
| 59 | + |
| 60 | + function test_reentry() external { |
| 61 | + rollup1 = new MaliciousRollup(); |
| 62 | + |
| 63 | + proposer = vm.addr(privateKey); |
| 64 | + rollup1.setProposer(proposer); |
| 65 | + |
| 66 | + vm.prank(registry.getGovernance()); |
| 67 | + registry.addRollup(IRollup(address(rollup1))); |
| 68 | + vm.warp(Timestamp.unwrap(rollup1.getTimestampForSlot(Slot.wrap(1)))); |
| 69 | + |
| 70 | + // Create a signature for the proposer |
| 71 | + uint256 round = governanceProposer.getCurrentRound(); |
| 72 | + signature = createSignature(privateKey, address(proposal), round); |
| 73 | + |
| 74 | + rollup1.maliciousValues(5, governanceProposer, proposal); |
| 75 | + |
| 76 | + assertEq(governanceProposer.signalCount(address(rollup1), 0, proposal), 0, "invalid number of votes"); |
| 77 | + |
| 78 | + vm.expectRevert(abi.encodeWithSelector(Errors.GovernanceProposer__SignalAlreadyCastForSlot.selector, 1)); |
| 79 | + |
| 80 | + rollup1.commenceAttack(); |
| 81 | + |
| 82 | + assertEq(governanceProposer.signalCount(address(rollup1), 0, proposal), 0, "invalid number of votes"); |
| 83 | + } |
| 84 | + |
| 85 | + function createSignature(uint256 _privateKey, address _payload, uint256 _round) |
| 86 | + internal |
| 87 | + view |
| 88 | + returns (Signature memory) |
| 89 | + { |
| 90 | + address signer = vm.addr(_privateKey); |
| 91 | + bytes32 digest = governanceProposer.getSignalSignatureDigest(IPayload(_payload), signer, _round); |
| 92 | + |
| 93 | + (uint8 v, bytes32 r, bytes32 s) = vm.sign(_privateKey, digest); |
| 94 | + |
| 95 | + return Signature({v: v, r: r, s: s}); |
| 96 | + } |
| 97 | +} |
0 commit comments