diff --git a/server/crates/arbiter-server/src/actors/proposal_manager.rs b/server/crates/arbiter-server/src/actors/proposal_manager.rs index d66b422..f97ed65 100644 --- a/server/crates/arbiter-server/src/actors/proposal_manager.rs +++ b/server/crates/arbiter-server/src/actors/proposal_manager.rs @@ -41,7 +41,10 @@ impl ProposalKind { pub fn encode_payload(&self) -> Vec { match self { Self::ApproveSdkClient { client_id } => client_id.to_be_bytes().to_vec(), - Self::GrantWalletAccess { wallet_id, client_id } => { + Self::GrantWalletAccess { + wallet_id, + client_id, + } => { let mut buf = Vec::with_capacity(8); buf.extend_from_slice(&wallet_id.to_be_bytes()); buf.extend_from_slice(&client_id.to_be_bytes()); @@ -49,8 +52,7 @@ impl ProposalKind { } Self::ApproveServerUpdate => vec![], Self::ReplaceOperator { new_pubkey } => { - #[expect(clippy::cast_possible_truncation, clippy::as_conversions, reason = "pubkey is always 32 bytes")] - let len = new_pubkey.len() as u32; + let len = u32::try_from(new_pubkey.len()).expect("pubkey len fits in u32"); let mut buf = Vec::with_capacity(4 + new_pubkey.len()); buf.extend_from_slice(&len.to_be_bytes()); buf.extend_from_slice(new_pubkey); @@ -153,7 +155,11 @@ pub struct ProposalManager { } impl ProposalManager { - pub const fn new(db: db::DatabasePool, vault: ActorRef, evm: ActorRef) -> Self { + pub const fn new( + db: db::DatabasePool, + vault: ActorRef, + evm: ActorRef, + ) -> Self { Self { db, vault, evm } } } @@ -162,10 +168,7 @@ impl kameo::Actor for ProposalManager { type Args = Self; type Error = (); - async fn on_start( - args: Self::Args, - actor_ref: ActorRef, - ) -> Result { + async fn on_start(args: Self::Args, actor_ref: ActorRef) -> Result { let weak = actor_ref.downgrade(); tokio::spawn(async move { loop { @@ -429,9 +432,10 @@ impl ProposalManager { ProposalKind::ApproveSdkClient { client_id } => { self.execute_approve_sdk_client(client_id).await } - ProposalKind::GrantWalletAccess { wallet_id, client_id } => { - self.execute_grant_wallet_access(wallet_id, client_id).await - } + ProposalKind::GrantWalletAccess { + wallet_id, + client_id, + } => self.execute_grant_wallet_access(wallet_id, client_id).await, ProposalKind::ApproveServerUpdate => Ok(()), ProposalKind::ReplaceOperator { new_pubkey } => { self.execute_replace_operator(new_pubkey).await @@ -443,12 +447,17 @@ impl ProposalManager { self.execute_approve_persistent_grant(payload_bytes).await } ProposalKind::ApproveOneOffTransaction { payload_bytes } => { - self.execute_approve_one_off_transaction(proposal.id, payload_bytes).await + self.execute_approve_one_off_transaction(proposal.id, payload_bytes) + .await } } } - async fn execute_grant_wallet_access(&self, wallet_id: i32, client_id: i32) -> Result<(), Error> { + async fn execute_grant_wallet_access( + &self, + wallet_id: i32, + client_id: i32, + ) -> Result<(), Error> { use crate::db::models::EvmWalletId; let mut conn = self.db.get().await.map_err(Error::DatabaseConnection)?; @@ -481,7 +490,10 @@ impl ProposalManager { reason = "signature must match other execute_* methods" )] fn execute_update_shamir_parameters(&self, new_n: u8) -> Result<(), Error> { - warn!(new_n, "UpdateShamirParameters approved; Shamir re-keying must be performed out-of-band"); + warn!( + new_n, + "UpdateShamirParameters approved; Shamir re-keying must be performed out-of-band" + ); Ok(()) } @@ -490,7 +502,6 @@ impl ProposalManager { proposal_id: i32, payload_bytes: Vec, ) -> Result<(), Error> { - use arbiter_proto::proto::operator::governance::ApproveOneOffTransactionPayload; use crate::actors::evm::ClientSignTransaction; use crate::db::models::NewProposalResult; use alloy::{ @@ -498,6 +509,7 @@ impl ProposalManager { eips::eip2930::AccessList, primitives::{Address, Bytes, TxKind, U256}, }; + use arbiter_proto::proto::operator::governance::ApproveOneOffTransactionPayload; use prost::Message as _; let p = ApproveOneOffTransactionPayload::decode(payload_bytes.as_slice()) @@ -520,7 +532,9 @@ impl ProposalManager { p.max_priority_fee_per_gas .as_slice() .try_into() - .map_err(|_| Error::ExecutionFailed("invalid max_priority_fee_per_gas".to_owned()))?, + .map_err(|_| { + Error::ExecutionFailed("invalid max_priority_fee_per_gas".to_owned()) + })?, ), to: TxKind::Call(to), value: U256::from_be_slice(p.value.as_slice()), @@ -552,10 +566,6 @@ impl ProposalManager { } async fn execute_approve_persistent_grant(&self, payload_bytes: Vec) -> Result<(), Error> { - use arbiter_proto::proto::operator::governance::{ - ApprovePersistentGrantPayload, - approve_persistent_grant_payload::Specific, - }; use crate::{ actors::evm::OperatorCreateGrant, evm::policies::{ @@ -564,6 +574,9 @@ impl ProposalManager { }, }; use alloy::primitives::{Address, U256}; + use arbiter_proto::proto::operator::governance::{ + ApprovePersistentGrantPayload, approve_persistent_grant_payload::Specific, + }; use chrono::Duration; use prost::Message as _; @@ -573,10 +586,18 @@ impl ProposalManager { let basic = SharedGrantSettings { wallet_access_id: payload.wallet_access_id, chain: payload.chain_id, - valid_from: payload.valid_from_secs.and_then(|s| chrono::DateTime::from_timestamp(s, 0)), - valid_until: payload.valid_until_secs.and_then(|s| chrono::DateTime::from_timestamp(s, 0)), - max_gas_fee_per_gas: payload.max_gas_fee_per_gas.map(|b| U256::from_be_slice(b.as_slice())), - max_priority_fee_per_gas: payload.max_priority_fee_per_gas.map(|b| U256::from_be_slice(b.as_slice())), + valid_from: payload + .valid_from_secs + .and_then(|s| chrono::DateTime::from_timestamp(s, 0)), + valid_until: payload + .valid_until_secs + .and_then(|s| chrono::DateTime::from_timestamp(s, 0)), + max_gas_fee_per_gas: payload + .max_gas_fee_per_gas + .map(|b| U256::from_be_slice(b.as_slice())), + max_priority_fee_per_gas: payload + .max_priority_fee_per_gas + .map(|b| U256::from_be_slice(b.as_slice())), rate_limit: payload.rate_limit.map(|r| TransactionRateLimit { count: r.count, window: Duration::seconds(r.window_secs), @@ -585,22 +606,27 @@ impl ProposalManager { let grant = match payload.specific { Some(Specific::EtherTransfer(spec)) => { - let target: Vec
= spec.targets + let target: Vec
= spec + .targets .iter() .map(|b| Address::from_slice(b.as_slice())) .collect(); - let limit = spec.limit + let limit = spec + .limit .map(|l| VolumeRateLimit { max_volume: U256::from_be_slice(l.max_volume.as_slice()), window: Duration::seconds(l.window_secs), }) - .ok_or_else(|| Error::ExecutionFailed("missing ether transfer limit".to_owned()))?; + .ok_or_else(|| { + Error::ExecutionFailed("missing ether transfer limit".to_owned()) + })?; SpecificGrant::EtherTransfer(ether_transfer::Settings { target, limit }) } Some(Specific::TokenTransfer(spec)) => { let token_contract = Address::from_slice(spec.token_contract.as_slice()); let target = spec.target.map(|b| Address::from_slice(b.as_slice())); - let volume_limits: Vec = spec.volume_limits + let volume_limits: Vec = spec + .volume_limits .iter() .map(|l| VolumeRateLimit { max_volume: U256::from_be_slice(l.max_volume.as_slice()), @@ -625,11 +651,8 @@ impl ProposalManager { } async fn execute_approve_sdk_client(&self, client_id: i32) -> Result<(), Error> { + use crate::{crypto::integrity, peers::client::ClientCredentials}; use arbiter_crypto::authn; - use crate::{ - crypto::integrity, - peers::client::ClientCredentials, - }; let mut conn = self.db.get().await.map_err(Error::DatabaseConnection)?; diff --git a/server/crates/arbiter-server/src/grpc/operator/governance.rs b/server/crates/arbiter-server/src/grpc/operator/governance.rs index 840ebb2..df28e0a 100644 --- a/server/crates/arbiter-server/src/grpc/operator/governance.rs +++ b/server/crates/arbiter-server/src/grpc/operator/governance.rs @@ -54,7 +54,8 @@ async fn handle_create( }, Some(ProtoKind::ApproveServerUpdate(_)) => ProposalKind::ApproveServerUpdate, Some(ProtoKind::ReplaceOperator(p)) => ProposalKind::ReplaceOperator { - new_pubkey: p.new_pubkey, + new_pubkey: p.new_pubkey.try_into() + .map_err(|_| Status::invalid_argument("replace_operator: pubkey must be 32 bytes"))?, }, Some(ProtoKind::UpdateShamirParameters(p)) => ProposalKind::UpdateShamirParameters { #[expect(clippy::cast_possible_truncation, clippy::as_conversions, reason = "new_n is always a small operator count")] diff --git a/server/crates/arbiter-server/tests/governance.rs b/server/crates/arbiter-server/tests/governance.rs index b6fca7b..ca86e64 100644 --- a/server/crates/arbiter-server/tests/governance.rs +++ b/server/crates/arbiter-server/tests/governance.rs @@ -717,7 +717,7 @@ async fn replace_operator_inserts_identity_row() { let op_id = register_operator(&db, &signing_key.public_key()).await; let new_op_key = authn::SigningKey::generate(); - let new_pubkey = new_op_key.public_key().to_bytes().to_vec(); + let new_pubkey = new_op_key.public_key().to_bytes(); let proposal_id = actors .proposal_manager