Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dev: update nonce for evm accounts #513

Merged
merged 31 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8c7bba7
feat: add eoa and ca class hash to config
ftupas Sep 4, 2023
b3220a2
dev: update nonce method for eoa and ca
ftupas Sep 4, 2023
c6f3f70
mock get_implementation
greged93 Sep 4, 2023
80bb83a
dev: fix handling of get_implementation
ftupas Sep 4, 2023
053b146
dev: use contract account class hash
ftupas Sep 4, 2023
8d7eb25
dev: add get_nonce integration test
ftupas Sep 5, 2023
44698f1
dev: refactor env var
ftupas Sep 5, 2023
51e0328
dev: modify test
ftupas Sep 6, 2023
5825f25
refactor: use ethers
ftupas Sep 6, 2023
6e245b9
chore: fix comment
ftupas Sep 7, 2023
2642b35
Merge branch 'main' of github.com:kkrt-labs/kakarot-rpc into dev/upda…
ftupas Sep 8, 2023
90e457c
dev: rename to account due to entrypoints
ftupas Sep 8, 2023
d4552b9
Merge branch 'main' of github.com:kkrt-labs/kakarot-rpc into dev/upda…
ftupas Sep 11, 2023
6cfa7d4
bump: kakarot
ftupas Sep 11, 2023
65e88d9
Merge branch 'main' of github.com:kkrt-labs/kakarot-rpc into dev/upda…
ftupas Sep 12, 2023
e5968ec
Merge branch 'main' of github.com:kkrt-labs/kakarot-rpc into dev/upda…
ftupas Sep 12, 2023
11cc2ef
chore: avoid format! to improve error
ftupas Sep 12, 2023
4fdf011
chore: improve docs
ftupas Sep 12, 2023
22d397e
chore: use Felt252Wrapper
ftupas Sep 12, 2023
da4025e
Merge branch 'main' of github.com:kkrt-labs/kakarot-rpc into dev/upda…
ftupas Sep 12, 2023
f6aa585
chore: simplify conversion
ftupas Sep 13, 2023
88cb91c
test: add unit test for nonce and implementation
ftupas Sep 13, 2023
0f4a260
Merge branch 'main' of github.com:kkrt-labs/kakarot-rpc into dev/upda…
ftupas Sep 13, 2023
8520cb8
chore: remove padding
ftupas Sep 13, 2023
78f1234
Merge branch 'main' of github.com:kkrt-labs/kakarot-rpc into dev/upda…
ftupas Sep 14, 2023
ab1163b
dev: propagate error for EnvironmentVariableSetWrong
ftupas Sep 14, 2023
27878e9
dev: use proper implementation
ftupas Sep 14, 2023
2faef55
dev: handle other errors
ftupas Sep 14, 2023
0bf88f1
chore: improve naming
ftupas Sep 14, 2023
9de5e75
dev: remove redundant ContractNotFound
ftupas Sep 15, 2023
9349349
Merge branch 'main' of github.com:kkrt-labs/kakarot-rpc into dev/upda…
ftupas Sep 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ KAKAROT_HTTP_RPC_ADDRESS=0.0.0.0:3030
## check `./deployments/katana/deployments.json` after running `make devnet`
KAKAROT_ADDRESS=
PROXY_ACCOUNT_CLASS_HASH=0xba8f3f34eb92f56498fdf14ecac1f19d507dcc6859fa6d85eb8545370654bd
EXTERNALLY_OWNED_ACCOUNT_CLASS_HASH=0x4730612e9d26ebca8dd27be1af79cea613f7dee43f5b1584a172040e39f4063
CONTRACT_ACCOUNT_CLASS_HASH=0x5599cc38b46f92273f8c3566810c292352beb857816d0d90b05afa967995b21

## configurations for testing
COMPILED_KAKAROT_PATH=lib/kakarot/build
Expand Down
72 changes: 45 additions & 27 deletions crates/core/src/client/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ use url::Url;
use super::constants::{KATANA_RPC_URL, MADARA_RPC_URL};
use super::errors::ConfigError;

