diff --git a/foundry.toml b/foundry.toml index bdc785c..10799e9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,6 +4,5 @@ fs_permissions = [ {access = "read-write", path = "./"} ] optimizer = true optimizer_runs = 999999 solc_version = "0.8.15" -viaIR = true # See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/script/universal/MultisigBase.sol b/script/universal/MultisigBase.sol index 2e5604f..4005c22 100644 --- a/script/universal/MultisigBase.sol +++ b/script/universal/MultisigBase.sol @@ -92,6 +92,39 @@ abstract contract MultisigBase is Simulator { }); } + function _encodeCall(IGnosisSafe _safe, bytes memory _data, bytes memory _signatures) internal pure returns (bytes memory) { + return abi.encodeCall( + _safe.execTransaction, + ( + address(multicall), + 0, + _data, + Enum.Operation.DelegateCall, + 0, + 0, + 0, + address(0), + payable(address(0)), + _signatures + ) + ); + } + + function _execTransaction(IGnosisSafe _safe, bytes memory _data, bytes memory _signatures) internal returns (bool) { + return _safe.execTransaction({ + to: address(multicall), + value: 0, + data: _data, + operation: Enum.Operation.DelegateCall, + safeTxGas: 0, + baseGas: 0, + gasPrice: 0, + gasToken: address(0), + refundReceiver: payable(address(0)), + signatures: _signatures + }); + } + function _executeTransaction(address _safe, IMulticall3.Call3[] memory _calls, bytes memory _signatures) internal returns (Vm.AccountAccess[] memory, SimulationPayload memory) @@ -101,39 +134,15 @@ abstract contract MultisigBase is Simulator { bytes32 hash = _getTransactionHash(_safe, data); _signatures = prepareSignatures(_safe, hash, _signatures); + bytes memory simData = _encodeCall(safe, data, _signatures); logSimulationLink({ _to: _safe, _from: msg.sender, - _data: abi.encodeCall( - safe.execTransaction, - ( - address(multicall), - 0, - data, - Enum.Operation.DelegateCall, - 0, - 0, - 0, - address(0), - payable(address(0)), - _signatures - ) - ) + _data: simData }); vm.startStateDiffRecording(); - bool success = safe.execTransaction({ - to: address(multicall), - value: 0, - data: data, - operation: Enum.Operation.DelegateCall, - safeTxGas: 0, - baseGas: 0, - gasPrice: 0, - gasToken: address(0), - refundReceiver: payable(address(0)), - signatures: _signatures - }); + bool success = _execTransaction(safe, data, _signatures); Vm.AccountAccess[] memory accesses = vm.stopAndReturnStateDiff(); require(success, "MultisigBase::_executeTransaction: Transaction failed"); require(accesses.length > 0, "MultisigBase::_executeTransaction: No state changes"); @@ -143,18 +152,7 @@ abstract contract MultisigBase is Simulator { SimulationPayload memory simPayload = SimulationPayload({ from: msg.sender, to: address(safe), - data: abi.encodeCall(safe.execTransaction, ( - address(multicall), - 0, - data, - Enum.Operation.DelegateCall, - 0, - 0, - 0, - address(0), - payable(address(0)), - _signatures - )), + data: simData, stateOverrides: new SimulationStateOverride[](0) }); return (accesses, simPayload); @@ -241,15 +239,7 @@ abstract contract MultisigBase is Simulator { uint256 j; for (uint256 i; i < count; i++) { (v, r, s) = signatureSplit(_signatures, i); - address owner; - if (v <= 1) { - owner = address(uint160(uint256(r))); - } else if (v > 30) { - owner = - ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v - 4, r, s); - } else { - owner = ecrecover(dataHash, v, r, s); - } + address owner = extractOwner(dataHash, r, s, v); // skip duplicate owners uint256 k; @@ -281,13 +271,28 @@ abstract contract MultisigBase is Simulator { // append the non-static part of the signatures (can contain EIP-1271 signatures if contracts are signers) // if there were any duplicates detected above, they will be safely ignored by Safe's checkNSignatures method - if (_signatures.length > sorted.length) { - sorted = bytes.concat(sorted, Bytes.slice(_signatures, sorted.length, _signatures.length - sorted.length)); - } + sorted = appendRemainingBytes(sorted, _signatures); return sorted; } + function appendRemainingBytes(bytes memory a1, bytes memory a2) internal pure returns (bytes memory) { + if (a2.length > a1.length) { + a1 = bytes.concat(a1, Bytes.slice(a2, a1.length, a2.length - a1.length)); + } + return a1; + } + + function extractOwner(bytes32 dataHash, bytes32 r, bytes32 s, uint8 v) internal pure returns (address) { + if (v <= 1) { + return address(uint160(uint256(r))); + } + if (v > 30) { + return ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v - 4, r, s); + } + return ecrecover(dataHash, v, r, s); + } + // see https://github.com/safe-global/safe-contracts/blob/1ed486bb148fe40c26be58d1b517cec163980027/contracts/common/SignatureDecoder.sol function signatureSplit(bytes memory signatures, uint256 pos) internal diff --git a/script/universal/NestedMultisigBuilder.sol b/script/universal/NestedMultisigBuilder.sol index 10aeabf..51ec796 100644 --- a/script/universal/NestedMultisigBuilder.sol +++ b/script/universal/NestedMultisigBuilder.sol @@ -156,62 +156,10 @@ abstract contract NestedMultisigBuilder is MultisigBase { internal returns (Vm.AccountAccess[] memory, SimulationPayload memory) { - IGnosisSafe safe = IGnosisSafe(payable(_safe)); IGnosisSafe signerSafe = IGnosisSafe(payable(_signerSafe)); + IGnosisSafe safe = IGnosisSafe(payable(_safe)); bytes memory data = abi.encodeCall(IMulticall3.aggregate3, (_calls)); - bytes32 hash = _getTransactionHash(_safe, data); - IMulticall3.Call3[] memory calls = new IMulticall3.Call3[](2); - - // simulate an approveHash, so that signer can verify the data they are signing - bytes memory approveHashData = abi.encodeCall(IMulticall3.aggregate3, (toArray( - IMulticall3.Call3({ - target: _safe, - allowFailure: false, - callData: abi.encodeCall(safe.approveHash, (hash)) - }) - ))); - bytes memory approveHashExec = abi.encodeCall( - signerSafe.execTransaction, - ( - address(multicall), - 0, - approveHashData, - Enum.Operation.DelegateCall, - 0, - 0, - 0, - address(0), - payable(address(0)), - genPrevalidatedSignature(address(multicall)) - ) - ); - calls[0] = IMulticall3.Call3({ - target: _signerSafe, - allowFailure: false, - callData: approveHashExec - }); - - // simulate the final state changes tx, so that signer can verify the final results - bytes memory finalExec = abi.encodeCall( - safe.execTransaction, - ( - address(multicall), - 0, - data, - Enum.Operation.DelegateCall, - 0, - 0, - 0, - address(0), - payable(address(0)), - genPrevalidatedSignature(_signerSafe) - ) - ); - calls[1] = IMulticall3.Call3({ - target: _safe, - allowFailure: false, - callData: finalExec - }); + IMulticall3.Call3[] memory calls = _simulateForSignerCalls(signerSafe, safe, data); // For each safe, determine if a nonce override is needed. At this point, // no state overrides (i.e. vm.store) have been applied to the Foundry VM, @@ -261,4 +209,37 @@ abstract contract NestedMultisigBuilder is MultisigBase { Vm.AccountAccess[] memory accesses = simulateFromSimPayload(simPayload); return (accesses, simPayload); } + + function _simulateForSignerCalls(IGnosisSafe _signerSafe, IGnosisSafe _safe, bytes memory _data) + internal view + returns (IMulticall3.Call3[] memory) + { + IMulticall3.Call3[] memory calls = new IMulticall3.Call3[](2); + bytes32 hash = _getTransactionHash(address(_safe), _data); + + // simulate an approveHash, so that signer can verify the data they are signing + bytes memory approveHashData = abi.encodeCall(IMulticall3.aggregate3, (toArray( + IMulticall3.Call3({ + target: address(_safe), + allowFailure: false, + callData: abi.encodeCall(_safe.approveHash, (hash)) + }) + ))); + bytes memory approveHashExec = _encodeCall(_signerSafe, approveHashData, genPrevalidatedSignature(address(multicall))); + calls[0] = IMulticall3.Call3({ + target: address(_signerSafe), + allowFailure: false, + callData: approveHashExec + }); + + // simulate the final state changes tx, so that signer can verify the final results + bytes memory finalExec = _encodeCall(_safe, _data, genPrevalidatedSignature(address(_signerSafe))); + calls[1] = IMulticall3.Call3({ + target: address(_safe), + allowFailure: false, + callData: finalExec + }); + + return calls; + } }