diff --git a/relayer/Cargo.lock b/relayer/Cargo.lock index 9f27787e..eaebccd0 100644 --- a/relayer/Cargo.lock +++ b/relayer/Cargo.lock @@ -1655,6 +1655,7 @@ dependencies = [ "hermes-cosmos-integration-tests", "hermes-error", "hermes-relayer-components", + "hermes-runtime-components", "hermes-starknet-chain-components", "hermes-starknet-chain-context", "starknet", diff --git a/relayer/crates/starknet-chain-components/src/components.rs b/relayer/crates/starknet-chain-components/src/components.rs index e3f598c7..d9a18360 100644 --- a/relayer/crates/starknet-chain-components/src/components.rs +++ b/relayer/crates/starknet-chain-components/src/components.rs @@ -1,10 +1,14 @@ use cgp_core::prelude::*; +pub use hermes_relayer_components::transaction::traits::types::tx_hash::TransactionHashTypeComponent; use crate::impls::contract::call::CallStarknetContract; +use crate::impls::contract::invoke::InvokeStarknetContract; use crate::impls::types::address::ProvideFeltAddressType; use crate::impls::types::blob::ProvideFeltBlobType; use crate::impls::types::method::ProvideFeltMethodSelector; +use crate::impls::types::tx_hash::ProvideFeltTxHash; pub use crate::traits::contract::call::ContractCallerComponent; +pub use crate::traits::contract::invoke::ContractInvokerComponent; pub use crate::traits::types::address::AddressTypeComponent; pub use crate::traits::types::blob::BlobTypeComponent; pub use crate::traits::types::method::MethodSelectorTypeComponent; @@ -15,9 +19,13 @@ define_components! { ProvideFeltAddressType, BlobTypeComponent: ProvideFeltBlobType, + TransactionHashTypeComponent: + ProvideFeltTxHash, MethodSelectorTypeComponent: ProvideFeltMethodSelector, ContractCallerComponent: CallStarknetContract, + ContractInvokerComponent: + InvokeStarknetContract, } } diff --git a/relayer/crates/starknet-chain-components/src/impls/account.rs b/relayer/crates/starknet-chain-components/src/impls/account.rs new file mode 100644 index 00000000..38ed850e --- /dev/null +++ b/relayer/crates/starknet-chain-components/src/impls/account.rs @@ -0,0 +1,29 @@ +use std::marker::PhantomData; + +use cgp_core::prelude::*; +use starknet::accounts::ConnectedAccount; + +use crate::traits::account::{ + HasStarknetAccountType, ProvideStarknetAccountType, StarknetAccountGetter, +}; + +pub struct GetStarknetAccountField(pub PhantomData); + +impl ProvideStarknetAccountType for GetStarknetAccountField +where + Chain: Async + HasField, + Tag: Async, + Chain::Field: Async + ConnectedAccount, +{ + type Account = Chain::Field; +} + +impl StarknetAccountGetter for GetStarknetAccountField +where + Chain: Async + HasStarknetAccountType + HasField, + Tag: Async, +{ + fn account(chain: &Chain) -> &Chain::Account { + chain.get_field(PhantomData) + } +} diff --git a/relayer/crates/starknet-chain-components/src/impls/contract/call.rs b/relayer/crates/starknet-chain-components/src/impls/contract/call.rs index c8e5711b..d3a284ca 100644 --- a/relayer/crates/starknet-chain-components/src/impls/contract/call.rs +++ b/relayer/crates/starknet-chain-components/src/impls/contract/call.rs @@ -2,8 +2,8 @@ use cgp_core::error::CanRaiseError; use starknet::core::types::{BlockId, BlockTag, Felt, FunctionCall}; use starknet::providers::{Provider, ProviderError}; -use crate::traits::client::HasJsonRpcClient; use crate::traits::contract::call::ContractCaller; +use crate::traits::provider::HasStarknetProvider; use crate::traits::types::address::HasAddressType; use crate::traits::types::blob::HasBlobType; use crate::traits::types::method::HasMethodSelectorType; @@ -15,7 +15,7 @@ where Chain: HasAddressType
+ HasMethodSelectorType + HasBlobType> - + HasJsonRpcClient + + HasStarknetProvider + CanRaiseError, { async fn call_contract( @@ -27,7 +27,7 @@ where let block_id = BlockId::Tag(BlockTag::Pending); let res = chain - .json_rpc_client() + .provider() .call( FunctionCall { contract_address: *contract_address, diff --git a/relayer/crates/starknet-chain-components/src/impls/contract/invoke.rs b/relayer/crates/starknet-chain-components/src/impls/contract/invoke.rs new file mode 100644 index 00000000..f99e70ab --- /dev/null +++ b/relayer/crates/starknet-chain-components/src/impls/contract/invoke.rs @@ -0,0 +1,48 @@ +use hermes_relayer_components::transaction::traits::types::tx_hash::HasTransactionHashType; +use starknet::accounts::{Account, Call}; +use starknet::core::types::Felt; + +use crate::traits::account::{CanRaiseAccountErrors, HasStarknetAccount}; +use crate::traits::contract::invoke::ContractInvoker; +use crate::traits::provider::HasStarknetProvider; +use crate::traits::types::address::HasAddressType; +use crate::traits::types::blob::HasBlobType; +use crate::traits::types::method::HasMethodSelectorType; + +pub struct InvokeStarknetContract; + +impl ContractInvoker for InvokeStarknetContract +where + Chain: HasAddressType
+ + HasMethodSelectorType + + HasBlobType> + + HasTransactionHashType + + HasStarknetProvider + + HasStarknetAccount + + CanRaiseAccountErrors, +{ + async fn invoke_contract( + chain: &Chain, + contract_address: &Felt, + entry_point_selector: &Felt, + calldata: &Vec, + ) -> Result { + let account = chain.account(); + + let call = Call { + to: *contract_address, + selector: *entry_point_selector, + calldata: calldata.clone(), + }; + + let execution = account.execute_v3(vec![call]); + + let tx_hash = execution + .send() + .await + .map_err(Chain::raise_error)? + .transaction_hash; + + Ok(tx_hash) + } +} diff --git a/relayer/crates/starknet-chain-components/src/impls/contract/mod.rs b/relayer/crates/starknet-chain-components/src/impls/contract/mod.rs index 8b6c1d45..3cdfc106 100644 --- a/relayer/crates/starknet-chain-components/src/impls/contract/mod.rs +++ b/relayer/crates/starknet-chain-components/src/impls/contract/mod.rs @@ -1 +1,2 @@ pub mod call; +pub mod invoke; diff --git a/relayer/crates/starknet-chain-components/src/impls/mod.rs b/relayer/crates/starknet-chain-components/src/impls/mod.rs index 9cf6c590..f51c05c0 100644 --- a/relayer/crates/starknet-chain-components/src/impls/mod.rs +++ b/relayer/crates/starknet-chain-components/src/impls/mod.rs @@ -1,2 +1,4 @@ +pub mod account; pub mod contract; +pub mod provider; pub mod types; diff --git a/relayer/crates/starknet-chain-components/src/impls/provider.rs b/relayer/crates/starknet-chain-components/src/impls/provider.rs new file mode 100644 index 00000000..daa3530c --- /dev/null +++ b/relayer/crates/starknet-chain-components/src/impls/provider.rs @@ -0,0 +1,29 @@ +use std::marker::PhantomData; + +use cgp_core::prelude::*; +use starknet::providers::Provider; + +use crate::traits::provider::{ + HasStarknetProviderType, ProvideStarknetProviderType, StarknetProviderGetter, +}; + +pub struct GetStarknetProviderField(pub PhantomData); + +impl ProvideStarknetProviderType for GetStarknetProviderField +where + Chain: Async + HasField, + Tag: Async, + Chain::Field: Async + Provider, +{ + type Provider = Chain::Field; +} + +impl StarknetProviderGetter for GetStarknetProviderField +where + Chain: Async + HasStarknetProviderType + HasField, + Tag: Async, +{ + fn provider(chain: &Chain) -> &Chain::Provider { + chain.get_field(PhantomData) + } +} diff --git a/relayer/crates/starknet-chain-components/src/impls/types/mod.rs b/relayer/crates/starknet-chain-components/src/impls/types/mod.rs index 7e4d9878..cb85e399 100644 --- a/relayer/crates/starknet-chain-components/src/impls/types/mod.rs +++ b/relayer/crates/starknet-chain-components/src/impls/types/mod.rs @@ -1,3 +1,4 @@ pub mod address; pub mod blob; pub mod method; +pub mod tx_hash; diff --git a/relayer/crates/starknet-chain-components/src/impls/types/tx_hash.rs b/relayer/crates/starknet-chain-components/src/impls/types/tx_hash.rs new file mode 100644 index 00000000..4c6f68d2 --- /dev/null +++ b/relayer/crates/starknet-chain-components/src/impls/types/tx_hash.rs @@ -0,0 +1,9 @@ +use cgp_core::Async; +use hermes_relayer_components::transaction::traits::types::tx_hash::ProvideTransactionHashType; +use starknet::core::types::Felt; + +pub struct ProvideFeltTxHash; + +impl ProvideTransactionHashType for ProvideFeltTxHash { + type TxHash = Felt; +} diff --git a/relayer/crates/starknet-chain-components/src/traits/account.rs b/relayer/crates/starknet-chain-components/src/traits/account.rs new file mode 100644 index 00000000..1210237c --- /dev/null +++ b/relayer/crates/starknet-chain-components/src/traits/account.rs @@ -0,0 +1,26 @@ +use cgp_core::prelude::*; +use starknet::accounts::{Account, AccountError, ConnectedAccount}; + +#[derive_component(StarknetAccountTypeComponent, ProvideStarknetAccountType)] +pub trait HasStarknetAccountType: Async { + type Account: Async + ConnectedAccount; +} + +#[derive_component(StarknetAccountGetterComponent, StarknetAccountGetter)] +pub trait HasStarknetAccount: HasStarknetAccountType { + fn account(&self) -> &Self::Account; +} + +pub trait CanRaiseAccountErrors: + HasStarknetAccountType + + CanRaiseError<::SignError> + + CanRaiseError::SignError>> +{ +} + +impl CanRaiseAccountErrors for Chain where + Chain: HasStarknetAccountType + + CanRaiseError<::SignError> + + CanRaiseError::SignError>> +{ +} diff --git a/relayer/crates/starknet-chain-components/src/traits/mod.rs b/relayer/crates/starknet-chain-components/src/traits/mod.rs index 27eafec6..46da4153 100644 --- a/relayer/crates/starknet-chain-components/src/traits/mod.rs +++ b/relayer/crates/starknet-chain-components/src/traits/mod.rs @@ -1,3 +1,5 @@ +pub mod account; pub mod client; pub mod contract; +pub mod provider; pub mod types; diff --git a/relayer/crates/starknet-chain-components/src/traits/provider.rs b/relayer/crates/starknet-chain-components/src/traits/provider.rs new file mode 100644 index 00000000..dc1e6731 --- /dev/null +++ b/relayer/crates/starknet-chain-components/src/traits/provider.rs @@ -0,0 +1,12 @@ +use cgp_core::prelude::*; +use starknet::providers::Provider; + +#[derive_component(StarknetProviderTypeComponent, ProvideStarknetProviderType)] +pub trait HasStarknetProviderType: Async { + type Provider: Async + Provider; +} + +#[derive_component(StarknetProviderGetterComponent, StarknetProviderGetter)] +pub trait HasStarknetProvider: HasStarknetProviderType { + fn provider(&self) -> &Self::Provider; +} diff --git a/relayer/crates/starknet-chain-context/src/contexts/chain.rs b/relayer/crates/starknet-chain-context/src/contexts/chain.rs index 2c803abb..2b639bfa 100644 --- a/relayer/crates/starknet-chain-context/src/contexts/chain.rs +++ b/relayer/crates/starknet-chain-context/src/contexts/chain.rs @@ -1,33 +1,39 @@ +use std::sync::Arc; + use cgp_core::error::{DelegateErrorRaiser, ErrorRaiserComponent, ErrorTypeComponent}; use cgp_core::prelude::*; use hermes_error::impls::ProvideHermesError; use hermes_starknet_chain_components::components::*; +use hermes_starknet_chain_components::impls::account::GetStarknetAccountField; +use hermes_starknet_chain_components::impls::provider::GetStarknetProviderField; +use hermes_starknet_chain_components::traits::account::{ + HasStarknetAccount, StarknetAccountGetterComponent, StarknetAccountTypeComponent, +}; use hermes_starknet_chain_components::traits::client::JsonRpcClientGetter; use hermes_starknet_chain_components::traits::contract::call::CanCallContract; +use hermes_starknet_chain_components::traits::contract::invoke::CanInvokeContract; +use hermes_starknet_chain_components::traits::provider::{ + HasStarknetProvider, StarknetProviderGetterComponent, StarknetProviderTypeComponent, +}; use hermes_starknet_chain_components::traits::types::address::HasAddressType; use hermes_starknet_chain_components::traits::types::blob::HasBlobType; use hermes_starknet_chain_components::traits::types::method::HasMethodSelectorType; +use starknet::accounts::SingleOwnerAccount; use starknet::core::types::Felt; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; -use url::Url; +use starknet::signers::LocalWallet; use crate::impls::error::HandleStarknetError; +#[derive(HasField)] pub struct StarknetChain { - pub rpc_client: JsonRpcClient, + pub rpc_client: Arc>, + pub account: SingleOwnerAccount>, LocalWallet>, } pub struct StarknetChainContextComponents; -impl StarknetChain { - pub fn new(json_rpc_url: Url) -> Self { - let rpc_client = JsonRpcClient::new(HttpTransport::new(json_rpc_url)); - - Self { rpc_client } - } -} - impl HasComponents for StarknetChain { type Components = StarknetChainContextComponents; } @@ -36,6 +42,16 @@ delegate_components! { StarknetChainContextComponents { ErrorTypeComponent: ProvideHermesError, ErrorRaiserComponent: DelegateErrorRaiser, + [ + StarknetProviderTypeComponent, + StarknetProviderGetterComponent, + ]: + GetStarknetProviderField, + [ + StarknetAccountTypeComponent, + StarknetAccountGetterComponent, + ]: + GetStarknetAccountField, } } @@ -57,7 +73,10 @@ pub trait CanUseStarknetChain: HasAddressType
+ HasMethodSelectorType + HasBlobType> + + HasStarknetProvider + + HasStarknetAccount + CanCallContract + + CanInvokeContract { } diff --git a/relayer/crates/starknet-chain-context/src/impls/error.rs b/relayer/crates/starknet-chain-context/src/impls/error.rs index 63863d0c..f7c1b822 100644 --- a/relayer/crates/starknet-chain-context/src/impls/error.rs +++ b/relayer/crates/starknet-chain-context/src/impls/error.rs @@ -9,10 +9,14 @@ use hermes_error::handlers::report::ReportError; use hermes_error::handlers::wrap::WrapErrorDetail; use hermes_error::traits::wrap::WrapError; use hermes_error::types::Error; +use starknet::accounts::{single_owner, AccountError}; use starknet::providers::ProviderError; +use starknet::signers::local_wallet; pub struct HandleStarknetError; +pub type SignError = single_owner::SignError; + delegate_components! { HandleStarknetError { Error: ReturnError, @@ -20,6 +24,8 @@ delegate_components! { [ Report, ProviderError, + SignError, + AccountError, ]: ReportError, [ <'a> &'a str, diff --git a/relayer/crates/starknet-integration-tests/Cargo.toml b/relayer/crates/starknet-integration-tests/Cargo.toml index 44252aae..e65d9566 100644 --- a/relayer/crates/starknet-integration-tests/Cargo.toml +++ b/relayer/crates/starknet-integration-tests/Cargo.toml @@ -13,6 +13,7 @@ readme = "README.md" cgp-core = { workspace = true } hermes-error = { workspace = true } hermes-relayer-components = { workspace = true } +hermes-runtime-components = { workspace = true } hermes-starknet-chain-components = { workspace = true } hermes-starknet-chain-context = { workspace = true } hermes-cosmos-integration-tests = { workspace = true } diff --git a/relayer/crates/starknet-integration-tests/tests/chain.rs b/relayer/crates/starknet-integration-tests/tests/chain.rs index 183aa535..da622e18 100644 --- a/relayer/crates/starknet-integration-tests/tests/chain.rs +++ b/relayer/crates/starknet-integration-tests/tests/chain.rs @@ -1,8 +1,18 @@ +use core::time::Duration; +use std::sync::Arc; + use hermes_cosmos_integration_tests::init::init_test_runtime; use hermes_error::types::Error; +use hermes_runtime_components::traits::sleep::CanSleep; use hermes_starknet_chain_components::traits::contract::call::CanCallContract; +use hermes_starknet_chain_components::traits::contract::invoke::CanInvokeContract; use hermes_starknet_chain_context::contexts::chain::StarknetChain; +use starknet::accounts::{ExecutionEncoding, SingleOwnerAccount}; use starknet::macros::{felt, selector}; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::{JsonRpcClient, Provider}; +use starknet::signers::{LocalWallet, SigningKey}; +use url::Url; // Note: the test needs to be run with starknet-devnet-rs with the seed 0: // @@ -15,7 +25,29 @@ fn test_starknet_chain_client() { .runtime .clone() .block_on(async move { - let chain = StarknetChain::new("http://localhost:5050/".try_into().unwrap()); + let json_rpc_url = Url::try_from("http://localhost:5050/")?; + + let signing_key = felt!("0x71d7bb07b9a64f6f78ac4c816aff4da9"); + + let account_address = + felt!("0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691"); + + let rpc_client = Arc::new(JsonRpcClient::new(HttpTransport::new(json_rpc_url))); + + let chain_id = rpc_client.chain_id().await?; + + let account = SingleOwnerAccount::new( + rpc_client.clone(), + LocalWallet::from_signing_key(SigningKey::from_secret_scalar(signing_key)), + account_address, + chain_id, + ExecutionEncoding::New, + ); + + let chain = StarknetChain { + rpc_client, + account, + }; /* Test running a query that is equivalent to the following starkli call: @@ -38,6 +70,34 @@ fn test_starknet_chain_client() { println!("query balance_of result: {:?}", result); + let tx_hash = chain + .invoke_contract( + &felt!("0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d"), + &selector!("transfer"), + &vec![ + felt!("0x78662e7352d062084b0010068b99288486c2d8b914f6e2a55ce945f8792c8b1"), + felt!("0x100"), + felt!("0x0"), + ], + ) + .await?; + + println!("invoke result tx hash: {}", tx_hash); + + runtime.sleep(Duration::from_secs(1)).await; + + let result = chain + .call_contract( + &felt!("0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d"), + &selector!("balance_of"), + &vec![felt!( + "0x78662e7352d062084b0010068b99288486c2d8b914f6e2a55ce945f8792c8b1" + )], + ) + .await?; + + println!("query balance_of result: {:?}", result); + >::Ok(()) }) .unwrap();