fn get_env_var(name: &str) -> Result<String, ConfigError> {
fn env_var(name: &str) -> Result<String, ConfigError> {
std::env::var(name).map_err(|_| ConfigError::EnvironmentVariableMissing(name.into()))
}

fn field_element_from_env(var_name: &str) -> Result<FieldElement, ConfigError> {
let env_var = env_var(var_name)?;

FieldElement::from_hex_be(&env_var).map_err(|_| ConfigError::EnvironmentVariableSetWrong(var_name.into(), env_var))
}

#[derive(Default, Clone, Debug)]
pub enum Network {
#[default]
Expand Down Expand Up @@ -61,19 +67,35 @@ pub struct StarknetConfig {
pub kakarot_address: FieldElement,
/// Proxy account class hash.
pub proxy_account_class_hash: FieldElement,
/// EOA class hash.
pub externally_owned_account_class_hash: FieldElement,
/// Contract Account class hash.
pub contract_account_class_hash: FieldElement,
ClementWalter marked this conversation as resolved.
Show resolved Hide resolved
Eikix marked this conversation as resolved.
Show resolved Hide resolved
}

impl StarknetConfig {
pub fn new(network: Network, kakarot_address: FieldElement, proxy_account_class_hash: FieldElement) -> Self {
StarknetConfig { network, kakarot_address, proxy_account_class_hash }
pub fn new(
network: Network,
kakarot_address: FieldElement,
proxy_account_class_hash: FieldElement,
externally_owned_account_class_hash: FieldElement,
contract_account_class_hash: FieldElement,
) -> Self {
StarknetConfig {
network,
kakarot_address,
proxy_account_class_hash,
externally_owned_account_class_hash,
contract_account_class_hash,
}
Eikix marked this conversation as resolved.
Show resolved Hide resolved
}

/// Create a new `StarknetConfig` from environment variables.
/// When using non-standard providers (i.e. not "katana", "madara", "mainnet"), the
/// `STARKNET_NETWORK` environment variable should be set the URL of a JsonRpc
/// starknet provider, e.g. https://starknet-goerli.g.alchemy.com/v2/some_key.
pub fn from_env() -> Result<Self, ConfigError> {
let network = get_env_var("STARKNET_NETWORK")?;
let network = env_var("STARKNET_NETWORK")?;
let network = match network.to_lowercase().as_str() {
"katana" => Network::Katana,
"madara" => Network::Madara,
Expand All @@ -85,21 +107,18 @@ impl StarknetConfig {
network_url => Network::JsonRpcProvider(Url::parse(network_url)?),
};

let kakarot_address = get_env_var("KAKAROT_ADDRESS")?;
let kakarot_address = FieldElement::from_hex_be(&kakarot_address).map_err(|_| {
ConfigError::EnvironmentVariableSetWrong(format!(
"KAKAROT_ADDRESS should be provided as a hex string, got {kakarot_address}"
))
})?;

let proxy_account_class_hash = get_env_var("PROXY_ACCOUNT_CLASS_HASH")?;
let proxy_account_class_hash = FieldElement::from_hex_be(&proxy_account_class_hash).map_err(|_| {
ConfigError::EnvironmentVariableSetWrong(format!(
"PROXY_ACCOUNT_CLASS_HASH should be provided as a hex string, got {proxy_account_class_hash}"
))
})?;

Ok(StarknetConfig::new(network, kakarot_address, proxy_account_class_hash))
let kakarot_address = field_element_from_env("KAKAROT_ADDRESS")?;
let proxy_account_class_hash = field_element_from_env("PROXY_ACCOUNT_CLASS_HASH")?;
let externally_owned_account_class_hash = field_element_from_env("EXTERNALLY_OWNED_ACCOUNT_CLASS_HASH")?;
let contract_account_class_hash = field_element_from_env("CONTRACT_ACCOUNT_CLASS_HASH")?;

Ok(StarknetConfig::new(
network,
kakarot_address,
proxy_account_class_hash,
externally_owned_account_class_hash,
contract_account_class_hash,
))
}
}

Expand Down Expand Up @@ -170,18 +189,17 @@ pub async fn get_starknet_account_from_env<P: Provider + Send + Sync + 'static>(
provider: Arc<P>,
) -> Result<SingleOwnerAccount<Arc<P>, LocalWallet>> {
let (starknet_account_private_key, starknet_account_address) = {
let starknet_account_private_key = get_env_var("DEPLOYER_ACCOUNT_PRIVATE_KEY")?;
let starknet_account_private_key = env_var("DEPLOYER_ACCOUNT_PRIVATE_KEY")?;
let starknet_account_private_key = FieldElement::from_hex_be(&starknet_account_private_key).map_err(|_| {
ConfigError::EnvironmentVariableSetWrong(format!(
"DEPLOYER_ACCOUNT_PRIVATE_KEY should be provided as a hex string, got {starknet_account_private_key}"
))
ConfigError::EnvironmentVariableSetWrong(
"DEPLOYER_ACCOUNT_PRIVATE_KEY".into(),
starknet_account_private_key,
)
})?;

let starknet_account_address = get_env_var("DEPLOYER_ACCOUNT_ADDRESS")?;
let starknet_account_address = env_var("DEPLOYER_ACCOUNT_ADDRESS")?;
let starknet_account_address = FieldElement::from_hex_be(&starknet_account_address).map_err(|_| {
ConfigError::EnvironmentVariableSetWrong(format!(
"DEPLOYER_ACCOUNT_ADDRESS should be provided as a hex string, got {starknet_account_private_key}"
))
ConfigError::EnvironmentVariableSetWrong("DEPLOYER_ACCOUNT_ADDRESS".into(), starknet_account_address)
})?;
(starknet_account_private_key, starknet_account_address)
};
Expand Down
2 changes: 2 additions & 0 deletions crates/core/src/client/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub mod selectors {

pub const BYTECODE: FieldElement = selector!("bytecode");
pub const STORAGE: FieldElement = selector!("storage");
pub const GET_IMPLEMENTATION: FieldElement = selector!("get_implementation");
pub const GET_NONCE: FieldElement = selector!("get_nonce");

pub const ETH_CALL: FieldElement = selector!("eth_call");
pub const ETH_SEND_TRANSACTION: FieldElement = selector!("eth_send_transaction");
Expand Down
4 changes: 2 additions & 2 deletions crates/core/src/client/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ pub enum ConfigError {
#[error("Missing mandatory environment variable: {0}")]
EnvironmentVariableMissing(String),
/// Environment variable set wrong error.
#[error("{0}")]
EnvironmentVariableSetWrong(String),
#[error("Environment variable {0} set wrong: {1}")]
EnvironmentVariableSetWrong(String, String),
/// Invalid URL error.
#[error("Invalid URL: {0}")]
InvalidUrl(#[from] url::ParseError),
Expand Down
62 changes: 44 additions & 18 deletions crates/core/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,21 @@ impl<P: Provider + Send + Sync + 'static> KakarotClient<P> {
starknet_provider: Arc<P>,
starknet_account: SingleOwnerAccount<Arc<P>, LocalWallet>,
) -> Self {
let StarknetConfig { kakarot_address, proxy_account_class_hash, network } = starknet_config;

let kakarot_contract =
KakarotContract::new(Arc::clone(&starknet_provider), kakarot_address, proxy_account_class_hash);
let StarknetConfig {
kakarot_address,
proxy_account_class_hash,
externally_owned_account_class_hash,
contract_account_class_hash,
network,
} = starknet_config;

let kakarot_contract = KakarotContract::new(
Arc::clone(&starknet_provider),
kakarot_address,
proxy_account_class_hash,
externally_owned_account_class_hash,
contract_account_class_hash,
);

Self { starknet_provider, network, kakarot_contract, deployer_account: starknet_account }
}
Expand Down Expand Up @@ -322,26 +333,41 @@ impl<P: Provider + Send + Sync + 'static> KakarotEthApi<P> for KakarotClient<P>
}

/// Returns the nonce for a given ethereum address
/// if it's an EOA, use native nonce and if it's a contract account, use managed nonce
Eikix marked this conversation as resolved.
Show resolved Hide resolved
/// if ethereum -> stark mapping doesn't exist in the starknet provider, we translate
/// ContractNotFound errors into zeros
async fn nonce(&self, ethereum_address: Address, block_id: BlockId) -> Result<U256, EthApiError<P::Error>> {
let starknet_block_id: StarknetBlockId = EthBlockId::new(block_id).try_into()?;
let starknet_address = self.compute_starknet_address(ethereum_address, &starknet_block_id).await?;

self.starknet_provider
.get_nonce(starknet_block_id, starknet_address)
.await
.map(|nonce| {
let nonce: Felt252Wrapper = nonce.into();
nonce.into()
})
.or_else(|err| match err {
ProviderError::StarknetError(StarknetErrorWithMessage {
code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound),
..
}) => Ok(U256::from(0)),
_ => Err(EthApiError::from(err)),
})
// Get the implementation of the account
let account = KakarotAccount::new(starknet_address, &self.starknet_provider);
let implementation = match account.implementation(&starknet_block_id).await {
Ok(class_hash) => class_hash,
Err(_) => return Ok(U256::from(0)), // Return 0 if the account doesn't exist
Eikix marked this conversation as resolved.
Show resolved Hide resolved
Eikix marked this conversation as resolved.
Show resolved Hide resolved
};

if implementation == self.kakarot_contract.contract_account_class_hash {
// Get the nonce of the contract account
let contract_account = ContractAccount::new(starknet_address, &self.starknet_provider);
contract_account.nonce(&starknet_block_id).await
} else {
// Get the nonce of the EOA
self.starknet_provider
.get_nonce(starknet_block_id, starknet_address)
.await
.map(|nonce| {
let nonce: Felt252Wrapper = nonce.into();
nonce.into()
})
.or_else(|err| match err {
ProviderError::StarknetError(StarknetErrorWithMessage {
code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound),
..
}) => Ok(U256::from(0)),
_ => Err(EthApiError::from(err)),
})
}
}

/// Returns the balance in Starknet's native token of a specific EVM address.
Expand Down
6 changes: 5 additions & 1 deletion crates/core/src/client/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ async fn test_block_number() {
#[tokio::test]
async fn test_nonce() {
// Given
let fixtures = fixtures(vec![wrap_kakarot!(JsonRpcMethod::GetNonce), AvailableFixtures::ComputeStarknetAddress]);
let fixtures = fixtures(vec![
wrap_kakarot!(JsonRpcMethod::GetNonce),
AvailableFixtures::ComputeStarknetAddress,
AvailableFixtures::GetImplementation,
]);
let client = init_mock_client(Some(fixtures));

// When
Expand Down
26 changes: 25 additions & 1 deletion crates/core/src/contracts/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use starknet::core::types::{BlockId, FunctionCall, StarknetError};
use starknet::providers::{MaybeUnknownErrorCode, Provider, ProviderError, StarknetErrorWithMessage};
use starknet_crypto::FieldElement;

use crate::client::constants::selectors::{BYTECODE, GET_EVM_ADDRESS};
use crate::client::constants::selectors::{BYTECODE, GET_EVM_ADDRESS, GET_IMPLEMENTATION};
use crate::client::errors::EthApiError;
use crate::client::helpers::{vec_felt_to_bytes, DataDecodingError};
use crate::models::felt::Felt252Wrapper;
Expand Down Expand Up @@ -54,6 +54,30 @@ pub trait Account<'a, P: Provider + Send + Sync + 'a> {
// TODO: Remove Manual Decoding
Ok(vec_felt_to_bytes(bytecode[1..].to_vec()))
}

/// Returns the class hash of account implementation of the contract.
async fn implementation(&self, block_id: &BlockId) -> Result<FieldElement, EthApiError<P::Error>> {
// Prepare the calldata for the get_implementation function call
let calldata = vec![];
let request = FunctionCall {
contract_address: self.starknet_address(),
entry_point_selector: GET_IMPLEMENTATION,
calldata,
};

// Make the function call to get the Starknet contract address
let implementation = self.provider().call(request, block_id).await?;

if implementation.len() != 1 {
return Err(EthApiError::DataDecodingError(DataDecodingError::InvalidReturnArrayLength {
entrypoint: "get_implementation".into(),
expected: 1,
actual: implementation.len(),
}));
}

Ok(implementation[0])
}
}

pub struct KakarotAccount<'a, P> {
Expand Down
25 changes: 24 additions & 1 deletion crates/core/src/contracts/contract_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use starknet::providers::Provider;
use starknet_crypto::FieldElement;

use super::account::Account;
use crate::client::constants::selectors::STORAGE;
use crate::client::constants::selectors::{GET_NONCE, STORAGE};
use crate::client::errors::EthApiError;
use crate::client::helpers::DataDecodingError;
use crate::models::felt::Felt252Wrapper;
Expand Down Expand Up @@ -58,4 +58,27 @@ impl<'a, P: Provider + Send + Sync> ContractAccount<'a, P> {
let value = Into::<U256>::into(low) + (Into::<U256>::into(high) << 128);
Ok(value)
}

/// Returns the nonce of the contract account.
Eikix marked this conversation as resolved.
Show resolved Hide resolved
/// In Kakarot EVM, there are two types of accounts: EOA and Contract Account.
/// EOA nonce is handled by Starknet protocol.
/// Contract Account nonce is handled by Kakarot through a dedicated storage, this function
/// returns that storage value.
pub async fn nonce(&self, block_id: &BlockId) -> Result<U256, EthApiError<P::Error>> {
Eikix marked this conversation as resolved.
Show resolved Hide resolved
// Prepare the calldata for the get_nonce function call
let calldata = vec![];
let request = FunctionCall { contract_address: self.address, entry_point_selector: GET_NONCE, calldata };

let result = self.provider.call(request, block_id).await?;
if result.len() != 1 {
return Err(DataDecodingError::InvalidReturnArrayLength {
entrypoint: "get_nonce".into(),
expected: 1,
actual: result.len(),
}
.into());
}

Ok(Felt252Wrapper::from(result[0]).into())
}
}
18 changes: 16 additions & 2 deletions crates/core/src/contracts/kakarot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,26 @@ use crate::client::waiter::TransactionWaiter;
pub struct KakarotContract<P> {
pub address: FieldElement,
pub proxy_account_class_hash: FieldElement,
pub externally_owned_account_class_hash: FieldElement,
pub contract_account_class_hash: FieldElement,
provider: Arc<P>,
}

impl<P: Provider + Send + Sync + 'static> KakarotContract<P> {
pub fn new(provider: Arc<P>, address: FieldElement, proxy_account_class_hash: FieldElement) -> Self {
Self { address, proxy_account_class_hash, provider }
pub fn new(
provider: Arc<P>,
address: FieldElement,
proxy_account_class_hash: FieldElement,
externally_owned_account_class_hash: FieldElement,
contract_account_class_hash: FieldElement,
) -> Self {
Self {
address,
proxy_account_class_hash,
externally_owned_account_class_hash,
contract_account_class_hash,
provider,
}
}

pub async fn compute_starknet_address(
Expand Down
Loading
Loading