feat: add solver 7702 delegate#4430
Conversation
There was a problem hiding this comment.
Code Review
This pull request replaces the CowSettlementForwarder contract with Solver7702Delegate to support EIP-7702 parallel settlement submissions. Key changes include the addition of the Solver7702Delegate artifact and its generated Rust bindings, a refactor of the driver's EIP-7702 setup to use deterministic CREATE2 deployment, and updates to the submission logic to handle the new delegate's fallback mechanism. The PR also adds unit tests for these components and reorganizes existing test files. I have no feedback to provide.
8af659d to
fdcb176
Compare
4dc8037 to
e94152c
Compare
kaze-cow
left a comment
There was a problem hiding this comment.
wow the changes seem very straightforward, nice work! I haven't finished running the actual code locally so give me some time but if the paralell submission test is already working thats a great sign 👍
AryanGodara
left a comment
There was a problem hiding this comment.
looks good mostly, just 2 comments. 1 med, 1 small.
Also replid to the "lets get opinion of backend team" threads 😅
…gate # Conflicts: # contracts/generated/contracts-generated/solver7702delegate/Cargo.toml # contracts/src/main.rs # crates/driver/src/domain/mempools.rs # crates/e2e/tests/e2e/parallel_settlement.rs
jmg-duarte
left a comment
There was a problem hiding this comment.
this is quite big which makes it harder to review, overall it looks ok to me but it's a bit hard to visualize the impact of the change in my head
| self.max_solutions_to_propose.get() == 1, | ||
| "solver '{}': max-solutions-to-propose > 1 requires at least one \ | ||
| submission-account (EIP-7702 parallel submission must be enabled)", |
There was a problem hiding this comment.
x == 1 & "at least one" are not the same
which one is the correct one?
There was a problem hiding this comment.
Its definitely worded wierdly but I think its referring to two different vars. if the var max-solutions-to-propose > 1, then submission-account.length > 0
There was a problem hiding this comment.
Good point. The check is about two different fields, but the message was confusing. I changed it to say that max-solutions-to-propose > 1 requires non-empty submission-accounts.
| anyhow::ensure!( | ||
| self.submission_accounts | ||
| .iter() | ||
| .all(|account| !matches!(account, Account::Address(_))), |
There was a problem hiding this comment.
same thing but there's no ! almost hiding
| .all(|account| !matches!(account, Account::Address(_))), | |
| .any(|account| matches!(account, Account::Address(_))), |
| PrivateKeySigner::from_bytes(&b256!( | ||
| "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" | ||
| )) | ||
| .unwrap() |
There was a problem hiding this comment.
just in case: leave a note explaining NOT to use this as a production key
| std::time::Duration, | ||
| }; | ||
|
|
||
| const CREATE2_DEPLOYER: Address = address!("4e59b44847b379578588920cA78FbF26c0B4956C"); |
There was a problem hiding this comment.
you can use the value from the driver
| } | ||
|
|
||
| fn solver_delegate_address(callers: [Address; 2]) -> Address { | ||
| let mut approved_callers = [Address::ZERO; 5]; |
There was a problem hiding this comment.
use MAX_ALLOWED_CALLERS (or what was the name of the var)
| /// Ensure EIP-7702 delegate deployment and solver delegation are set up for all | ||
| /// solvers with parallel submission accounts. Called once at driver startup. |
There was a problem hiding this comment.
the error descriptions disappeared
| let forwarder = config.forwarder_contract.ok_or_else(|| { | ||
| anyhow::anyhow!( | ||
| "solver {}: submission_accounts configured but forwarder_contract missing", | ||
| config.name | ||
| ) | ||
| })?; |
There was a problem hiding this comment.
thought that this had been removed already
There was a problem hiding this comment.
This check is now moved out of EIP-7702 setup and into config validation, where it belongs.
| fn delegation_status(code: &[u8]) -> DelegationStatus { | ||
| if code.is_empty() { | ||
| DelegationStatus::Empty | ||
| } else if code.len() == 23 && code.starts_with(&DELEGATION_PREFIX) { |
| Empty, | ||
| DelegatedTo(Address), | ||
| OtherCode, |
| OtherCode, | ||
| } | ||
|
|
||
| fn delegation_status(code: &[u8]) -> DelegationStatus { |
There was a problem hiding this comment.
take it or leave it: maybe?
| fn delegation_status(code: &[u8]) -> DelegationStatus { | |
| impl DelegationStatus { | |
| fn from_code(code: &[u8]) -> Self { | |
| ... | |
| } | |
| } |
| // Check delegation status. | ||
| let code = provider.get_code_at(solver_address).await?; | ||
| let needs_delegation = !is_delegated_to(&code, forwarder); | ||
| let mut approved_callers = [Address::ZERO; MAX_APPROVED_CALLERS]; |
There was a problem hiding this comment.
Worth rejecting duplicates and Address::ZERO here. Both change (or alias with the padding for) the CREATE2 target with no signal to the operator. A typo in the TOML would silently produce a working-but-wrong delegate.
anyhow::ensure!(
callers.iter().all(|a| *a != Address::ZERO),
"submission accounts cannot include the zero address"
);
let mut seen = std::collections::HashSet::with_capacity(callers.len());
anyhow::ensure!(
callers.iter().all(|a| seen.insert(*a)),
"submission accounts must be unique"
);Also worth a comment noting that reordering submission accounts in the TOML changes the delegate address. The test asserts it, but operators won't read the test.
There was a problem hiding this comment.
Agreed, added these checks
| .github( | ||
| "Solver7702Delegate", | ||
| // TODO(post-audit): bump to the audited commit hash or release tag. | ||
| "cowprotocol/solver-7702-delegate/acde4b0cd452d208c2ea0f0fbbf83e3698decbf8/out/\ |
There was a problem hiding this comment.
The vendor URL is at commit acde4b0c..., but the committed JSON's sourceCommit is 4273853b.... Re-running the vendor command would pull a different artifact and overwrite the checked-in one. Please pin them to the same commit (the post-audit TODO can update both at once).
There was a problem hiding this comment.
Thanks, I did not realize out folder needed to be exposed for this to work, so now that it's exposed I've pinned the commit to the latest one.
| "CREATE2 deployer {CREATE2_DEPLOYER:?} has unexpected code", | ||
| ); | ||
|
|
||
| let tx_sender = config.account.address(); |
There was a problem hiding this comment.
This recovery path pays gas from the solver EOA. If the operator only funds submission accounts (reasonable setup), startup fails with a confusing OOF error. No auth is needed in DeployOnly mode, so a submission account could send the CREATE2 deploy instead. Non-blocking.
There was a problem hiding this comment.
Good point. In DeployOnly mode the deploy tx can be sent from a submission account, so I changed it to use the first submission account when available.
| } | ||
|
|
||
| impl Config { | ||
| fn validate(&self) -> Result<()> { |
There was a problem hiding this comment.
The submission-accounts-must-be-signers check lives in Config::validate, but the main-account-must-be-signer check is over in eip7702::setup. Both are the same kind of EIP-7702 precondition. Moving the main-account check into validate would catch misconfig at construction. Minor.
There was a problem hiding this comment.
Agreed. I moved the main-account-must-be-signer check into Config::validate, next to the submission account signer validation.
kaze-cow
left a comment
There was a problem hiding this comment.
my feedback was addressed or superseded by other feedbacks. in other news, I was able to test with the playground and verify the wholistic change is working as expected, so looks good to me 👍
# Description This PR removes the old `CowSettlementForwarder` implementation and its config surface. The forwarder is being replaced by `Solver7702Delegate` in the follow-up PR. Removing the old contract, artifact, bindings, and explicit `forwarder-contract` config first keeps the stack easier to review: this PR is only the cleanup of the old path, while cowprotocol#4430 introduces the new delegate behavior. # Changes * Remove `CowSettlementForwarder.sol` and its artifact. * Remove generated `cowsettlementforwarder` bindings and facade wiring. * Remove `forwarder_contract` / `forwarder-contract` from driver config loading and solver config. * Remove e2e setup fields and test code that deployed or passed a forwarder address. * Keep the replacement `Solver7702Delegate` implementation for the follow-up PR. ## How to test This PR is intended to be validated together with cowprotocol#4430, which adds the replacement implementation. 1. `just generate-contracts` 2. `just fmt --check` 3. `just clippy` 4. `just test-unit`
|
Thinking that It says that |
Description
This PR adds the new
Solver7702Delegateflow for EIP-7702 parallel settlement submission.Solver7702Delegateis also much more efficient and secure that its predecessor, due to a minimal attack surface. The contract itself is vendored from https://github.com/cowprotocol/solver-7702-delegateChanges
Solver7702Delegateartifact and generated Rust bindings.Solver7702Delegateinto contract generation and the contracts facade.forward(target, data)calldata wrapping with the new delegate calldata formattargetAddress || targetCalldata.How to test
just generate-contractsjust fmt --checkjust clippyjust test-unitjust test-e2e parallel_settlement