-
Notifications
You must be signed in to change notification settings - Fork 0
feat: setup #2
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
feat: setup #2
Changes from all commits
58f2f5d
e75ef0d
7ea2f7b
cff45c1
87e139b
6aff2a5
76fd6ff
504741f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| FORK_RPC_URL= | ||
| BASE_RPC_URL= | ||
| COW_HISTORICAL_TX_HASHES= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,8 @@ | ||
| # Contract Template | ||
| # Solver7702Delegate | ||
|
|
||
| Template for creating new smart contract projects. | ||
| `Solver7702Delegate` is a minimal ERC-7702 delegation target for CoW Protocol solvers. It lets a solver keep using its existing solver EOA while allowing a fixed set of auxiliary EOAs to submit transactions through that solver EOA. The main benefit is parallel settlement submission: auxiliary EOAs provide independent nonce lanes, while downstream contracts still see the solver EOA as `msg.sender`, which keeps the authorization clean. | ||
|
|
||
| This project is meant to be used as a templated during the creation of new Github repositories (will show in the `Create a new repository > Configuration > Start with a template` selector). | ||
|
|
||
| It will contain some useful configuration files and scripts, that can be used also with existing projects (manually copied). | ||
| Read more about the initiative [here](https://www.notion.so/cownation/Solver7702Delegate-Design-Doc-3588da5f04ca80a1b521c436abf17724). | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. linking to a private notion on a public repo's readme seems a bit wierd. not sure if we want this here or not.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, I feel like it should be there for any internal integrators. |
||
|
|
||
| ## Usage | ||
|
|
||
|
|
@@ -28,6 +26,25 @@ If specific features are needed (like PUSH0 in 0.8.20 for gas optimizations or t | |
| just test | ||
| ``` | ||
|
|
||
| #### Replaying Your Own Historical Transactions | ||
|
|
||
| The fork test `test_fork_historicalTransaction_directVsDelegated_userSuppliedTxHashes` lets you replay your own batch transactions through the delegate. | ||
|
|
||
| Set: | ||
|
|
||
| - `FORK_RPC_URL` to the RPC URL you want Foundry to fork from. | ||
| - `COW_HISTORICAL_TX_HASHES` to a comma-separated list of transaction hashes. | ||
|
|
||
| The supplied transaction hashes just need to exist on that network, and the RPC must support the historical state needed by `vm.rollFork(txHash)`. | ||
|
|
||
| Example: | ||
|
|
||
| ```shell | ||
| FORK_RPC_URL=<your_rpc_url> \ | ||
| COW_HISTORICAL_TX_HASHES=0xabc...,0xdef... \ | ||
| just test --match-test test_fork_historicalTransaction_directVsDelegated_userSuppliedTxHashes | ||
| ``` | ||
|
|
||
| ### Format | ||
|
|
||
| ```shell | ||
|
|
@@ -93,8 +110,35 @@ just snapshot | |
|
|
||
| ### Deploy | ||
|
|
||
| The deploy script reads up to five approved caller addresses from `APPROVED_CALLERS`. | ||
| If fewer than five addresses are needed, omit the rest. | ||
|
|
||
| ```shell | ||
| export APPROVED_CALLERS=<approved_caller_0>,<approved_caller_1> | ||
|
|
||
| just forge script script/DeploySolver7702Delegate.s.sol:DeploySolver7702Delegate \ | ||
| --rpc-url <your_rpc_url> \ | ||
| --private-key <your_private_key> \ | ||
| --broadcast | ||
| ``` | ||
|
|
||
| Deployments use `CREATE2` with a zero salt by default. To use a different salt, pass a `bytes32` value: | ||
|
|
||
| ```shell | ||
| export SALT=<bytes32_salt> | ||
|
|
||
| just forge script script/DeploySolver7702Delegate.s.sol:DeploySolver7702Delegate \ | ||
| --rpc-url <your_rpc_url> \ | ||
| --private-key <your_private_key> \ | ||
| --broadcast | ||
| ``` | ||
|
|
||
| To simulate the deployment and inspect the computed address, run the same command without `--broadcast`: | ||
|
|
||
| ```shell | ||
| forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key> | ||
| just forge script script/DeploySolver7702Delegate.s.sol:DeploySolver7702Delegate \ | ||
| --rpc-url <your_rpc_url> \ | ||
| --private-key <your_private_key> | ||
| ``` | ||
|
|
||
| ## New project creation checklist | ||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| // SPDX-License-Identifier: MIT OR Apache-2.0 | ||
| pragma solidity ^0.8.34; | ||
|
|
||
| import {Script} from "forge-std/Script.sol"; | ||
|
|
||
| import {Solver7702Delegate} from "../src/Solver7702Delegate.sol"; | ||
|
|
||
| /// @title DeploySolver7702Delegate | ||
| /// @author CoW Foundation | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in the past we have used https://github.com/cowprotocol/euler-integration-contracts/blob/master/src/CowWrapper.sol#L16 (Other contracts have used "CoW Swap Developers" or CoW Developers, so at the very least lets not add a whole nother author to the mix)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As per cowprotocol/contracts-template#20 and legal recommendations, CoW Foundation is the name we should use going forward. |
||
| /// @notice Deploys Solver7702Delegate. | ||
| contract DeploySolver7702Delegate is Script { | ||
| uint256 internal constant APPROVED_CALLERS_LENGTH = 5; | ||
|
|
||
| error InvalidApprovedCallersLength(uint256 length); | ||
|
|
||
| /// @notice Deploys Solver7702Delegate using approved caller environment variables. | ||
| /// @return solver7702Delegate The deployed Solver7702Delegate contract. | ||
| function run() external returns (Solver7702Delegate solver7702Delegate) { | ||
| vm.startBroadcast(); | ||
| solver7702Delegate = new Solver7702Delegate{salt: vm.envOr("SALT", bytes32(0))}(_getApprovedCallers()); | ||
| vm.stopBroadcast(); | ||
| } | ||
|
|
||
| /// @notice Reads the approved caller addresses from environment variables. | ||
| /// @return approvedCallers The approved caller addresses. | ||
| function _getApprovedCallers() internal view returns (address[APPROVED_CALLERS_LENGTH] memory approvedCallers) { | ||
| address[] memory rawApprovedCallers = vm.envAddress("APPROVED_CALLERS", ","); | ||
|
|
||
| if (rawApprovedCallers.length == 0 || rawApprovedCallers.length > APPROVED_CALLERS_LENGTH) { | ||
| revert InvalidApprovedCallersLength(rawApprovedCallers.length); | ||
| } | ||
|
|
||
| for (uint256 i; i < rawApprovedCallers.length; ++i) { | ||
| approvedCallers[i] = rawApprovedCallers[i]; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| { | ||
| "detectors_to_exclude": "solc-version", | ||
| "detectors_to_exclude": "solc-version,naming-convention,assembly", | ||
|
igorroncevic marked this conversation as resolved.
|
||
| "filter_paths": "/(lib|test|script)/" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "historical tx - swap and bridge order - delegated call": "949703", | ||
| "historical tx - swap and bridge order - direct call": "945548" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "approved caller empty target payload - success - forwards": "24507", | ||
| "approved caller short calldata - success - receives ETH": "21760", | ||
| "approved caller short calldata - success - returns empty": "21760", | ||
| "approved caller target empty revert - reverts - bubbles empty data": "24861", | ||
| "approved caller target payload - success - forwards": "31258", | ||
| "approved caller target return data - success - bubbles return data": "25069", | ||
| "approved caller target revert data - reverts - bubbles non-empty data": "25738", | ||
| "unauthorized caller - success - receives ETH": "21232", | ||
| "unauthorized caller no value - reverts - unauthorized": "21930" | ||
| } |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| // SPDX-License-Identifier: MIT OR Apache-2.0 | ||
| pragma solidity ^0.8.34; | ||
|
|
||
| /// @title Solver7702Delegate | ||
| /// @author CoW Foundation | ||
| /// @notice ERC-7702 delegation target for solver EOAs | ||
| contract Solver7702Delegate { | ||
| /// @notice Error thrown when a caller is unauthorized | ||
| error Unauthorized(address sender); | ||
|
|
||
| /// @notice Address of the first approved caller | ||
| address private immutable APPROVED_CALLER_0; | ||
|
|
||
| /// @notice Address of the second approved caller | ||
| address private immutable APPROVED_CALLER_1; | ||
|
|
||
| /// @notice Address of the third approved caller | ||
| address private immutable APPROVED_CALLER_2; | ||
|
|
||
| /// @notice Address of the fourth approved caller | ||
| address private immutable APPROVED_CALLER_3; | ||
|
|
||
| /// @notice Address of the fifth approved caller | ||
| address private immutable APPROVED_CALLER_4; | ||
|
Comment on lines
+11
to
+24
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I left these as However, there would need to be some additional assumptions in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is a major issue because if the contract is deployed with Most ethereum libraries provide some sort of a function to predict the create2 address, such as To then know which addresses are authorized, the list can be read from the |
||
|
|
||
| /// @notice Constructor to initialize the approved callers | ||
| /// @param approvedCallers The addresses of the approved callers | ||
| constructor(address[5] memory approvedCallers) { | ||
| APPROVED_CALLER_0 = approvedCallers[0]; | ||
| APPROVED_CALLER_1 = approvedCallers[1]; | ||
| APPROVED_CALLER_2 = approvedCallers[2]; | ||
| APPROVED_CALLER_3 = approvedCallers[3]; | ||
| APPROVED_CALLER_4 = approvedCallers[4]; | ||
| } | ||
|
|
||
| /// @notice Fallback function to handle calls to the delegate | ||
| /// @dev Expected calldata format is `bytes20(target) || targetCalldata`. | ||
| fallback() external payable { | ||
| // Possibly short circuit by recognizing one of the approved callers | ||
| if ( | ||
| msg.sender == APPROVED_CALLER_0 || msg.sender == APPROVED_CALLER_1 || msg.sender == APPROVED_CALLER_2 | ||
| || msg.sender == APPROVED_CALLER_3 || msg.sender == APPROVED_CALLER_4 | ||
| ) return _callThrough(); | ||
|
|
||
| // Accept ETH from anyone, even if unauthorized | ||
| if (msg.value > 0) return; | ||
| revert Unauthorized(msg.sender); | ||
| } | ||
|
igorroncevic marked this conversation as resolved.
|
||
|
|
||
| function _callThrough() internal { | ||
| // Receive ETH and exit when no target address is encoded. | ||
| if (msg.data.length < 20) return; | ||
|
|
||
| // Extract the first 20 bytes of calldata as the target address. | ||
| address target = address(bytes20(msg.data[0:20])); | ||
|
igorroncevic marked this conversation as resolved.
|
||
|
|
||
| assembly { | ||
| // Extract calldata in range (target, len(msg.data)). | ||
| // We take full control of memory in this inline assembly block because it will not return to Solidity code. | ||
| // This is why we overwrite the Solidity scratch pad at memory position 0. | ||
| calldatacopy(0x00, 20, sub(calldatasize(), 20)) | ||
|
|
||
| // Call the implementation | ||
| let result := | ||
| call( | ||
| gas(), // gas - forward all of it | ||
| target, // target to call | ||
| callvalue(), // value - forward all Ether | ||
| 0x00, // input offset - pointer to calldata | ||
| sub(calldatasize(), 20), // input size - length of calldata | ||
| 0x00, // output offset - read via returndatacopy below | ||
| 0x00 // output size - read via returndatacopy below | ||
| ) | ||
|
|
||
| // Copy return data into memory | ||
| returndatacopy(0x00, 0x00, returndatasize()) | ||
|
|
||
| // Handle return data, 0 = revert / 1 = success | ||
| switch result | ||
| case 0 { | ||
| revert(0x00, returndatasize()) | ||
| } | ||
| default { | ||
| return(0x00, returndatasize()) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,11 @@ | ||
| { | ||
| "rules": { | ||
| "func-name-mixedcase": "off", | ||
| "gas-strict-inequalities": "off", | ||
| "gas-small-strings": "off", | ||
| "avoid-low-level-calls": "off", | ||
| "max-states-count": "off", | ||
| "no-empty-blocks": "off", | ||
| "use-natspec": "off" | ||
| } | ||
| } |
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 of these are necessary:
"func-visibility": ["error", { "ignoreConstructors": true }]-> constructor doesn't require explicit visibility modifier since Solidity 0.7"no-complex-fallback": "off"-> we explicitly need our fallback to be complex"no-inline-assembly": "off"-> we explicitly need assembly for gas efficiencyThere 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.
id almost say that
no-inline-assemblyshould be added to the template repo because we almost end up using it for one reason or another.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 sit on that as it might not be required by other repos.