-
Notifications
You must be signed in to change notification settings - Fork 14
MultisigProposal: Support DelegateCall #96
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
base: main
Are you sure you want to change the base?
Conversation
…tion handling - Introduced a new function `getOperations` to allow customization of operations for each action in the proposal. - Updated `getCalldata` to include operations validation, ensuring consistency across targets, values, arguments, and operations arrays.
…on handling and code clarity - Removed the constant MULTISIG_BYTECODE_HASH from MultisigProposal. - Simplified the getOperations function for better readability. - Enhanced getCalldata to ensure consistent validation of targets, values, arguments, and operations. - Updated internal functions for clarity and conciseness, including _validateAction and _processStateChanges. - Improved logging for proposal actions and calldata.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds support for Safe DelegateCall operations in multisig proposals by updating constants, extending parameterization of operations, and rewriting the simulation logic to bypass signature checks during testing.
- Introduces
SAFE_CREATION_BYTECODEand a new constant address for the Safe multisend contract - Adds
getOperationshook and includes operations ingetCalldata - Rewrites
_simulateActionsto deploy a modified Safe and callexecTransactionwith delegatecall support
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| utils/Constants.sol | Replaced MULTISEND_BYTECODE, removed SAFE_BYTECODE, added SAFE_CREATION_BYTECODE and multisend address |
| src/proposals/Proposal.sol | Fixed duplicate Addresses import and adjusted comment placement in account-access filtering |
| src/proposals/MultisigProposal.sol | Removed old bytecode hash, added getOperations, updated getCalldata, and rewrote _simulateActions |
Comments suppressed due to low confidence (2)
src/proposals/MultisigProposal.sol:44
- [nitpick] The error message
"Array lengths mismatch"now covers four arrays (targets, values, arguments, operations). Consider updating it to something like"Targets, values, arguments, and operations length mismatch"for clarity.
require(
src/proposals/MultisigProposal.sol:85
- [nitpick] The variable name
addris quite generic; using a more descriptive name likedeployedSafeorsafeClonecan improve readability by clarifying its purpose.
address addr;
|
|
||
| bytes public constant SAFE_BYTECODE = | ||
| hex"608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea2646970667358221220d1429297349653a4918076d650332de1a1068c5f3e07c5c82360c277770b955264736f6c63430007060033"; | ||
| address constant SAFE_MULTISEND_COTNRACT = |
Copilot
AI
Jul 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a typo in the constant name SAFE_MULTISEND_COTNRACT. It should likely be SAFE_MULTISEND_CONTRACT.
| address constant SAFE_MULTISEND_COTNRACT = | |
| address constant SAFE_MULTISEND_CONTRACT = |
| for (uint256 i = 0; i < actionsLength; i++) { | ||
| operations[i] = 0; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
free memory slots are always 0, but this is client side code so ig it doesn't matter
| 0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761; | ||
|
|
||
| // This is a modified Safe that overrides the checkNSignatures function | ||
| bytes public constant SAFE_CREATION_BYTECODE = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since you're not using the constructor to attach an address, we can simplify to the runtime bytecode. then you won't need the assembly to create the contract in the first place
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the creation bytecode, this contract (safe with checkNSignatures modified) is not deployed. I've modified the SafeL2 contract, compiled it on my machine, and grabbed the creation bytecode. I've never deployed. Would you like for me to deploy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you don't need to deploy it to get it's runtime bytecode. you should be able to get that in solidity by just deploying the contract and logging out its bytecode
| bytes memory bytecode = Constants.SAFE_CREATION_BYTECODE; | ||
| address addr; | ||
| /// @solidity memory-safe-assembly | ||
| assembly { | ||
| addr := create(0, add(bytecode, 0x20), mload(bytecode)) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer etching runtime bytecode of this safe with the checkNSignatures function stubbed out instead of needing to create it every time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
then no need to deploy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How that would work without deploying. Etching only works with deployed bytecode, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
grab the runtime bytecode from an instance you deploy on your local machine in a foundry test, log it out to the terminal, copy and paste that here, rename from SAFE_CREATION_BYTECODE to RUNTIME_BYTECODE
| bytes callData; | ||
| } | ||
|
|
||
| /// @notice get operations for each action, override this to provide custom operations |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you walk me through what an example flow using this would look like? it seems like this flow would be really counter-intuitive because a user would need to:
- specify the calls as they normally would using FPS - nothing amiss here
- override this function and write solidity that looks something like this to index which calls you gathered up should be calls and which should be delegate calls.
/// first call is delegatecall
operations[0] = 1;
/// second call is delegatecall
operations[1] = 1;
/// third call is call
operations[2] = 0;
/// fourth call is delegatecall
operations[3] = 1;
/// fifth call is call
operations[4] = 0;I think we should brainstorm cleaner and more intuitive ways of writing this so that other users will be able to easily understand what is going on here.
Alternative approach:
- all or nothing, have a method where a user must specify if all calls are either regular or delegate calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all or nothing, have a method where a user must specify if all calls are either regular or delegate calls.
I don't like that because I might want to create a batch transaction that first enables a module (using delegatecall) and then immediately performs another action (regular call)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok why don't we just make a flag that is isDelegateCall, when you have a delegatecall operations you flip the flag to true, otherwise it just does regular calls.
This adds support for Safe DelegateCall operations.
Changes:
operationfield_simulateActions:This function was etching the multisend contract as safe but that doesn't support delegate calls because the safe bytecode changed to the multisend contract.
The SAFE_CREATION_BYTECODE contains the SafeL2 bytecode with a modification to the checkNSignatures bypassing signature checks.