Skip to content

Commit

Permalink
chore: better error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
chris13524 committed Jan 8, 2025
1 parent a31c001 commit 01301b1
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 29 deletions.
63 changes: 63 additions & 0 deletions crates/yttrium/src/gas_abstraction/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use {
alloy::{
contract,
rpc::types::TransactionReceipt,
transports::{RpcError, TransportErrorKind},
},
thiserror::Error,
};

#[derive(Debug, Error)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
pub enum PrepareError {
#[error("Checking account code: {0}")]
CheckingAccountCode(RpcError<TransportErrorKind>),

#[error("Getting nonce: {0}")]
GettingNonce(RpcError<TransportErrorKind>),

#[error("Creating sponsored user operation: {0}")]
CreatingSponsoredUserOp(CreateSponsoredUserOpError),
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug, Error)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
pub enum PrepareDeployError {
#[error("Sending delegation transaction: {0}")]
SendingDelegationTransaction(contract::Error),

#[error("Delegation transaction failed: {0:?}")]
DelegationTransactionFailed(TransactionReceipt),

#[error("Getting delegation transaction receipt: {0:?}")]
GettingDelegationTransactionReceipt(
alloy::providers::PendingTransactionError,
),

#[error("Creating sponsored user operation: {0}")]
CreatingSponsoredUserOp(CreateSponsoredUserOpError),
}

#[derive(Debug, Error)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
pub enum CreateSponsoredUserOpError {
#[error("Getting nonce: {0}")]
GettingNonce(contract::Error),

#[error("Getting user operation gas price: {0}")]
GettingUserOperationGasPrice(eyre::Report),

#[error("Sponsoring user operation: {0}")]
SponsoringUserOperation(eyre::Report),
}

#[derive(Debug, Error)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
pub enum SendError {
#[error("Checking account code: {0}")]
SendingUserOperation(eyre::Report),

#[error("Waiting for user operation receipt: {0}")]
WaitingForUserOperationReceipt(eyre::Report),
}
69 changes: 45 additions & 24 deletions crates/yttrium/src/gas_abstraction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,16 @@ use {
sol_types::SolCall,
},
alloy_provider::{Provider, ProviderBuilder},
error::{
CreateSponsoredUserOpError, PrepareDeployError, PrepareError, SendError,
},
relay_rpc::domain::ProjectId,
reqwest::Url,
std::collections::HashMap,
std::{collections::HashMap, time::Duration},
};

pub mod error;

#[cfg(test)]
mod tests;

Expand Down Expand Up @@ -134,11 +139,14 @@ impl Client {
pub async fn prepare(
&self,
transaction: InitialTransaction,
) -> PreparedGasAbstraction {
) -> Result<PreparedGasAbstraction, PrepareError> {
let provider =
self.provider_pool.get_provider(&transaction.chain_id).await;

let code = provider.get_code_at(transaction.from).await.unwrap();
let code = provider
.get_code_at(transaction.from)
.await
.map_err(PrepareError::CheckingAccountCode)?;
// TODO check if the code is our contract, or something else
// If no code, return 7702 txn and UserOp hash to sign
// If our contract, return UserOp hash to sign
Expand All @@ -160,31 +168,33 @@ impl Client {
nonce: provider
.get_transaction_count(transaction.from)
.await
.unwrap(),
.map_err(PrepareError::GettingNonce)?,
};

let auth = PreparedGasAbstractionAuthorization {
hash: auth.signature_hash(),
auth,
};

PreparedGasAbstraction::DeploymentRequired {
Ok(PreparedGasAbstraction::DeploymentRequired {
auth,
prepare_deploy_params: PrepareDeployParams { transaction },
}
})
} else {
let prepared_send =
self.create_sponsored_user_op(transaction).await;
let prepared_send = self
.create_sponsored_user_op(transaction)
.await
.map_err(PrepareError::CreatingSponsoredUserOp)?;

PreparedGasAbstraction::DeploymentNotRequired { prepared_send }
Ok(PreparedGasAbstraction::DeploymentNotRequired { prepared_send })
}
}

