Skip to content
Draft
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
8 changes: 2 additions & 6 deletions script/universal/MultisigDeploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ contract MultisigDeployScript is Script {
console.log(" Salt Nonce:", saltNonce);

// Resolve owner addresses (combine direct owners + referenced safe addresses)
address[] memory resolvedOwners = resolveOwnerAddresses(config, safes);
address[] memory resolvedOwners = resolveOwnerAddresses(config);

console.log(" Total Owners:", resolvedOwners.length);
console.log(" Direct Owners:", config.owners.length);
Expand Down Expand Up @@ -186,11 +186,7 @@ contract MultisigDeployScript is Script {
}
}

function resolveOwnerAddresses(SafeWallet memory config, SafeWallet[] memory safes)
internal
view
returns (address[] memory)
{
function resolveOwnerAddresses(SafeWallet memory config) internal view returns (address[] memory) {
uint256 totalOwners = config.owners.length + config.ownerRefIndices.length;
address[] memory resolved = new address[](totalOwners);

Expand Down
47 changes: 45 additions & 2 deletions script/universal/MultisigScript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {console} from "forge-std/console.sol";
import {Script} from "forge-std/Script.sol";
import {IMulticall3} from "forge-std/interfaces/IMulticall3.sol";
import {Vm} from "forge-std/Vm.sol";
import {stdJson} from "forge-std/StdJson.sol";

import {IGnosisSafe, Enum} from "./IGnosisSafe.sol";
import {Signatures} from "./Signatures.sol";
Expand Down Expand Up @@ -332,7 +333,7 @@ abstract contract MultisigScript is Script {
return safes;
}

function _transactionDatas(address[] memory _safes) private view returns (bytes[] memory datas, uint256 value) {
function _transactionDatas(address[] memory _safes) internal view returns (bytes[] memory datas, uint256 value) {
// Build the calls and sum the values
IMulticall3.Call3Value[] memory calls = _buildCalls();
for (uint256 i = 0; i < calls.length; i++) {
Expand Down Expand Up @@ -375,7 +376,8 @@ abstract contract MultisigScript is Script {
}

function _printDataToSign(address _safe, bytes memory _data, uint256 _value) internal {
bytes memory txData = _encodeTransactionData(_safe, _data, _value);
bytes memory txData =
_printDataHashes() ? _encodeTransactionData(_safe, _data, _value) : _encodeEIP712Json(_safe, _data, _value);
bytes32 hash = _getTransactionHash(_safe, _data, _value);

emit DataToSign(txData);
Expand All @@ -397,6 +399,14 @@ abstract contract MultisigScript is Script {
console.log("###############################");
}

// Controls whether the safe tx is printed as structured EIP-712 data, or just hashes.
//
// If you want to print and sign hashed EIP-712 data (domain + message hash) rather than the
// typed EIP-712 data struct, override this function and return `true`.
function _printDataHashes() internal view virtual returns (bool) {
return false;
}

function _executeTransaction(
address _safe,
bytes memory _data,
Expand Down Expand Up @@ -566,6 +576,39 @@ abstract contract MultisigScript is Script {
});
}

function _encodeEIP712Json(address _safe, bytes memory _data, uint256 _value) internal returns (bytes memory) {
string memory types = '{"EIP712Domain":[' '{"name":"chainId","type":"uint256"},'
'{"name":"verifyingContract","type":"address"}],' '"SafeTx":[' '{"name":"to","type":"address"},'
'{"name":"value","type":"uint256"},' '{"name":"data","type":"bytes"},'
'{"name":"operation","type":"uint8"},' '{"name":"safeTxGas","type":"uint256"},'
'{"name":"baseGas","type":"uint256"},' '{"name":"gasPrice","type":"uint256"},'
'{"name":"gasToken","type":"address"},' '{"name":"refundReceiver","type":"address"},'
'{"name":"nonce","type":"uint256"}]}';

string memory domain = stdJson.serialize("domain", "chainId", uint256(block.chainid));
domain = stdJson.serialize("domain", "verifyingContract", address(_safe));

string memory message = stdJson.serialize("message", "to", MULTICALL3_ADDRESS);
message = stdJson.serialize("message", "value", _value);
message = stdJson.serialize("message", "data", _data);
message = stdJson.serialize(
"message", "operation", uint256(_value == 0 ? Enum.Operation.DelegateCall : Enum.Operation.Call)
);
message = stdJson.serialize("message", "safeTxGas", uint256(0));
message = stdJson.serialize("message", "baseGas", uint256(0));
message = stdJson.serialize("message", "gasPrice", uint256(0));
message = stdJson.serialize("message", "gasToken", address(0));
message = stdJson.serialize("message", "refundReceiver", address(0));
message = stdJson.serialize("message", "nonce", _getNonce(_safe));

string memory json = stdJson.serialize("", "primaryType", string("SafeTx"));
json = stdJson.serialize("", "types", types);
json = stdJson.serialize("", "domain", domain);
json = stdJson.serialize("", "message", message);

return abi.encodePacked(json);
}

function _execTransactionCalldata(address _safe, bytes memory _data, uint256 _value, bytes memory _signatures)
internal
pure
Expand Down
4 changes: 4 additions & 0 deletions test/universal/DoubleNestedMultisigBuilder.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ contract DoubleNestedMultisigBuilderTest is Test, DoubleNestedMultisigBuilder {
return safe4;
}

function _printDataHashes() internal view override returns (bool) {
return true;
}

function test_sign_double_nested_safe1() external {
vm.recordLogs();
bytes memory txData = abi.encodeCall(DoubleNestedMultisigBuilder.sign, (safe1, safe3));
Expand Down
30 changes: 30 additions & 0 deletions test/universal/MultisigBuilder.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ contract MultisigBuilderTest is Test, MultisigBuilder {
Counter internal counter = new Counter(address(safe));

function () internal view returns (IMulticall3.Call3Value[] memory) buildCallsInternal;
function () internal view returns (bool) printDataHashesInternal = printDataHashesEnabled;

bytes internal dataToSignNoValue =
// solhint-disable-next-line max-line-length
Expand All @@ -29,6 +30,10 @@ contract MultisigBuilderTest is Test, MultisigBuilder {
// solhint-disable-next-line max-line-length
hex"1901d4bb33110137810c444c1d9617abe97df097d587ecde64e6fcb38d7f49e1280cd150dbb03d4bb38e5325a914ff3861da880437fd5856c0f7e39054e64e05aed0";

string internal dataToSignTyped =
// solhint-disable-next-line max-line-length
'{"domain":{"chainId":31337,"verifyingContract":"0x00000000000000000000000000000000000003e9"},"message":{"baseGas":0,"data":"0x174dea710000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004d09de08a00000000000000000000000000000000000000000000000000000000","gasPrice":0,"gasToken":"0x0000000000000000000000000000000000000000","nonce":0,"operation":1,"refundReceiver":"0x0000000000000000000000000000000000000000","safeTxGas":0,"to":"0xcA11bde05977b3631167028862bE2a173976CA11","value":0},"primaryType":"SafeTx","types":{"EIP712Domain":[{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"SafeTx":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"},{"name":"operation","type":"uint8"},{"name":"safeTxGas","type":"uint256"},{"name":"baseGas","type":"uint256"},{"name":"gasPrice","type":"uint256"},{"name":"gasToken","type":"address"},{"name":"refundReceiver","type":"address"},{"name":"nonce","type":"uint256"}]}}';

function setUp() public {
vm.etch(safe, Preinstalls.getDeployedCode(Preinstalls.Safe_v130, block.chainid));
vm.etch(Preinstalls.MultiCall3, Preinstalls.getDeployedCode(Preinstalls.MultiCall3, block.chainid));
Expand All @@ -54,6 +59,10 @@ contract MultisigBuilderTest is Test, MultisigBuilder {
return address(safe);
}

function _printDataHashes() internal view override returns (bool) {
return printDataHashesInternal();
}

function test_sign_no_value() external {
buildCallsInternal = _buildCallsNoValue;

Expand All @@ -78,6 +87,19 @@ contract MultisigBuilderTest is Test, MultisigBuilder {
assertEq(keccak256(logs[logs.length - 1].data), keccak256(abi.encode(dataToSignWithValue)));
}

function test_sign_typed_data() external {
buildCallsInternal = _buildCallsNoValue;
printDataHashesInternal = printDataHashesDisabled;

vm.recordLogs();
bytes memory txData = abi.encodeCall(MultisigBuilder.sign, ());
vm.prank(wallet1.addr);
(bool success,) = address(this).call(txData);
vm.assertTrue(success);
Vm.Log[] memory logs = vm.getRecordedLogs();
assertEq(keccak256(logs[logs.length - 1].data), keccak256(abi.encode(dataToSignTyped)));
}

function test_run() external {
buildCallsInternal = _buildCallsNoValue;
(uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(wallet1, keccak256(dataToSignNoValue));
Expand Down Expand Up @@ -111,4 +133,12 @@ contract MultisigBuilderTest is Test, MultisigBuilder {

return calls;
}

function printDataHashesEnabled() internal pure returns (bool) {
return true;
}

function printDataHashesDisabled() internal pure returns (bool) {
return false;
}
}
4 changes: 4 additions & 0 deletions test/universal/NestedMultisigBuilder.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ contract NestedMultisigBuilderTest is Test, NestedMultisigBuilder {
return address(safe3);
}

function _printDataHashes() internal view override returns (bool) {
return true;
}

function test_sign_safe1() external {
vm.recordLogs();
bytes memory txData = abi.encodeCall(NestedMultisigBuilder.sign, (safe1));
Expand Down
Loading