Skip to content
Open
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
1 change: 1 addition & 0 deletions crates/driver/src/infra/config/file/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ pub async fn load(chain: Chain, path: &Path) -> infra::Config {
submission_accounts: join_all(
solver_config
.submission_accounts
.into_inner()
.into_iter()
.map(|acc| load_account(acc, config.chain_id)),
)
Expand Down
90 changes: 89 additions & 1 deletion crates/driver/src/infra/config/file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Comment on lines +370 to +372
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// accounts cannot sign the delegated settlement transactions. The invariant is
/// enforced on deserialization, so any constructed value is valid by
/// construction.
/// accounts cannot sign the delegated settlement transactions.

Not relevant here

#[derive(Debug, Default)]
struct SubmissionAccounts(Vec<Account>);

impl SubmissionAccounts {

This comment was marked as resolved.

fn new(accounts: Vec<Account>) -> Result<Self, InvalidSubmissionAccounts> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

#[serde_as]
#[derive(Debug, Deserialize, Default)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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"));
}
}
Loading