// TODO error type
async fn create_sponsored_user_op(
&self,
transaction: InitialTransaction,
) -> PreparedSend {
) -> Result<PreparedSend, CreateSponsoredUserOpError> {
let InitialTransaction { chain_id, from, to, value, input } =
transaction;

Expand All @@ -200,7 +210,7 @@ impl Client {
encode_validator_key(ownable_validator.address),
)
.await
.unwrap();
.map_err(CreateSponsoredUserOpError::GettingNonce)?;

let mock_signature = get_ownable_validator_mock_signature(&owners);

Expand All @@ -215,7 +225,7 @@ impl Client {
let gas_price = pimlico_client
.estimate_user_operation_gas_price()
.await
.unwrap()
.map_err(CreateSponsoredUserOpError::GettingUserOperationGasPrice)?
.fast;
let user_op = UserOperationV07 {
sender: from.into(),
Expand Down Expand Up @@ -247,7 +257,7 @@ impl Client {
None,
)
.await
.unwrap();
.map_err(CreateSponsoredUserOpError::SponsoringUserOperation)?;

UserOperationV07 {
call_gas_limit: sponsor_user_op_result.call_gas_limit,
Expand Down Expand Up @@ -276,11 +286,11 @@ impl Client {

let hash = eip191_hash_message(message);

PreparedSend {
Ok(PreparedSend {
message: message.into(),
hash,
send_params: SendParams { user_op },
}
})
}

// TODO error type
Expand All @@ -293,7 +303,7 @@ impl Client {
// TODO remove this `sponsor` param once 4337 supports sponsoring 7702 txns
// Pass None to use anvil faucet
sponsor: Option<PrivateKeySigner>,
) -> PreparedSend {
) -> Result<PreparedSend, PrepareDeployError> {
let account = params.transaction.from;
let SignedAuthorization { auth, signature } = auth_sig;
let chain_id = auth.chain_id;
Expand Down Expand Up @@ -324,7 +334,7 @@ impl Client {
let ownable_validator = get_ownable_validator(&owners, None);

// TODO do this in the UserOp as a factory
assert!(SetupContract::new(account, sponsor_provider)
let receipt = SetupContract::new(account, sponsor_provider)
.setup(
safe_owners.owners,
U256::from(safe_owners.threshold),
Expand Down Expand Up @@ -363,13 +373,21 @@ impl Client {
})
.send()
.await
.unwrap()
.map_err(PrepareDeployError::SendingDelegationTransaction)?
.with_timeout(Some(Duration::from_secs(30)))
.get_receipt()
.await
.unwrap()
.status());
.map_err(PrepareDeployError::GettingDelegationTransactionReceipt)?;

if !receipt.status() {
return Err(PrepareDeployError::DelegationTransactionFailed(
receipt,
));
}

self.create_sponsored_user_op(params.transaction).await
self.create_sponsored_user_op(params.transaction)
.await
.map_err(PrepareDeployError::CreatingSponsoredUserOp)
}

// TODO error type
Expand All @@ -378,17 +396,20 @@ impl Client {
&self,
signature: PrimitiveSignature,
params: SendParams,
) -> UserOperationReceipt {
) -> Result<UserOperationReceipt, SendError> {
let bundler_client =
BundlerClient::new(BundlerConfig::new(self.bundler_url.clone()));
let signature = signature.as_bytes().into();
let user_op = UserOperationV07 { signature, ..params.user_op };
let hash = bundler_client
.send_user_operation(ENTRYPOINT_ADDRESS_V07.into(), user_op.clone())
.await
.unwrap();
.map_err(SendError::SendingUserOperation)?;

bundler_client.wait_for_user_operation_receipt(hash).await.unwrap()
bundler_client
.wait_for_user_operation_receipt(hash)
.await
.map_err(SendError::WaitingForUserOperationReceipt)
}

// Signature creation
Expand Down
10 changes: 6 additions & 4 deletions crates/yttrium/src/gas_abstraction/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ async fn happy_path() {
input: Bytes::new(),
};

let result = client.prepare(txn).await;
let result = client.prepare(txn).await.unwrap();
assert!(matches!(
result,
PreparedGasAbstraction::DeploymentRequired { .. }
Expand All @@ -75,8 +75,10 @@ async fn happy_path() {
signature: eoa.sign_hash_sync(&auth.auth.signature_hash()).unwrap(),
auth: auth.auth,
};
let prepared_send =
client.prepare_deploy(auth_sig, prepare_deploy_params, None).await;
let prepared_send = client
.prepare_deploy(auth_sig, prepare_deploy_params, None)
.await
.unwrap();

// Display fee information to the user: prepare_deploy_result.fees
// User approved? Yes
Expand All @@ -98,7 +100,7 @@ async fn happy_path() {
input: Bytes::new(),
};

let result = client.prepare(txn).await;
let result = client.prepare(txn).await.unwrap();
assert!(matches!(
result,
PreparedGasAbstraction::DeploymentNotRequired { .. }
Expand Down
29 changes: 28 additions & 1 deletion crates/yttrium/src/uniffi_compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ use {
smart_accounts::account_address::AccountAddress,
},
alloy::{
contract::Error as AlloyError,
dyn_abi::Eip712Domain,
primitives::{
aliases::U48, Address, Bytes, PrimitiveSignature, Uint, B256, U128,
U256, U64, U8,
},
rpc::types::Authorization,
rpc::types::{Authorization, TransactionReceipt},
signers::local::PrivateKeySigner,
transports::{self, TransportErrorKind},
},
alloy_provider::PendingTransactionError,
eyre::Report as EyreError,
relay_rpc::domain::ProjectId,
reqwest::Url,
};
Expand Down Expand Up @@ -98,6 +102,29 @@ uniffi::custom_type!(Url, String, {
lower: |obj| obj.to_string(),
});

pub type RpcError = transports::RpcError<TransportErrorKind>;

uniffi::custom_type!(RpcError, String, {
try_lift: |_val| unimplemented!("Does not support lifting RpcError"),
lower: |obj| obj.to_string(),
});
uniffi::custom_type!(EyreError, String, {
try_lift: |_val| unimplemented!("Does not support lifting EyreError"),
lower: |obj| obj.to_string(),
});
uniffi::custom_type!(AlloyError, String, {
try_lift: |_val| unimplemented!("Does not support lifting AlloyError"),
lower: |obj| obj.to_string(),
});
uniffi::custom_type!(TransactionReceipt, String, {
try_lift: |_val| unimplemented!("Does not support lifting TransactionReceipt"),
lower: |obj| serde_json::to_string(&obj).unwrap(),
});
uniffi::custom_type!(PendingTransactionError, String, {
try_lift: |_val| unimplemented!("Does not support lifting PendingTransactionError"),
lower: |obj| obj.to_string(),
});

// uniffi::custom_type!(Unit, u8, {
// try_lift: |val| Ok(Unit::new(val).expect("Unit must be less than 77")),
// lower: |obj| obj.get(),
Expand Down

0 comments on commit 01301b1

Please sign in to comment.