diff --git a/Cargo.toml b/Cargo.toml index 05e7b476..7d075a31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ - "crates/cli", "crates/kotlin-ffi", "crates/yttrium", "crates/yttrium_dart/rust", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml deleted file mode 100644 index b566a752..00000000 --- a/crates/cli/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "cli" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -# yttrium = { path = "../yttrium" } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs deleted file mode 100644 index 5bf256ea..00000000 --- a/crates/cli/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello world"); -} diff --git a/crates/kotlin-ffi/src/lib.rs b/crates/kotlin-ffi/src/lib.rs index 3e69a367..bb5de5ec 100644 --- a/crates/kotlin-ffi/src/lib.rs +++ b/crates/kotlin-ffi/src/lib.rs @@ -14,24 +14,23 @@ use { std::time::Duration, yttrium::{ account_client::AccountClient as YAccountClient, + call::{ + send::safe_test::{ + self, DoSendTransactionParams, OwnerSignature, + PreparedSendTransaction, + }, + Call, + }, chain_abstraction::{ api::{ prepare::{PrepareResponse, PrepareResponseAvailable}, status::{StatusResponse, StatusResponseCompleted}, - InitialTransaction, }, client::Client, currency::Currency, ui_fields::UiFields, }, config::Config, - execution::{ - send::safe_test::{ - self, DoSendTransactionParams, OwnerSignature, - PreparedSendTransaction, - }, - Execution, - }, smart_accounts::{ account_address::AccountAddress as FfiAccountAddress, safe::{SignOutputEnum, SignStep3Params}, @@ -127,10 +126,12 @@ impl ChainAbstractionClient { pub async fn prepare( &self, - initial_transaction: InitialTransaction, + chain_id: String, + from: FFIAddress, + call: Call, ) -> Result { self.client - .prepare(initial_transaction) + .prepare(chain_id, from, call) .await .map_err(|e| FFIError::General(e.to_string())) } @@ -261,7 +262,7 @@ impl FFIAccountClient { pub async fn prepare_send_transactions( &self, - transactions: Vec, + transactions: Vec, ) -> Result { self.account_client .prepare_send_transactions(transactions) diff --git a/crates/yttrium/src/account_client.rs b/crates/yttrium/src/account_client.rs index 68e7f2d7..72466181 100644 --- a/crates/yttrium/src/account_client.rs +++ b/crates/yttrium/src/account_client.rs @@ -4,8 +4,7 @@ use { client::BundlerClient, config::BundlerConfig, pimlico::paymaster::client::PaymasterClient, }, - config::Config, - execution::{ + call::{ send::{ do_send_transactions, prepare_send_transaction, safe_test::{ @@ -13,8 +12,9 @@ use { PreparedSendTransaction, }, }, - Execution, + Call, }, + config::Config, smart_accounts::{ account_address::AccountAddress, safe::{ @@ -96,7 +96,7 @@ impl AccountClient { pub async fn prepare_send_transactions( &self, - transactions: Vec, + transactions: Vec, ) -> eyre::Result { prepare_send_transaction( transactions, diff --git a/crates/yttrium/src/execution.rs b/crates/yttrium/src/call.rs similarity index 69% rename from crates/yttrium/src/execution.rs rename to crates/yttrium/src/call.rs index d224e563..dcb0a1d5 100644 --- a/crates/yttrium/src/execution.rs +++ b/crates/yttrium/src/call.rs @@ -7,45 +7,46 @@ pub mod send; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] -pub struct Execution { +#[serde(rename_all = "camelCase")] +pub struct Call { pub to: Address, pub value: U256, - pub data: Bytes, + pub input: Bytes, } -impl Execution { - pub fn new(to: Address, value: U256, data: Bytes) -> Self { - Self { to, value, data } +impl Call { + pub fn new(to: Address, value: U256, input: Bytes) -> Self { + Self { to, value, input } } pub fn new_from_strings( to: String, value: String, - data: String, + input: String, ) -> eyre::Result { let to = to.parse()?; let value = value.parse()?; - let data = data.parse()?; - Ok(Self { to, value, data }) + let input = input.parse()?; + Ok(Self { to, value, input }) } } -impl std::fmt::Display for Execution { +impl std::fmt::Display for Call { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "Transaction(to: {}, value: {}, data: {})", - self.to, self.value, self.data + "Transaction(to: {}, value: {}, input: {})", + self.to, self.value, self.input ) } } -impl Execution { +impl Call { pub fn mock() -> Self { Self { to: address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"), value: U256::ZERO, - data: "0x68656c6c6f".parse().unwrap(), + input: "0x68656c6c6f".parse().unwrap(), } } } @@ -56,9 +57,9 @@ mod tests { #[test] fn test_new_from_strings() -> eyre::Result<()> { - let expected_transaction = Execution::mock(); + let expected_transaction = Call::mock(); - let transaction = Execution::new_from_strings( + let transaction = Call::new_from_strings( "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".to_string(), "0".to_string(), "0x68656c6c6f".to_string(), diff --git a/crates/yttrium/src/execution/send.rs b/crates/yttrium/src/call/send.rs similarity index 95% rename from crates/yttrium/src/execution/send.rs rename to crates/yttrium/src/call/send.rs index 8a956de3..7a1492ec 100644 --- a/crates/yttrium/src/execution/send.rs +++ b/crates/yttrium/src/call/send.rs @@ -1,6 +1,6 @@ use { crate::{ - config::Config, execution::Execution, + call::Call, config::Config, smart_accounts::account_address::AccountAddress, user_operation::UserOperationV07, }, @@ -47,7 +47,7 @@ impl fmt::Display for SentUserOperationHash { } pub async fn prepare_send_transaction( - transactions: Vec, + transactions: Vec, owner: AccountAddress, _chain_id: u64, config: Config, diff --git a/crates/yttrium/src/execution/send/safe_test.rs b/crates/yttrium/src/call/send/safe_test.rs similarity index 97% rename from crates/yttrium/src/execution/send/safe_test.rs rename to crates/yttrium/src/call/send/safe_test.rs index dc2550c3..6f201101 100644 --- a/crates/yttrium/src/execution/send/safe_test.rs +++ b/crates/yttrium/src/call/send/safe_test.rs @@ -8,10 +8,10 @@ use { paymaster::client::PaymasterClient, }, }, + call::Call, chain::ChainId, config::Config, entry_point::{EntryPointVersion, ENTRYPOINT_ADDRESS_V07}, - execution::Execution, smart_accounts::{ account_address::AccountAddress, nonce::get_nonce, @@ -91,7 +91,7 @@ pub async fn get_address( } pub async fn send_transactions( - execution_calldata: Vec, + execution_calldata: Vec, owner: LocalSigner, address: Option, authorization_list: Option>, @@ -174,7 +174,7 @@ pub struct DoSendTransactionParams { } pub async fn prepare_send_transactions( - execution_calldata: Vec, + execution_calldata: Vec, owner: Address, address: Option, authorization_list: Option>, @@ -210,7 +210,7 @@ pub async fn prepare_send_transactions( #[allow(clippy::too_many_arguments)] pub async fn prepare_send_transactions_inner( - execution_calldata: Vec, + execution_calldata: Vec, owners: Owners, address: Option, authorization_list: Option>, @@ -449,8 +449,8 @@ mod tests { use { super::*, crate::{ + call::Call, chain::ChainId, - execution::Execution, smart_accounts::safe::{ prepare_sign, sign, sign_step_3, PreparedSignature, SignOutputEnum, @@ -493,10 +493,10 @@ mod tests { ) .await; - let transaction = vec![Execution { + let transaction = vec![Call { to: destination.address(), value: Uint::from(1), - data: Bytes::new(), + input: Bytes::new(), }]; let receipt = send_transactions( @@ -512,10 +512,10 @@ mod tests { let balance = provider.get_balance(destination.address()).await?; assert_eq!(balance, Uint::from(1)); - let transaction = vec![Execution { + let transaction = vec![Call { to: destination.address(), value: Uint::from(1), - data: Bytes::new(), + input: Bytes::new(), }]; let receipt = @@ -589,10 +589,10 @@ mod tests { ) .await; - let transaction = vec![Execution { + let transaction = vec![Call { to: destination.address(), value: Uint::from(1), - data: Bytes::new(), + input: Bytes::new(), }]; let receipt = send_transactions( @@ -630,10 +630,10 @@ mod tests { ) .await; - let transaction = vec![Execution { + let transaction = vec![Call { to: destination.address(), value: Uint::from(1), - data: Bytes::new(), + input: Bytes::new(), }]; let receipt = send_transactions(transaction, owner, None, None, config) @@ -673,10 +673,10 @@ mod tests { ) .await; - let transaction = vec![Execution { + let transaction = vec![Call { to: destination.address(), value: Uint::from(1), - data: Bytes::new(), + input: Bytes::new(), }]; let receipt = send_transactions( @@ -803,15 +803,15 @@ mod tests { .await; let transaction = vec![ - Execution { + Call { to: destination1.address(), value: Uint::from(1), - data: Bytes::new(), + input: Bytes::new(), }, - Execution { + Call { to: destination2.address(), value: Uint::from(2), - data: Bytes::new(), + input: Bytes::new(), }, ]; @@ -1034,10 +1034,10 @@ mod tests { provider.get_balance(destination.address()).await.unwrap(); assert_eq!(balance, Uint::from(0)); let receipt = send_transactions( - vec![Execution { + vec![Call { to: destination.address(), value: Uint::from(1), - data: Bytes::new(), + input: Bytes::new(), }], owner.clone(), None, @@ -1136,10 +1136,10 @@ mod tests { ) .await; - let transaction = vec![Execution { + let transaction = vec![Call { to: destination.address(), value: Uint::from(1), - data: Bytes::new(), + input: Bytes::new(), }]; let receipt = send_transactions(transaction, owner, None, None, config) @@ -1228,10 +1228,10 @@ mod tests { provider.get_code_at(authority.address()).await? ); - let transaction = vec![Execution { + let transaction = vec![Call { to: destination.address(), value: Uint::from(1), - data: Bytes::new(), + input: Bytes::new(), }]; let receipt = send_transactions( @@ -1381,10 +1381,10 @@ mod tests { provider.get_code_at(authority.address()).await? ); - let transaction: Vec<_> = vec![Execution { + let transaction = vec![Call { to: destination.address(), value: Uint::from(1), - data: Bytes::new(), + input: Bytes::new(), }]; let receipt = send_transactions( diff --git a/crates/yttrium/src/chain_abstraction/api/mod.rs b/crates/yttrium/src/chain_abstraction/api/mod.rs index 2d406f32..fe7f2619 100644 --- a/crates/yttrium/src/chain_abstraction/api/mod.rs +++ b/crates/yttrium/src/chain_abstraction/api/mod.rs @@ -12,19 +12,6 @@ pub mod fungible_price; pub mod prepare; pub mod status; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[cfg_attr(feature = "uniffi", derive(uniffi_macros::Record))] -#[serde(rename_all = "camelCase")] -pub struct InitialTransaction { - // CAIP-2 chain ID - pub chain_id: String, - - pub from: Address, - pub to: Address, - pub value: U256, - pub input: Bytes, -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "uniffi", derive(uniffi_macros::Record))] #[serde(rename_all = "camelCase")] diff --git a/crates/yttrium/src/chain_abstraction/api/prepare.rs b/crates/yttrium/src/chain_abstraction/api/prepare.rs index 2543cc0b..f9aee864 100644 --- a/crates/yttrium/src/chain_abstraction/api/prepare.rs +++ b/crates/yttrium/src/chain_abstraction/api/prepare.rs @@ -1,6 +1,6 @@ use { - super::{InitialTransaction, Transaction}, - crate::chain_abstraction::amount::Amount, + super::Transaction, + crate::{call::Call, chain_abstraction::amount::Amount}, alloy::primitives::{utils::Unit, Address, U256}, relay_rpc::domain::ProjectId, serde::{Deserialize, Serialize}, @@ -16,7 +16,38 @@ pub struct RouteQueryParams { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PrepareRequest { - pub transaction: InitialTransaction, + pub transaction: PrepareRequestTransaction, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PrepareRequestTransaction { + pub chain_id: String, + pub from: Address, + #[serde(flatten)] + pub calls: CallOrCalls, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum CallOrCalls { + Call { + #[serde(flatten)] + call: Call, + }, + // Don't use this yet until Blockchain API upgrades + Calls { + calls: Vec, + }, +} + +impl CallOrCalls { + pub fn into_calls(self) -> Vec { + match self { + Self::Call { call } => vec![call], + Self::Calls { calls } => calls, + } + } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -163,3 +194,97 @@ impl PrepareResponse { } } } + +#[cfg(test)] +mod tests { + use {super::*, alloy::primitives::Bytes}; + + #[test] + fn deserializes_current_request_body() { + let chain_id = "eip155:1"; + let from = Address::ZERO; + let to = Address::ZERO; + let value = U256::from(0); + let input = Bytes::new(); + let json = serde_json::json!({ + "transaction": { + "chainId": chain_id, + "from": from, + "to": to, + "value": value, + "input": input, + } + }); + let result = serde_json::from_value::(json).unwrap(); + assert_eq!(result.transaction.chain_id, chain_id); + assert_eq!(result.transaction.from, from); + assert!(matches!(result.transaction.calls, CallOrCalls::Call { .. })); + let calls = result.transaction.calls.into_calls(); + assert_eq!(calls.len(), 1); + assert_eq!(calls[0].to, to); + assert_eq!(calls[0].value, value); + assert_eq!(calls[0].input, input); + } + + #[test] + fn deserializes_single_call() { + let chain_id = "eip155:1"; + let from = Address::ZERO; + let to = Address::ZERO; + let value = U256::from(0); + let input = Bytes::new(); + let json = serde_json::json!({ + "transaction": { + "chainId": chain_id, + "from": from, + "calls": [{ + "to": to, + "value": value, + "input": input, + }] + } + }); + let result = serde_json::from_value::(json).unwrap(); + assert_eq!(result.transaction.chain_id, chain_id); + assert_eq!(result.transaction.from, from); + assert!(matches!(result.transaction.calls, CallOrCalls::Calls { .. })); + let calls = result.transaction.calls.into_calls(); + assert_eq!(calls.len(), 1); + assert_eq!(calls[0].to, to); + assert_eq!(calls[0].value, value); + assert_eq!(calls[0].input, input); + } + + #[test] + fn deserializes_multiple_calls() { + let chain_id = "eip155:1"; + let from = Address::ZERO; + let to = Address::ZERO; + let value = U256::from(0); + let input = Bytes::new(); + let json = serde_json::json!({ + "transaction": { + "chainId": chain_id, + "from": from, + "calls": [{ + "to": to, + "value": value, + "input": input, + }, { + "to": to, + "value": value, + "input": input, + }] + } + }); + let result = serde_json::from_value::(json).unwrap(); + assert_eq!(result.transaction.chain_id, chain_id); + assert_eq!(result.transaction.from, from); + assert!(matches!(result.transaction.calls, CallOrCalls::Calls { .. })); + let calls = result.transaction.calls.into_calls(); + assert_eq!(calls.len(), 2); + assert_eq!(calls[0].to, to); + assert_eq!(calls[0].value, value); + assert_eq!(calls[0].input, input); + } +} diff --git a/crates/yttrium/src/chain_abstraction/client.rs b/crates/yttrium/src/chain_abstraction/client.rs index c7d7cef3..05dc2e97 100644 --- a/crates/yttrium/src/chain_abstraction/client.rs +++ b/crates/yttrium/src/chain_abstraction/client.rs @@ -6,14 +6,15 @@ use { FUNGIBLE_PRICE_ENDPOINT_PATH, NATIVE_TOKEN_ADDRESS, }, prepare::{ - PrepareRequest, PrepareResponse, PrepareResponseAvailable, + CallOrCalls, PrepareRequest, PrepareRequestTransaction, + PrepareResponse, PrepareResponseAvailable, PrepareResponseSuccess, RouteQueryParams, ROUTE_ENDPOINT_PATH, }, status::{ StatusQueryParams, StatusResponse, StatusResponseCompleted, STATUS_ENDPOINT_PATH, }, - InitialTransaction, Transaction, + Transaction, }, currency::Currency, error::{ @@ -23,6 +24,7 @@ use { ui_fields::UiFields, }, crate::{ + call::Call, chain_abstraction::{ error::UiFieldsError, l1_data_fee::get_l1_data_fee, ui_fields, }, @@ -54,7 +56,9 @@ impl Client { pub async fn prepare( &self, - transaction: InitialTransaction, + chain_id: String, + from: Address, + call: Call, ) -> Result { let response = self .provider_pool @@ -65,7 +69,13 @@ impl Client { .join(ROUTE_ENDPOINT_PATH) .unwrap(), ) - .json(&PrepareRequest { transaction }) + .json(&PrepareRequest { + transaction: PrepareRequestTransaction { + chain_id, + from, + calls: CallOrCalls::Call { call }, + }, + }) .query(&RouteQueryParams { project_id: self.provider_pool.project_id.clone(), }) @@ -256,13 +266,15 @@ impl Client { // TODO test pub async fn prepare_detailed( &self, - transaction: InitialTransaction, + chain_id: String, + from: Address, + call: Call, local_currency: Currency, // TODO use this to e.g. modify priority fee // _speed: String, ) -> Result { - let response = self - .prepare(transaction) + let response: PrepareResponse = self + .prepare(chain_id, from, call) .await .map_err(PrepareDetailedError::Prepare)?; match response { diff --git a/crates/yttrium/src/chain_abstraction/tests.rs b/crates/yttrium/src/chain_abstraction/tests.rs index 6e0b35ec..025ff8c6 100644 --- a/crates/yttrium/src/chain_abstraction/tests.rs +++ b/crates/yttrium/src/chain_abstraction/tests.rs @@ -1,5 +1,6 @@ use { crate::{ + call::Call, chain_abstraction::{ amount::Amount, api::{ @@ -7,7 +8,7 @@ use { BridgingError, PrepareResponse, PrepareResponseError, }, status::StatusResponse, - InitialTransaction, Transaction, + Transaction, }, client::Client, currency::Currency, @@ -384,8 +385,7 @@ async fn bridging_routes_routes_available() { } assert!(chain_1_address_1_token.token_balance().await >= required_amount); - let transaction = InitialTransaction { - from: source.address(), + let transaction = Call { to: *chain_2_address_1_token.token.address(), value: U256::ZERO, input: ERC20::transferCall { @@ -394,7 +394,6 @@ async fn bridging_routes_routes_available() { } .abi_encode() .into(), - chain_id: chain_2.eip155_chain_id().to_owned(), }; println!("input transaction: {:?}", transaction); @@ -402,7 +401,11 @@ async fn bridging_routes_routes_available() { let client = Client::new(project_id); let start = Instant::now(); let mut result = client - .prepare(transaction.clone()) + .prepare( + chain_2.eip155_chain_id().to_owned(), + source.address(), + transaction.clone(), + ) .await .unwrap() .into_result() @@ -727,8 +730,7 @@ async fn happy_path() { } assert!(source.token_balance(&sources).await >= required_amount); - let initial_transaction = InitialTransaction { - from: source.address(&sources), + let initial_transaction = Call { to: *source.other().bridge_token(&sources).token.address(), value: U256::ZERO, input: ERC20::transferCall { @@ -737,20 +739,23 @@ async fn happy_path() { } .abi_encode() .into(), - chain_id: source - .other() - .bridge_token(&sources) - .params - .chain - .eip155_chain_id() - .to_owned(), }; println!("input transaction: {:?}", initial_transaction); let project_id = std::env::var("REOWN_PROJECT_ID").unwrap().into(); let client = Client::new(project_id); let mut result = client - .prepare(initial_transaction.clone()) + .prepare( + source + .other() + .bridge_token(&sources) + .params + .chain + .eip155_chain_id() + .to_owned(), + source.address(&sources), + initial_transaction.clone(), + ) .await .unwrap() .into_result() @@ -1285,8 +1290,7 @@ async fn happy_path_full_dependency_on_ui_fields() { } assert!(source.token_balance(&sources).await >= required_amount); - let initial_transaction = InitialTransaction { - from: source.address(&sources), + let initial_transaction = Call { to: *source.other().bridge_token(&sources).token.address(), value: U256::ZERO, input: ERC20::transferCall { @@ -1295,20 +1299,25 @@ async fn happy_path_full_dependency_on_ui_fields() { } .abi_encode() .into(), - chain_id: source - .other() - .bridge_token(&sources) - .params - .chain - .eip155_chain_id() - .to_owned(), }; println!("input transaction: {:?}", initial_transaction); + let initial_transaction_chain_id = source + .other() + .bridge_token(&sources) + .params + .chain + .eip155_chain_id() + .to_owned(); + let project_id = std::env::var("REOWN_PROJECT_ID").unwrap().into(); let client = Client::new(project_id); let mut result = client - .prepare(initial_transaction.clone()) + .prepare( + initial_transaction_chain_id.clone(), + source.address(&sources), + initial_transaction.clone(), + ) .await .unwrap() .into_result() @@ -1510,7 +1519,7 @@ async fn happy_path_full_dependency_on_ui_fields() { println!("confirmed receipts in {:?}", approval_start.elapsed()); let provider = provider_for_chain(&Chain::from_eip155_chain_id( - &initial_transaction.chain_id, + &initial_transaction_chain_id, )); let original = ui_fields.initial.transaction.into_transaction_request(); @@ -1649,8 +1658,7 @@ async fn bridging_routes_routes_insufficient_funds() { let send_amount = U256::from(1_500_000); // 1.5 USDC (6 decimals) - let transaction = InitialTransaction { - from: account_1.address(), + let transaction = Call { to: *chain_1_address_2_token.token.address(), value: U256::ZERO, input: ERC20::transferCall { @@ -1659,13 +1667,19 @@ async fn bridging_routes_routes_insufficient_funds() { } .abi_encode() .into(), - chain_id: chain_1.eip155_chain_id().to_owned(), }; println!("input transaction: {:?}", transaction); let project_id = std::env::var("REOWN_PROJECT_ID").unwrap().into(); let client = Client::new(project_id); - let result = client.prepare(transaction.clone()).await.unwrap(); + let result = client + .prepare( + chain_1.eip155_chain_id().to_owned(), + account_1.address(), + transaction.clone(), + ) + .await + .unwrap(); assert_eq!( result, PrepareResponse::Error(PrepareResponseError { diff --git a/crates/yttrium/src/examples/eip7702_smart_sessions.rs b/crates/yttrium/src/examples/eip7702_smart_sessions.rs index 0d6dfcbc..6110975e 100644 --- a/crates/yttrium/src/examples/eip7702_smart_sessions.rs +++ b/crates/yttrium/src/examples/eip7702_smart_sessions.rs @@ -7,6 +7,7 @@ use { config::BundlerConfig, pimlico::{self, paymaster::client::PaymasterClient}, }, + call::Call, config::{LOCAL_BUNDLER_URL, LOCAL_PAYMASTER_URL, LOCAL_RPC_URL}, entry_point::ENTRYPOINT_ADDRESS_V07, erc7579::{ @@ -23,7 +24,6 @@ use { get_smart_sessions_validator, ActionData, ERC7739Data, Session, }, }, - execution::Execution, smart_accounts::{ nonce::get_nonce_with_key, safe::{ @@ -264,10 +264,10 @@ async fn test_impl( nonce, factory: None, factory_data: None, - call_data: get_call_data(vec![Execution { + call_data: get_call_data(vec![Call { to: session.actions[0].actionTarget, value: U256::ZERO, - data: session.actions[0].actionTargetSelector.into(), + input: session.actions[0].actionTargetSelector.into(), }]), call_gas_limit: U256::ZERO, verification_gas_limit: U256::ZERO, diff --git a/crates/yttrium/src/gas_abstraction/mod.rs b/crates/yttrium/src/gas_abstraction/mod.rs index f2a6a12f..b5ca4092 100644 --- a/crates/yttrium/src/gas_abstraction/mod.rs +++ b/crates/yttrium/src/gas_abstraction/mod.rs @@ -6,7 +6,8 @@ use { config::BundlerConfig, pimlico::{self, paymaster::client::PaymasterClient}, }, - chain_abstraction::{amount::Amount, api::InitialTransaction}, + call::Call, + chain_abstraction::amount::Amount, entry_point::ENTRYPOINT_ADDRESS_V07, erc7579::{ accounts::safe::encode_validator_key, @@ -137,13 +138,14 @@ impl Client { // TODO error type pub async fn prepare( &self, - transaction: InitialTransaction, + chain_id: String, + from: Address, + calls: Vec, ) -> Result { - let provider = - self.provider_pool.get_provider(&transaction.chain_id).await; + let provider = self.provider_pool.get_provider(&chain_id).await; let code = provider - .get_code_at(transaction.from) + .get_code_at(from) .await .map_err(PrepareError::CheckingAccountCode)?; // TODO check if the code is our contract, or something else @@ -156,8 +158,7 @@ impl Client { // TODO throw error if it's not our account (how?) let auth = Authorization { - chain_id: transaction - .chain_id + chain_id: chain_id .strip_prefix("eip155:") .unwrap() .parse() @@ -165,7 +166,7 @@ impl Client { address: SAFE_L2_SINGLETON_1_4_1, // TODO should this be `pending` tag? https://github.com/wevm/viem/blob/a49c100a0b2878fbfd9f1c9b43c5cc25de241754/src/experimental/eip7702/actions/signAuthorization.ts#L149 nonce: provider - .get_transaction_count(transaction.from) + .get_transaction_count(from) .await .map_err(PrepareError::GettingNonce)?, }; @@ -177,11 +178,15 @@ impl Client { Ok(PreparedGasAbstraction::DeploymentRequired { auth, - prepare_deploy_params: PrepareDeployParams { transaction }, + prepare_deploy_params: PrepareDeployParams { + chain_id, + from, + calls, + }, }) } else { let prepared_send = self - .create_sponsored_user_op(transaction) + .create_sponsored_user_op(chain_id, from, calls) .await .map_err(PrepareError::CreatingSponsoredUserOp)?; @@ -192,11 +197,10 @@ impl Client { // TODO error type async fn create_sponsored_user_op( &self, - transaction: InitialTransaction, + chain_id: String, + from: Address, + calls: Vec, ) -> Result { - let InitialTransaction { chain_id, from, to, value, input } = - transaction; - // TODO don't look this up a second time let provider = self.provider_pool.get_provider(&chain_id).await; @@ -231,11 +235,7 @@ impl Client { nonce, factory: None, factory_data: None, - call_data: get_call_data(vec![crate::execution::Execution { - to, - value, - data: input, - }]), + call_data: get_call_data(calls), call_gas_limit: U256::ZERO, verification_gas_limit: U256::ZERO, pre_verification_gas: U256::ZERO, @@ -320,7 +320,7 @@ impl Client { // Pass None to use anvil faucet sponsor: Option, ) -> Result { - let account = params.transaction.from; + let account = params.from; let SignedAuthorization { auth, signature } = auth_sig; let chain_id = auth.chain_id; let auth = auth.into_signed(signature); @@ -402,9 +402,13 @@ impl Client { )); } - self.create_sponsored_user_op(params.transaction) - .await - .map_err(PrepareDeployError::CreatingSponsoredUserOp) + self.create_sponsored_user_op( + params.chain_id, + params.from, + params.calls, + ) + .await + .map_err(PrepareDeployError::CreatingSponsoredUserOp) } // TODO error type @@ -451,7 +455,9 @@ pub enum PreparedGasAbstraction { #[derive(Clone)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct PrepareDeployParams { - pub transaction: InitialTransaction, + pub chain_id: String, + pub from: Address, + pub calls: Vec, } #[derive(Clone)] diff --git a/crates/yttrium/src/gas_abstraction/tests.rs b/crates/yttrium/src/gas_abstraction/tests.rs index e3e76d08..8a1c5103 100644 --- a/crates/yttrium/src/gas_abstraction/tests.rs +++ b/crates/yttrium/src/gas_abstraction/tests.rs @@ -1,6 +1,6 @@ use { crate::{ - chain_abstraction::api::InitialTransaction, + call::Call, config::{LOCAL_BUNDLER_URL, LOCAL_PAYMASTER_URL, LOCAL_RPC_URL}, gas_abstraction::{ Client as GasAbstractionClient, PreparedGasAbstraction, @@ -42,18 +42,17 @@ async fn happy_path() { // You have an EOA let eoa = LocalSigner::random(); + let from = eoa.address(); { // You have an incomming eth_sendTransaction - let txn = InitialTransaction { - chain_id: chain_id.clone(), - from: eoa.address(), + let txn = vec![Call { to: LocalSigner::random().address(), value: U256::ZERO, input: Bytes::new(), - }; + }]; - let result = client.prepare(txn).await.unwrap(); + let result = client.prepare(chain_id.clone(), from, txn).await.unwrap(); assert!(matches!( result, PreparedGasAbstraction::DeploymentRequired { .. } @@ -92,15 +91,52 @@ async fn happy_path() { // Second eth_sendTransaction { // You have an incomming eth_sendTransaction - let txn = InitialTransaction { - chain_id, - from: eoa.address(), + let calls = vec![Call { to: LocalSigner::random().address(), value: U256::ZERO, input: Bytes::new(), + }]; + + let result = + client.prepare(chain_id.clone(), from, calls).await.unwrap(); + assert!(matches!( + result, + PreparedGasAbstraction::DeploymentNotRequired { .. } + )); + let prepared_send = match result { + PreparedGasAbstraction::DeploymentNotRequired { prepared_send } => { + prepared_send + } + PreparedGasAbstraction::DeploymentRequired { .. } => { + panic!("unexpected") + } }; - let result = client.prepare(txn).await.unwrap(); + // Display fee information to the user: prepare.fees + // User approved? Yes + + let signature = eoa.sign_hash_sync(&prepared_send.hash).unwrap(); + let receipt = client.send(signature, prepared_send.send_params).await; + println!("receipt: {:?}", receipt); + } + + // Third eth_sendTransaction (2 calls) + { + // You have an incomming eth_sendTransaction + let calls = vec![ + Call { + to: LocalSigner::random().address(), + value: U256::ZERO, + input: Bytes::new(), + }, + Call { + to: LocalSigner::random().address(), + value: U256::ZERO, + input: Bytes::new(), + }, + ]; + + let result = client.prepare(chain_id, from, calls).await.unwrap(); assert!(matches!( result, PreparedGasAbstraction::DeploymentNotRequired { .. } diff --git a/crates/yttrium/src/lib.rs b/crates/yttrium/src/lib.rs index d60d478c..2cffad1b 100644 --- a/crates/yttrium/src/lib.rs +++ b/crates/yttrium/src/lib.rs @@ -7,6 +7,7 @@ pub mod account_client; pub mod blockchain_api; #[cfg(not(target_arch = "wasm32"))] pub mod bundler; +pub mod call; pub mod chain; pub mod chain_abstraction; pub mod config; @@ -16,7 +17,6 @@ pub mod erc20; pub mod erc6492_client; pub mod erc7579; pub mod error; -pub mod execution; pub mod gas_abstraction; pub mod jsonrpc; pub mod provider_pool; diff --git a/crates/yttrium/src/smart_accounts/safe.rs b/crates/yttrium/src/smart_accounts/safe.rs index c6498320..6172b79e 100644 --- a/crates/yttrium/src/smart_accounts/safe.rs +++ b/crates/yttrium/src/smart_accounts/safe.rs @@ -1,19 +1,19 @@ use { crate::{ bundler::pimlico::paymaster::client::PaymasterClient, - entry_point::{ - EntryPoint::{self, PackedUserOperation}, - ENTRYPOINT_ADDRESS_V07, - }, - erc7579::addresses::RHINESTONE_ATTESTER_ADDRESS, - execution::{ + call::{ send::safe_test::{ encode_send_transactions, prepare_send_transactions_inner, DoSendTransactionParams, OwnerSignature, PreparedSendTransaction, }, - Execution, + Call, }, + entry_point::{ + EntryPoint::{self, PackedUserOperation}, + ENTRYPOINT_ADDRESS_V07, + }, + erc7579::addresses::RHINESTONE_ATTESTER_ADDRESS, smart_accounts::account_address::AccountAddress, user_operation::{ hash::pack_v07::{ @@ -321,15 +321,12 @@ where SAFE_PROXY_FACTORY_1_4_1.create2(salt, keccak256(deployment_code)).into() } -pub fn get_call_data(execution_calldata: Vec) -> Bytes { - get_call_data_with_try(execution_calldata, false) +pub fn get_call_data(calls: Vec) -> Bytes { + get_call_data_with_try(calls, false) } -pub fn get_call_data_with_try( - execution_calldata: Vec, - exec_type: bool, -) -> Bytes { - let batch = execution_calldata.len() != 1; +pub fn get_call_data_with_try(calls: Vec, exec_type: bool) -> Bytes { + let batch = calls.len() != 1; let selector = [0u8; 4]; let context = [0u8; 22]; @@ -342,11 +339,9 @@ pub fn get_call_data_with_try( ]) .abi_encode_packed(); - let execution_calldata = encode_calls(execution_calldata); - Safe7579::executeCall { mode: FixedBytes::from_slice(&mode), - executionCalldata: execution_calldata, + executionCalldata: encode_calls(calls), } .abi_encode() .into() @@ -356,9 +351,9 @@ sol! { function executionBatch((address, uint256, bytes)[]); } -fn encode_calls(calls: Vec) -> Bytes { - fn call(call: Execution) -> (Address, U256, Bytes) { - (call.to, call.value, call.data) +fn encode_calls(calls: Vec) -> Bytes { + fn call(call: Call) -> (Address, U256, Bytes) { + (call.to, call.value, call.input) } let tuples = calls.into_iter().map(call).collect::>(); @@ -623,10 +618,10 @@ mod tests { #[test] fn single_execution_call_data_value() { assert_eq!( - encode_calls(vec![Execution { + encode_calls(vec![Call { to: address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), value: U256::from(19191919), - data: bytes!(""), + input: bytes!(""), }]), bytes!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000124d86f") ); @@ -635,10 +630,10 @@ mod tests { #[test] fn single_execution_call_data_data() { assert_eq!( - encode_calls(vec![Execution { + encode_calls(vec![Call { to: address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), value: U256::ZERO, - data: bytes!("7777777777777777"), + input: bytes!("7777777777777777"), }]), bytes!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000000000007777777777777777") ); @@ -647,14 +642,14 @@ mod tests { #[test] fn two_execution_call_data() { assert_eq!( - encode_calls(vec![Execution { + encode_calls(vec![Call { to: address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), value: U256::from(19191919), - data: bytes!(""), - }, Execution { + input: bytes!(""), + }, Call { to: address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), value: U256::ZERO, - data: bytes!("7777777777777777"), + input: bytes!("7777777777777777"), }]), bytes!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000124d86f00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000087777777777777777000000000000000000000000000000000000000000000000") ); @@ -668,10 +663,10 @@ mod tests { #[test] fn single_call_data_value() { assert_eq!( - get_call_data(vec![Execution { + get_call_data(vec![Call { to: address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), value: U256::from(19191919), - data: bytes!(""), + input: bytes!(""), }]), bytes!("e9ae5c53000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000034aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000124d86f000000000000000000000000") ); @@ -680,10 +675,10 @@ mod tests { #[test] fn single_call_data_data() { assert_eq!( - get_call_data(vec![Execution { + get_call_data(vec![Call { to: address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), value: U256::ZERO, - data: bytes!("7777777777777777"), + input: bytes!("7777777777777777"), }]), bytes!("e9ae5c5300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000003caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000777777777777777700000000") ); @@ -692,14 +687,14 @@ mod tests { #[test] fn two_call_data() { assert_eq!( - get_call_data(vec![Execution { + get_call_data(vec![Call { to: address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), value: U256::from(19191919), - data: bytes!(""), - }, Execution { + input: bytes!(""), + }, Call { to: address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), value: U256::ZERO, - data: bytes!("7777777777777777"), + input: bytes!("7777777777777777"), }]), bytes!("e9ae5c530100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000124d86f00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000087777777777777777000000000000000000000000000000000000000000000000") ); diff --git a/crates/yttrium_dart/rust/src/lib.rs b/crates/yttrium_dart/rust/src/lib.rs index 5aefaa6d..ba75cf6c 100644 --- a/crates/yttrium_dart/rust/src/lib.rs +++ b/crates/yttrium_dart/rust/src/lib.rs @@ -10,29 +10,18 @@ use { std::time::Duration, yttrium::{ account_client::AccountClient as YAccountClient, + call::{send::safe_test::OwnerSignature as YOwnerSignature, Call}, chain_abstraction::{ api::{ prepare::PrepareResponse, status::{StatusResponse, StatusResponseCompleted}, - InitialTransaction, }, client::Client, }, config::Config, - execution::{ - send::safe_test::OwnerSignature as YOwnerSignature, - Execution as YTransaction, - }, }, }; -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Transaction { - pub to: String, - pub value: String, - pub data: String, -} - #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PreparedSendTransaction { pub hash: String, @@ -71,10 +60,12 @@ impl ChainAbstractionClient { pub async fn route( &self, - initial_transaction: InitialTransaction, + chain_id: String, + from: Address, + call: Call, ) -> Result { self.client - .prepare(initial_transaction) + .prepare(chain_id, from, call) .await .map_err(|e| Error::General(e.to_string())) } @@ -177,10 +168,10 @@ impl AccountClient { pub async fn prepare_send_transactions( &self, - transactions: Vec, + transactions: Vec, ) -> Result { - let ytransactions: Vec = - transactions.into_iter().map(YTransaction::from).collect(); + let ytransactions: Vec = + transactions.into_iter().map(Call::from).collect(); let prepared_send_transaction = self .account_client @@ -248,17 +239,6 @@ impl AccountClient { } } -impl From for YTransaction { - fn from(transaction: Transaction) -> Self { - YTransaction::new_from_strings( - transaction.to, - transaction.value, - transaction.data, - ) - .unwrap() - } -} - #[cfg(test)] mod tests { use alloy::{