-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1de0fd9
commit b6580d3
Showing
3 changed files
with
137 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// Copyright Immutable Pty Ltd 2018 - 2024 | ||
// SPDX-License-Identifier: Apache 2.0 | ||
pragma solidity 0.8.19; | ||
|
||
import "forge-std/Test.sol"; | ||
import {GuardedMulticallerV2} from "../../contracts/multicall/GuardedMulticallerV2.sol"; | ||
import {MockFunctions} from "../../contracts/mocks/MockFunctions.sol"; | ||
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; | ||
import {SigUtils} from "./SigUtils.t.sol"; | ||
|
||
contract GuardedMulticallerV2Test is Test { | ||
GuardedMulticallerV2 gmc; | ||
SigUtils sigUtils; | ||
MockFunctions target; | ||
|
||
address defaultAdmin = makeAddr("defaultAdmin"); | ||
address signer; | ||
uint256 signerPk; | ||
|
||
function setUp() public { | ||
target = new MockFunctions(); | ||
(signer, signerPk) = makeAddrAndKey("signer"); | ||
|
||
gmc = new GuardedMulticallerV2(defaultAdmin, "name", "1"); | ||
vm.prank(defaultAdmin); | ||
gmc.grantMulticallSignerRole(signer); | ||
|
||
sigUtils = new SigUtils("name", "1", address(gmc)); | ||
} | ||
|
||
function test_Roles() public { | ||
assertTrue(gmc.hasRole(gmc.DEFAULT_ADMIN_ROLE(), defaultAdmin)); | ||
assertTrue(gmc.hasRole(gmc.MULTICALL_SIGNER_ROLE(), signer)); | ||
} | ||
|
||
function test_Execute() public { | ||
bytes32 ref = keccak256("ref"); | ||
uint256 deadline = block.timestamp + 1; | ||
GuardedMulticallerV2.Call[] memory calls = new GuardedMulticallerV2.Call[](1); | ||
calls[0] = GuardedMulticallerV2.Call(address(target), "succeed()", ""); | ||
bytes32 digest = sigUtils.hashTypedData(ref, calls, deadline); | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, digest); | ||
bytes memory signature = abi.encodePacked(r, s, v); | ||
gmc.execute(signer, ref, calls, deadline, signature); | ||
assertTrue(gmc.hasBeenExecuted(ref)); | ||
} | ||
|
||
function test_RevertWhen_UnauthorizedSigner() public { | ||
(address fakeSigner, uint256 fakeSignerPk) = makeAddrAndKey("fakeSigner"); | ||
bytes32 ref = keccak256("ref"); | ||
uint256 deadline = block.timestamp + 1; | ||
GuardedMulticallerV2.Call[] memory calls = new GuardedMulticallerV2.Call[](1); | ||
calls[0] = GuardedMulticallerV2.Call(address(target), "revertWithNoReason()", ""); | ||
bytes32 digest = sigUtils.hashTypedData(ref, calls, deadline); | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(fakeSignerPk, digest); | ||
bytes memory signature = abi.encodePacked(r, s, v); | ||
vm.expectRevert(abi.encodeWithSelector(GuardedMulticallerV2.UnauthorizedSigner.selector, fakeSigner)); | ||
gmc.execute(fakeSigner, ref, calls, deadline, signature); | ||
} | ||
|
||
function test_RevertWhen_CallFailed() public { | ||
bytes32 ref = keccak256("ref"); | ||
uint256 deadline = block.timestamp + 1; | ||
GuardedMulticallerV2.Call[] memory calls = new GuardedMulticallerV2.Call[](1); | ||
calls[0] = GuardedMulticallerV2.Call(address(target), "revertWithNoReason()", ""); | ||
bytes32 digest = sigUtils.hashTypedData(ref, calls, deadline); | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, digest); | ||
bytes memory signature = abi.encodePacked(r, s, v); | ||
vm.expectRevert(abi.encodeWithSelector(GuardedMulticallerV2.FailedCall.selector, calls[0])); | ||
gmc.execute(signer, ref, calls, deadline, signature); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.19; | ||
|
||
import {GuardedMulticallerV2} from "../../contracts/multicall/GuardedMulticallerV2.sol"; | ||
|
||
contract SigUtils { | ||
bytes32 private constant _TYPE_HASH = | ||
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); | ||
|
||
bytes32 internal constant CALL_TYPEHASH = keccak256("Call(address target,string functionSignature,bytes data)"); | ||
|
||
bytes32 internal constant MULTICALL_TYPEHASH = | ||
keccak256( | ||
"Multicall(bytes32 ref,Call[] call,uint256 deadline)Call(address target,string functionSignature,bytes data)" | ||
); | ||
|
||
bytes32 private immutable cachedDomainSeparator; | ||
|
||
constructor(string memory _name, string memory _version, address _verifyingContract) { | ||
cachedDomainSeparator = keccak256(abi.encode(_TYPE_HASH, keccak256(bytes(_name)), keccak256(bytes(_version)), block.chainid, _verifyingContract)); | ||
} | ||
|
||
function _hashCallArray(GuardedMulticallerV2.Call[] calldata _calls) internal pure returns (bytes32) { | ||
bytes32[] memory hashedCallArr = new bytes32[](_calls.length); | ||
for (uint256 i = 0; i < _calls.length; i++) { | ||
hashedCallArr[i] = keccak256( | ||
abi.encode(CALL_TYPEHASH, _calls[i].target, _calls[i].functionSignature, _calls[i].data) | ||
); | ||
} | ||
return keccak256(abi.encodePacked(hashedCallArr)); | ||
} | ||
|
||
function hashTypedData( | ||
bytes32 _reference, | ||
GuardedMulticallerV2.Call[] calldata _calls, | ||
uint256 _deadline | ||
) public view returns (bytes32) { | ||
bytes32 digest = keccak256(abi.encode(MULTICALL_TYPEHASH, _reference, _hashCallArray(_calls), _deadline)); | ||
return keccak256(abi.encodePacked("\x19\x01", cachedDomainSeparator, digest)); | ||
} | ||
} |