Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-introduce Safe interface, and remove from function signatures #104

Merged
merged 3 commits into from
Oct 29, 2024
Merged
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
4 changes: 2 additions & 2 deletions script/deploy/l1/SetGasLimitBuilder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ abstract contract SetGasLimitBuilder is MultisigBuilder {
return SYSTEM_CONFIG_OWNER;
}

function _getNonce(IGnosisSafe safe) internal view override returns (uint256 nonce) {
nonce = safe.nonce() + _nonceOffset();
function _getNonce(address safe) internal view override returns (uint256 nonce) {
nonce = IGnosisSafe(safe).nonce() + _nonceOffset();
}

// We need to expect that the gas limit will have been updated previously in our simulation
Expand Down
117 changes: 117 additions & 0 deletions script/universal/IGnosisSafe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.10;

/// @title Enum - Collection of enums used in Safe contracts.
/// @author Richard Meissner - @rmeissner
abstract contract Enum {
enum Operation {
Call,
DelegateCall
}
}

/// @title IGnosisSafe - Gnosis Safe Interface
interface IGnosisSafe {
event AddedOwner(address owner);
event ApproveHash(bytes32 indexed approvedHash, address indexed owner);
event ChangedFallbackHandler(address handler);
event ChangedGuard(address guard);
event ChangedThreshold(uint256 threshold);
event DisabledModule(address module);
event EnabledModule(address module);
event ExecutionFailure(bytes32 txHash, uint256 payment);
event ExecutionFromModuleFailure(address indexed module);
event ExecutionFromModuleSuccess(address indexed module);
event ExecutionSuccess(bytes32 txHash, uint256 payment);
event RemovedOwner(address owner);
event SafeReceived(address indexed sender, uint256 value);
event SafeSetup(
address indexed initiator, address[] owners, uint256 threshold, address initializer, address fallbackHandler
);
event SignMsg(bytes32 indexed msgHash);

function VERSION() external view returns (string memory);
function addOwnerWithThreshold(address owner, uint256 _threshold) external;
function approveHash(bytes32 hashToApprove) external;
function approvedHashes(address, bytes32) external view returns (uint256);
function changeThreshold(uint256 _threshold) external;
function checkNSignatures(bytes32 dataHash, bytes memory data, bytes memory signatures, uint256 requiredSignatures)
external
view;
function checkSignatures(bytes32 dataHash, bytes memory data, bytes memory signatures) external view;
function disableModule(address prevModule, address module) external;
function domainSeparator() external view returns (bytes32);
function enableModule(address module) external;
function encodeTransactionData(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver,
uint256 _nonce
) external view returns (bytes memory);
function execTransaction(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver,
bytes memory signatures
) external payable returns (bool success);
function execTransactionFromModule(address to, uint256 value, bytes memory data, Enum.Operation operation)
external
returns (bool success);
function execTransactionFromModuleReturnData(address to, uint256 value, bytes memory data, Enum.Operation operation)
external
returns (bool success, bytes memory returnData);
function getChainId() external view returns (uint256);
function getModulesPaginated(address start, uint256 pageSize)
external
view
returns (address[] memory array, address next);
function getOwners() external view returns (address[] memory);
function getStorageAt(uint256 offset, uint256 length) external view returns (bytes memory);
function getThreshold() external view returns (uint256);
function getTransactionHash(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver,
uint256 _nonce
) external view returns (bytes32);
function isModuleEnabled(address module) external view returns (bool);
function isOwner(address owner) external view returns (bool);
function nonce() external view returns (uint256);
function removeOwner(address prevOwner, address owner, uint256 _threshold) external;
function requiredTxGas(address to, uint256 value, bytes memory data, Enum.Operation operation)
external
returns (uint256);
function setFallbackHandler(address handler) external;
function setGuard(address guard) external;
function setup(
address[] memory _owners,
uint256 _threshold,
address to,
bytes memory data,
address fallbackHandler,
address paymentToken,
uint256 payment,
address paymentReceiver
) external;
function signedMessages(bytes32) external view returns (uint256);
function simulateAndRevert(address targetContract, bytes memory calldataPayload) external;
function swapOwner(address prevOwner, address oldOwner, address newOwner) external;
}
45 changes: 21 additions & 24 deletions script/universal/MultisigBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {Vm} from "forge-std/Vm.sol";
import {console} from "forge-std/console.sol";
import {CommonBase} from "forge-std/Base.sol";
import {IMulticall3} from "forge-std/interfaces/IMulticall3.sol";
import {IGnosisSafe, Enum} from "@eth-optimism-bedrock/scripts/interfaces/IGnosisSafe.sol";
import {IGnosisSafe, Enum} from "./IGnosisSafe.sol";
import {Simulation} from "./Simulation.sol";
import {Signatures} from "./Signatures.sol";

Expand Down Expand Up @@ -33,8 +33,8 @@ abstract contract MultisigBase is CommonBase {
// 3. If it does not exist and _readFrom_SAFE_NONCE() returns true, we do the same for the
// SAFE_NONCE env var.
// 4. Otherwise we fallback to the safe's current nonce (no override).
function _getNonce(IGnosisSafe safe) internal view virtual returns (uint256 nonce) {
uint256 safeNonce = safe.nonce();
function _getNonce(address _safe) internal view virtual returns (uint256 nonce) {
uint256 safeNonce = IGnosisSafe(_safe).nonce();
nonce = safeNonce;

// first try SAFE_NONCE
Expand All @@ -45,25 +45,25 @@ abstract contract MultisigBase is CommonBase {
}

// then try SAFE_NONCE_{UPPERCASE_SAFE_ADDRESS}
string memory envVarName = string.concat("SAFE_NONCE_", vm.toUppercase(vm.toString(address(safe))));
string memory envVarName = string.concat("SAFE_NONCE_", vm.toUppercase(vm.toString(_safe)));
try vm.envUint(envVarName) {
nonce = vm.envUint(envVarName);
} catch {}

// print if any override
if (nonce != safeNonce) {
console.log("Overriding nonce for safe %s: %d -> %d", address(safe), safeNonce, nonce);
console.log("Overriding nonce for safe %s: %d -> %d", _safe, safeNonce, nonce);
}
}

function _printDataToSign(IGnosisSafe _safe, IMulticall3.Call3[] memory _calls) internal {
function _printDataToSign(address _safe, IMulticall3.Call3[] memory _calls) internal {
bytes memory data = abi.encodeCall(IMulticall3.aggregate3, (_calls));
bytes memory txData = _encodeTransactionData(_safe, data);
bytes32 hash = _getTransactionHash(_safe, data);

emit DataToSign(txData);

console.log("---\nIf submitting onchain, call Safe.approveHash on %s with the following hash:", address(_safe));
console.log("---\nIf submitting onchain, call Safe.approveHash on %s with the following hash:", _safe);
console.logBytes32(hash);

console.log("---\nData to sign:");
Expand All @@ -79,18 +79,18 @@ abstract contract MultisigBase is CommonBase {
console.log("###############################");
}

function _checkSignatures(IGnosisSafe _safe, IMulticall3.Call3[] memory _calls, bytes memory _signatures)
function _checkSignatures(address _safe, IMulticall3.Call3[] memory _calls, bytes memory _signatures)
internal
view
{
bytes memory data = abi.encodeCall(IMulticall3.aggregate3, (_calls));
bytes32 hash = _getTransactionHash(_safe, data);
_signatures = Signatures.prepareSignatures(_safe, hash, _signatures);

_safe.checkSignatures({dataHash: hash, data: data, signatures: _signatures});
IGnosisSafe(_safe).checkSignatures({dataHash: hash, data: data, signatures: _signatures});
}

function _executeTransaction(IGnosisSafe _safe, IMulticall3.Call3[] memory _calls, bytes memory _signatures)
function _executeTransaction(address _safe, IMulticall3.Call3[] memory _calls, bytes memory _signatures)
internal
returns (Vm.AccountAccess[] memory, Simulation.Payload memory)
{
Expand All @@ -99,7 +99,7 @@ abstract contract MultisigBase is CommonBase {
_signatures = Signatures.prepareSignatures(_safe, hash, _signatures);

bytes memory simData = _execTransationCalldata(_safe, data, _signatures);
Simulation.logSimulationLink({_to: address(_safe), _from: msg.sender, _data: simData});
Simulation.logSimulationLink({_to: _safe, _from: msg.sender, _data: simData});

vm.startStateDiffRecording();
bool success = _execTransaction(_safe, data, _signatures);
Expand All @@ -111,24 +111,24 @@ abstract contract MultisigBase is CommonBase {
// data about the state diff before broadcasting the transaction.
Simulation.Payload memory simPayload = Simulation.Payload({
from: msg.sender,
to: address(_safe),
to: _safe,
data: simData,
stateOverrides: new Simulation.StateOverride[](0)
});
return (accesses, simPayload);
}

function _getTransactionHash(IGnosisSafe _safe, IMulticall3.Call3[] memory calls) internal view returns (bytes32) {
function _getTransactionHash(address _safe, IMulticall3.Call3[] memory calls) internal view returns (bytes32) {
bytes memory data = abi.encodeCall(IMulticall3.aggregate3, (calls));
return _getTransactionHash(_safe, data);
}

function _getTransactionHash(IGnosisSafe _safe, bytes memory _data) internal view returns (bytes32) {
function _getTransactionHash(address _safe, bytes memory _data) internal view returns (bytes32) {
return keccak256(_encodeTransactionData(_safe, _data));
}

function _encodeTransactionData(IGnosisSafe _safe, bytes memory _data) internal view returns (bytes memory) {
return _safe.encodeTransactionData({
function _encodeTransactionData(address _safe, bytes memory _data) internal view returns (bytes memory) {
return IGnosisSafe(_safe).encodeTransactionData({
to: MULTICALL3_ADDRESS,
value: 0,
data: _data,
Expand All @@ -142,13 +142,13 @@ abstract contract MultisigBase is CommonBase {
});
}

function _execTransationCalldata(IGnosisSafe _safe, bytes memory _data, bytes memory _signatures)
function _execTransationCalldata(address _safe, bytes memory _data, bytes memory _signatures)
internal
pure
returns (bytes memory)
{
return abi.encodeCall(
_safe.execTransaction,
IGnosisSafe(_safe).execTransaction,
(
MULTICALL3_ADDRESS,
0,
Expand All @@ -164,11 +164,8 @@ abstract contract MultisigBase is CommonBase {
);
}

function _execTransaction(IGnosisSafe _safe, bytes memory _data, bytes memory _signatures)
internal
returns (bool)
{
return _safe.execTransaction({
function _execTransaction(address _safe, bytes memory _data, bytes memory _signatures) internal returns (bool) {
return IGnosisSafe(_safe).execTransaction({
to: MULTICALL3_ADDRESS,
value: 0,
data: _data,
Expand All @@ -186,7 +183,7 @@ abstract contract MultisigBase is CommonBase {
// This allows simulation of the final transaction by overriding the threshold to 1.
// State changes reflected in the simulation as a result of these overrides will
// not be reflected in the prod execution.
function _safeOverrides(IGnosisSafe _safe, address _owner)
function _safeOverrides(address _safe, address _owner)
internal
view
virtual
Expand Down
20 changes: 10 additions & 10 deletions script/universal/MultisigBuilder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ abstract contract MultisigBuilder is MultisigBase {
* used by a separate tx executor address in step 2, which doesn't have to be a signer.
*/
function sign() public {
IGnosisSafe safe = IGnosisSafe(_ownerSafe());
address safe = _ownerSafe();

// Snapshot and restore Safe nonce after simulation, otherwise the data logged to sign
// would not match the actual data we need to sign, because the simulation
Expand All @@ -73,7 +73,7 @@ abstract contract MultisigBuilder is MultisigBase {
_postCheck(accesses, simPayload);

// Restore the original nonce.
vm.store(address(safe), SAFE_NONCE_SLOT, bytes32(_nonce));
vm.store(safe, SAFE_NONCE_SLOT, bytes32(_nonce));

_printDataToSign(safe, calls);
}
Expand All @@ -85,7 +85,7 @@ abstract contract MultisigBuilder is MultisigBase {
* This allow transactions to be pre-signed and stored safely before execution.
*/
function verify(bytes memory _signatures) public view {
_checkSignatures(IGnosisSafe(_ownerSafe()), _buildCalls(), _signatures);
_checkSignatures(_ownerSafe(), _buildCalls(), _signatures);
}

/**
Expand All @@ -97,8 +97,8 @@ abstract contract MultisigBuilder is MultisigBase {
* Differs from `run` in that you can override the safe nonce for simulation purposes.
*/
function simulate(bytes memory _signatures) public {
IGnosisSafe safe = IGnosisSafe(_ownerSafe());
vm.store(address(safe), SAFE_NONCE_SLOT, bytes32(_getNonce(safe)));
address safe = _ownerSafe();
vm.store(safe, SAFE_NONCE_SLOT, bytes32(_getNonce(safe)));

(Vm.AccountAccess[] memory accesses, Simulation.Payload memory simPayload) =
_executeTransaction(safe, _buildCalls(), _signatures);
Expand All @@ -119,7 +119,7 @@ abstract contract MultisigBuilder is MultisigBase {
function run(bytes memory _signatures) public {
vm.startBroadcast();
(Vm.AccountAccess[] memory accesses, Simulation.Payload memory simPayload) =
_executeTransaction(IGnosisSafe(_ownerSafe()), _buildCalls(), _signatures);
_executeTransaction(_ownerSafe(), _buildCalls(), _signatures);
vm.stopBroadcast();

_postRun(accesses, simPayload);
Expand All @@ -138,7 +138,7 @@ abstract contract MultisigBuilder is MultisigBase {
return true;
}

function _simulateForSigner(IGnosisSafe _safe, IMulticall3.Call3[] memory _calls)
function _simulateForSigner(address _safe, IMulticall3.Call3[] memory _calls)
internal
returns (Vm.AccountAccess[] memory, Simulation.Payload memory)
{
Expand All @@ -147,17 +147,17 @@ abstract contract MultisigBuilder is MultisigBase {
Simulation.StateOverride[] memory overrides = _overrides(_safe);

bytes memory txData = _execTransationCalldata(_safe, data, Signatures.genPrevalidatedSignature(msg.sender));
Simulation.logSimulationLink({_to: address(_safe), _data: txData, _from: msg.sender, _overrides: overrides});
Simulation.logSimulationLink({_to: _safe, _data: txData, _from: msg.sender, _overrides: overrides});

// Forge simulation of the data logged in the link. If the simulation fails
// we revert to make it explicit that the simulation failed.
Simulation.Payload memory simPayload =
Simulation.Payload({to: address(_safe), data: txData, from: msg.sender, stateOverrides: overrides});
Simulation.Payload({to: _safe, data: txData, from: msg.sender, stateOverrides: overrides});
Vm.AccountAccess[] memory accesses = Simulation.simulateFromSimPayload(simPayload);
return (accesses, simPayload);
}

function _overrides(IGnosisSafe _safe) internal view returns (Simulation.StateOverride[] memory) {
function _overrides(address _safe) internal view returns (Simulation.StateOverride[] memory) {
Simulation.StateOverride[] memory simOverrides = _simulationOverrides();
Simulation.StateOverride[] memory overrides = new Simulation.StateOverride[](1 + simOverrides.length);
overrides[0] = _safeOverrides(_safe, msg.sender);
Expand Down
Loading