From 44ccda2b92133761aa099aab0b9aa497a33c9c53 Mon Sep 17 00:00:00 2001 From: Jonathan LEI Date: Fri, 9 Aug 2024 18:22:18 +0800 Subject: [PATCH] feat: dynamic signer interactivity --- examples/mint_tokens.rs | 4 +-- examples/transfer_with_ledger.rs | 4 +-- starknet-accounts/src/account/declaration.rs | 28 +++++++++++++++---- starknet-accounts/src/account/execution.rs | 27 ++++++++++++------ starknet-accounts/src/account/mod.rs | 19 ++++++------- starknet-accounts/src/factory/argent.rs | 5 ++-- .../src/factory/open_zeppelin.rs | 5 ++-- starknet-accounts/src/lib.rs | 3 -- starknet-accounts/src/single_owner.rs | 10 +++---- .../tests/single_owner_account.rs | 4 +-- starknet-contract/src/factory.rs | 4 +-- .../src => starknet-core/src/types}/call.rs | 2 +- starknet-core/src/types/mod.rs | 3 ++ starknet-signers/src/ledger.rs | 4 +-- starknet-signers/src/lib.rs | 2 +- starknet-signers/src/local_wallet.rs | 4 +-- starknet-signers/src/signer.rs | 24 ++++++++++++++-- 17 files changed, 100 insertions(+), 52 deletions(-) rename {starknet-accounts/src => starknet-core/src/types}/call.rs (91%) diff --git a/examples/mint_tokens.rs b/examples/mint_tokens.rs index 64fc7ada..7a2deaec 100644 --- a/examples/mint_tokens.rs +++ b/examples/mint_tokens.rs @@ -1,8 +1,8 @@ use starknet::{ - accounts::{Account, Call, ExecutionEncoding, SingleOwnerAccount}, + accounts::{Account, ExecutionEncoding, SingleOwnerAccount}, core::{ chain_id, - types::{BlockId, BlockTag, Felt}, + types::{BlockId, BlockTag, Call, Felt}, utils::get_selector_from_name, }, providers::{ diff --git a/examples/transfer_with_ledger.rs b/examples/transfer_with_ledger.rs index 223da492..d6fd1fff 100644 --- a/examples/transfer_with_ledger.rs +++ b/examples/transfer_with_ledger.rs @@ -1,8 +1,8 @@ use starknet::{ - accounts::{Account, Call, ExecutionEncoding, SingleOwnerAccount}, + accounts::{Account, ExecutionEncoding, SingleOwnerAccount}, core::{ chain_id, - types::{BlockId, BlockTag, Felt}, + types::{BlockId, BlockTag, Call, Felt}, utils::get_selector_from_name, }, macros::felt, diff --git a/starknet-accounts/src/account/declaration.rs b/starknet-accounts/src/account/declaration.rs index 8efe9b59..cca83010 100644 --- a/starknet-accounts/src/account/declaration.rs +++ b/starknet-accounts/src/account/declaration.rs @@ -17,6 +17,7 @@ use starknet_core::{ }; use starknet_crypto::PoseidonHasher; use starknet_providers::Provider; +use starknet_signers::SignerInteractivityContext; use std::sync::Arc; /// Cairo string for "declare" @@ -209,7 +210,9 @@ where &self, nonce: Felt, ) -> Result> { - let skip_signature = self.account.is_signer_interactive(); + let skip_signature = self + .account + .is_signer_interactive(SignerInteractivityContext::Other); let prepared = PreparedDeclarationV2 { account: self.account, @@ -245,7 +248,10 @@ where skip_validate: bool, skip_fee_charge: bool, ) -> Result> { - let skip_signature = if self.account.is_signer_interactive() { + let skip_signature = if self + .account + .is_signer_interactive(SignerInteractivityContext::Other) + { // If signer is interactive, we would try to minimize signing requests. However, if the // caller has decided to not skip validation, it's best we still request a real // signature, as otherwise the simulation would most likely fail. @@ -519,7 +525,9 @@ where &self, nonce: Felt, ) -> Result> { - let skip_signature = self.account.is_signer_interactive(); + let skip_signature = self + .account + .is_signer_interactive(SignerInteractivityContext::Other); let prepared = PreparedDeclarationV3 { account: self.account, @@ -556,7 +564,10 @@ where skip_validate: bool, skip_fee_charge: bool, ) -> Result> { - let skip_signature = if self.account.is_signer_interactive() { + let skip_signature = if self + .account + .is_signer_interactive(SignerInteractivityContext::Other) + { // If signer is interactive, we would try to minimize signing requests. However, if the // caller has decided to not skip validation, it's best we still request a real // signature, as otherwise the simulation would most likely fail. @@ -753,7 +764,9 @@ where &self, nonce: Felt, ) -> Result> { - let skip_signature = self.account.is_signer_interactive(); + let skip_signature = self + .account + .is_signer_interactive(SignerInteractivityContext::Other); let prepared = PreparedLegacyDeclaration { account: self.account, @@ -788,7 +801,10 @@ where skip_validate: bool, skip_fee_charge: bool, ) -> Result> { - let skip_signature = if self.account.is_signer_interactive() { + let skip_signature = if self + .account + .is_signer_interactive(SignerInteractivityContext::Other) + { // If signer is interactive, we would try to minimize signing requests. However, if the // caller has decided to not skip validation, it's best we still request a real // signature, as otherwise the simulation would most likely fail. diff --git a/starknet-accounts/src/account/execution.rs b/starknet-accounts/src/account/execution.rs index b96ee505..c3f75f38 100644 --- a/starknet-accounts/src/account/execution.rs +++ b/starknet-accounts/src/account/execution.rs @@ -2,19 +2,20 @@ use super::{ super::NotPreparedError, Account, AccountError, ConnectedAccount, ExecutionV1, ExecutionV3, PreparedExecutionV1, PreparedExecutionV3, RawExecutionV1, RawExecutionV3, }; -use crate::{Call, ExecutionEncoder}; +use crate::ExecutionEncoder; use starknet_core::{ crypto::compute_hash_on_elements, types::{ BroadcastedInvokeTransaction, BroadcastedInvokeTransactionV1, - BroadcastedInvokeTransactionV3, BroadcastedTransaction, DataAvailabilityMode, FeeEstimate, - Felt, InvokeTransactionResult, ResourceBounds, ResourceBoundsMapping, SimulatedTransaction, - SimulationFlag, SimulationFlagForEstimateFee, + BroadcastedInvokeTransactionV3, BroadcastedTransaction, Call, DataAvailabilityMode, + FeeEstimate, Felt, InvokeTransactionResult, ResourceBounds, ResourceBoundsMapping, + SimulatedTransaction, SimulationFlag, SimulationFlagForEstimateFee, }, }; use starknet_crypto::PoseidonHasher; use starknet_providers::Provider; +use starknet_signers::SignerInteractivityContext; /// Cairo string for "invoke" const PREFIX_INVOKE: Felt = Felt::from_raw([ @@ -271,7 +272,9 @@ where &self, nonce: Felt, ) -> Result> { - let skip_signature = self.account.is_signer_interactive(); + let skip_signature = self + .account + .is_signer_interactive(SignerInteractivityContext::Execution { calls: &self.calls }); let prepared = PreparedExecutionV1 { account: self.account, @@ -309,7 +312,10 @@ where skip_validate: bool, skip_fee_charge: bool, ) -> Result> { - let skip_signature = if self.account.is_signer_interactive() { + let skip_signature = if self + .account + .is_signer_interactive(SignerInteractivityContext::Execution { calls: &self.calls }) + { // If signer is interactive, we would try to minimize signing requests. However, if the // caller has decided to not skip validation, it's best we still request a real // signature, as otherwise the simulation would most likely fail. @@ -498,7 +504,9 @@ where &self, nonce: Felt, ) -> Result> { - let skip_signature = self.account.is_signer_interactive(); + let skip_signature = self + .account + .is_signer_interactive(SignerInteractivityContext::Execution { calls: &self.calls }); let prepared = PreparedExecutionV3 { account: self.account, @@ -537,7 +545,10 @@ where skip_validate: bool, skip_fee_charge: bool, ) -> Result> { - let skip_signature = if self.account.is_signer_interactive() { + let skip_signature = if self + .account + .is_signer_interactive(SignerInteractivityContext::Execution { calls: &self.calls }) + { // If signer is interactive, we would try to minimize signing requests. However, if the // caller has decided to not skip validation, it's best we still request a real // signature, as otherwise the simulation would most likely fail. diff --git a/starknet-accounts/src/account/mod.rs b/starknet-accounts/src/account/mod.rs index a3e19f54..4979377d 100644 --- a/starknet-accounts/src/account/mod.rs +++ b/starknet-accounts/src/account/mod.rs @@ -1,12 +1,11 @@ -use crate::Call; - use async_trait::async_trait; use auto_impl::auto_impl; use starknet_core::types::{ contract::{legacy::LegacyContractClass, CompressProgramError, ComputeClassHashError}, - BlockId, BlockTag, Felt, FlattenedSierraClass, + BlockId, BlockTag, Call, Felt, FlattenedSierraClass, }; use starknet_providers::{Provider, ProviderError}; +use starknet_signers::SignerInteractivityContext; use std::{error::Error, sync::Arc}; mod declaration; @@ -89,7 +88,7 @@ pub trait Account: ExecutionEncoder + Sized { /// /// This affects how an account makes decision on whether to request a real signature for /// estimation/simulation purposes. - fn is_signer_interactive(&self) -> bool; + fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool; /// Generates an instance of [`ExecutionV1`] for sending `INVOKE` v1 transactions. Pays /// transaction fees in `ETH`. @@ -465,8 +464,8 @@ where .await } - fn is_signer_interactive(&self) -> bool { - (*self).is_signer_interactive() + fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool { + (*self).is_signer_interactive(context) } } @@ -532,8 +531,8 @@ where .await } - fn is_signer_interactive(&self) -> bool { - self.as_ref().is_signer_interactive() + fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool { + self.as_ref().is_signer_interactive(context) } } @@ -599,8 +598,8 @@ where .await } - fn is_signer_interactive(&self) -> bool { - self.as_ref().is_signer_interactive() + fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool { + self.as_ref().is_signer_interactive(context) } } diff --git a/starknet-accounts/src/factory/argent.rs b/starknet-accounts/src/factory/argent.rs index 23bf3d7f..0a3b3a6c 100644 --- a/starknet-accounts/src/factory/argent.rs +++ b/starknet-accounts/src/factory/argent.rs @@ -6,7 +6,7 @@ use crate::{ use async_trait::async_trait; use starknet_core::types::{BlockId, BlockTag, Felt}; use starknet_providers::Provider; -use starknet_signers::Signer; +use starknet_signers::{Signer, SignerInteractivityContext}; /// [`AccountFactory`] implementation for deploying `Argent X` account contracts. #[derive(Debug)] @@ -78,7 +78,8 @@ where } fn is_signer_interactive(&self) -> bool { - self.signer.is_interactive() + self.signer + .is_interactive(SignerInteractivityContext::Other) } fn block_id(&self) -> BlockId { diff --git a/starknet-accounts/src/factory/open_zeppelin.rs b/starknet-accounts/src/factory/open_zeppelin.rs index 90c32722..6c20a9f4 100644 --- a/starknet-accounts/src/factory/open_zeppelin.rs +++ b/starknet-accounts/src/factory/open_zeppelin.rs @@ -6,7 +6,7 @@ use crate::{ use async_trait::async_trait; use starknet_core::types::{BlockId, BlockTag, Felt}; use starknet_providers::Provider; -use starknet_signers::Signer; +use starknet_signers::{Signer, SignerInteractivityContext}; /// [`AccountFactory`] implementation for deploying `OpenZeppelin` account contracts. #[derive(Debug)] @@ -75,7 +75,8 @@ where } fn is_signer_interactive(&self) -> bool { - self.signer.is_interactive() + self.signer + .is_interactive(SignerInteractivityContext::Other) } fn block_id(&self) -> BlockId { diff --git a/starknet-accounts/src/lib.rs b/starknet-accounts/src/lib.rs index 5da9d090..869e5c90 100644 --- a/starknet-accounts/src/lib.rs +++ b/starknet-accounts/src/lib.rs @@ -10,9 +10,6 @@ pub use account::{ RawDeclarationV3, RawExecutionV1, RawExecutionV3, RawLegacyDeclaration, }; -mod call; -pub use call::Call; - mod factory; pub use factory::{ argent::ArgentAccountFactory, open_zeppelin::OpenZeppelinAccountFactory, AccountDeploymentV1, diff --git a/starknet-accounts/src/single_owner.rs b/starknet-accounts/src/single_owner.rs index 1c15bee8..d954bd70 100644 --- a/starknet-accounts/src/single_owner.rs +++ b/starknet-accounts/src/single_owner.rs @@ -1,12 +1,12 @@ use crate::{ - Account, Call, ConnectedAccount, ExecutionEncoder, RawDeclarationV2, RawDeclarationV3, + Account, ConnectedAccount, ExecutionEncoder, RawDeclarationV2, RawDeclarationV3, RawExecutionV1, RawExecutionV3, RawLegacyDeclaration, }; use async_trait::async_trait; -use starknet_core::types::{contract::ComputeClassHashError, BlockId, BlockTag, Felt}; +use starknet_core::types::{contract::ComputeClassHashError, BlockId, BlockTag, Call, Felt}; use starknet_providers::Provider; -use starknet_signers::Signer; +use starknet_signers::{Signer, SignerInteractivityContext}; /// A generic [`Account`] implementation for controlling account contracts that only have one signer /// using ECDSA the STARK curve. @@ -177,8 +177,8 @@ where Ok(vec![signature.r, signature.s]) } - fn is_signer_interactive(&self) -> bool { - self.signer.is_interactive() + fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool { + self.signer.is_interactive(context) } } diff --git a/starknet-accounts/tests/single_owner_account.rs b/starknet-accounts/tests/single_owner_account.rs index bc1febe8..1598bdc9 100644 --- a/starknet-accounts/tests/single_owner_account.rs +++ b/starknet-accounts/tests/single_owner_account.rs @@ -1,5 +1,5 @@ use starknet_accounts::{ - Account, AccountError, Call, ConnectedAccount, ExecutionEncoding, SingleOwnerAccount, + Account, AccountError, ConnectedAccount, ExecutionEncoding, SingleOwnerAccount, }; use starknet_core::{ types::{ @@ -7,7 +7,7 @@ use starknet_core::{ legacy::{LegacyContractClass, RawLegacyAbiEntry, RawLegacyFunction}, SierraClass, }, - BlockId, BlockTag, Felt, StarknetError, + BlockId, BlockTag, Call, Felt, StarknetError, }, utils::get_selector_from_name, }; diff --git a/starknet-contract/src/factory.rs b/starknet-contract/src/factory.rs index 57e25f61..65416722 100644 --- a/starknet-contract/src/factory.rs +++ b/starknet-contract/src/factory.rs @@ -1,6 +1,6 @@ -use starknet_accounts::{Account, AccountError, Call, ConnectedAccount, ExecutionV1, ExecutionV3}; +use starknet_accounts::{Account, AccountError, ConnectedAccount, ExecutionV1, ExecutionV3}; use starknet_core::{ - types::{FeeEstimate, Felt, InvokeTransactionResult, SimulatedTransaction}, + types::{Call, FeeEstimate, Felt, InvokeTransactionResult, SimulatedTransaction}, utils::{get_udc_deployed_address, UdcUniqueSettings, UdcUniqueness}, }; diff --git a/starknet-accounts/src/call.rs b/starknet-core/src/types/call.rs similarity index 91% rename from starknet-accounts/src/call.rs rename to starknet-core/src/types/call.rs index 0788e011..0971f55e 100644 --- a/starknet-accounts/src/call.rs +++ b/starknet-core/src/types/call.rs @@ -1,4 +1,4 @@ -use starknet_core::types::Felt; +use crate::types::Felt; /// A contract call as part of a multi-call execution request. #[derive(Debug, Clone)] diff --git a/starknet-core/src/types/mod.rs b/starknet-core/src/types/mod.rs index 3e7756b4..5f8e6db3 100644 --- a/starknet-core/src/types/mod.rs +++ b/starknet-core/src/types/mod.rs @@ -62,6 +62,9 @@ pub use receipt_block::ReceiptBlock; mod msg; pub use msg::MsgToL2; +mod call; +pub use call::Call; + // TODO: move generated request code to `starknet-providers` /// Module containing JSON-RPC request types. pub mod requests; diff --git a/starknet-signers/src/ledger.rs b/starknet-signers/src/ledger.rs index fd139b06..2d385242 100644 --- a/starknet-signers/src/ledger.rs +++ b/starknet-signers/src/ledger.rs @@ -8,7 +8,7 @@ use crypto_bigint::{ArrayEncoding, U256}; use semver::Version; use starknet_core::{crypto::Signature, types::Felt}; -use crate::{Signer, VerifyingKey}; +use crate::{Signer, SignerInteractivityContext, VerifyingKey}; pub use coins_bip32::path::DerivationPath; @@ -128,7 +128,7 @@ impl Signer for LedgerSigner { self.app.sign_hash(self.derivation_path.clone(), hash).await } - fn is_interactive(&self) -> bool { + fn is_interactive(&self, _context: SignerInteractivityContext<'_>) -> bool { true } } diff --git a/starknet-signers/src/lib.rs b/starknet-signers/src/lib.rs index 59f9d292..14ffc9a7 100644 --- a/starknet-signers/src/lib.rs +++ b/starknet-signers/src/lib.rs @@ -9,7 +9,7 @@ pub use key_pair::{SigningKey, VerifyingKey}; pub use key_pair::KeystoreError; mod signer; -pub use signer::Signer; +pub use signer::{Signer, SignerInteractivityContext}; /// Module containing types related to the use of a simple in-memory signer. pub mod local_wallet; diff --git a/starknet-signers/src/local_wallet.rs b/starknet-signers/src/local_wallet.rs index 0ad14c61..4f220fbe 100644 --- a/starknet-signers/src/local_wallet.rs +++ b/starknet-signers/src/local_wallet.rs @@ -1,4 +1,4 @@ -use crate::{Infallible, Signer, SigningKey, VerifyingKey}; +use crate::{Infallible, Signer, SignerInteractivityContext, SigningKey, VerifyingKey}; use async_trait::async_trait; use starknet_core::{ @@ -42,7 +42,7 @@ impl Signer for LocalWallet { Ok(self.private_key.sign(hash)?) } - fn is_interactive(&self) -> bool { + fn is_interactive(&self, _context: SignerInteractivityContext<'_>) -> bool { false } } diff --git a/starknet-signers/src/signer.rs b/starknet-signers/src/signer.rs index 2a7d3a46..f7569b10 100644 --- a/starknet-signers/src/signer.rs +++ b/starknet-signers/src/signer.rs @@ -2,7 +2,10 @@ use crate::VerifyingKey; use async_trait::async_trait; use auto_impl::auto_impl; -use starknet_core::{crypto::Signature, types::Felt}; +use starknet_core::{ + crypto::Signature, + types::{Call, Felt}, +}; use std::error::Error; /// Any signer that can provide a public key as [`Felt`], and sign a raw hash for a signature @@ -38,5 +41,22 @@ pub trait Signer { /// non-interactive signers, it's fine to sign multiple times for getting the most accurate /// estimation/simulation possible; but with interactive signers, they would accept less /// accurate results to minimize signing requests. - fn is_interactive(&self) -> bool; + fn is_interactive(&self, context: SignerInteractivityContext<'_>) -> bool; +} + +/// Context for helping signer implementations make decisions on whether to act interactively or +/// not, useful for signers with dynamic interactivity. +/// +/// This type only exposes execution details as context, with everything else falling under the +/// `Other` variant, as it's deemed very much pointless to act differently in those scenarios. +/// When an execution is requested, only the list of calls is exposed. +#[derive(Debug, Clone, Copy)] +pub enum SignerInteractivityContext<'a> { + /// An execution is being requested. + Execution { + /// The list of calls being authorized. + calls: &'a [Call], + }, + /// A class declaration or account deployment is being requested. + Other, }