-
Notifications
You must be signed in to change notification settings - Fork 169
chore: validate submission accounts on deserialization #4450
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?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -321,7 +321,7 @@ struct SolverConfig { | |
| /// via EIP-7702 delegation. When non-empty, enables parallel submission | ||
| /// with one lane per account. | ||
| #[serde(default)] | ||
| submission_accounts: Vec<Account>, | ||
| submission_accounts: SubmissionAccounts, | ||
|
|
||
| /// Maximum number of solutions the driver proposes to the autopilot per | ||
| /// auction. Defaults to 1 (only the best-scoring solution). Values > 1 | ||
|
|
@@ -365,6 +365,52 @@ enum Account { | |
| Address(eth::Address), | ||
| } | ||
|
|
||
| /// Accounts used for parallel EIP-7702 settlement submission. Every account is | ||
| /// guaranteed to be a signer (never [`Account::Address`]), because address-only | ||
| /// accounts cannot sign the delegated settlement transactions. The invariant is | ||
| /// enforced on deserialization, so any constructed value is valid by | ||
| /// construction. | ||
| #[derive(Debug, Default)] | ||
| struct SubmissionAccounts(Vec<Account>); | ||
|
|
||
| impl SubmissionAccounts { | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| fn new(accounts: Vec<Account>) -> Result<Self, InvalidSubmissionAccounts> { | ||
|
Contributor
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. Document the check/invariant + add two tests pass/fail for this |
||
| if accounts | ||
| .iter() | ||
| .any(|account| matches!(account, Account::Address(_))) | ||
| { | ||
| return Err(InvalidSubmissionAccounts); | ||
| } | ||
| Ok(Self(accounts)) | ||
| } | ||
|
|
||
| fn into_inner(self) -> Vec<Account> { | ||
| self.0 | ||
| } | ||
| } | ||
|
|
||
| #[derive(Debug)] | ||
| struct InvalidSubmissionAccounts; | ||
|
|
||
| impl std::fmt::Display for InvalidSubmissionAccounts { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| f.write_str( | ||
| "EIP-7702 submission accounts must be signers; address-only accounts cannot sign \ | ||
| delegated settlement transactions", | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| impl<'de> Deserialize<'de> for SubmissionAccounts { | ||
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
| where | ||
| D: Deserializer<'de>, | ||
| { | ||
| let accounts = Vec::<Account>::deserialize(deserializer)?; | ||
| Self::new(accounts).map_err(serde::de::Error::custom) | ||
| } | ||
| } | ||
|
|
||
|
Comment on lines
+404
to
+413
This comment was marked as outdated.
Sorry, something went wrong. |
||
| #[serde_as] | ||
| #[derive(Debug, Deserialize, Default)] | ||
| #[serde(rename_all = "kebab-case", deny_unknown_fields)] | ||
|
|
@@ -1082,4 +1128,46 @@ mod tests { | |
| _ => panic!("expected Alloy variant as default"), | ||
| } | ||
| } | ||
|
|
||
| /// Mirrors how `submission-accounts` is declared on `SolverConfig` so the | ||
| /// tests exercise the real TOML deserialization path. | ||
| #[derive(Debug, Deserialize)] | ||
| #[serde(rename_all = "kebab-case")] | ||
| struct SubmissionAccountsConfig { | ||
| #[serde(default)] | ||
| submission_accounts: SubmissionAccounts, | ||
| } | ||
|
|
||
| #[test] | ||
| fn submission_accounts_default_when_omitted() { | ||
| let config: SubmissionAccountsConfig = toml::from_str("").unwrap(); | ||
|
|
||
| assert!(config.submission_accounts.into_inner().is_empty()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn submission_accounts_accepts_signers() { | ||
| let config: SubmissionAccountsConfig = toml::from_str( | ||
| r#" | ||
| submission-accounts = [ | ||
| "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", | ||
| ] | ||
| "#, | ||
|
Comment on lines
+1151
to
+1155
Contributor
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. can we match the indentation on start and end? #" and "# |
||
| ) | ||
| .unwrap(); | ||
|
|
||
| assert_eq!(config.submission_accounts.into_inner().len(), 1); | ||
| } | ||
|
|
||
| #[test] | ||
| fn submission_accounts_rejects_address_only_on_deserialization() { | ||
| let err = toml::from_str::<SubmissionAccountsConfig>( | ||
| r#" | ||
| submission-accounts = ["0x0000000000000000000000000000000000000002"] | ||
| "#, | ||
| ) | ||
| .unwrap_err(); | ||
|
|
||
| assert!(err.to_string().contains("must be signers")); | ||
| } | ||
| } | ||
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.
Not relevant here