Skip to content

Commit

Permalink
Merge branch 'main' of github.com:kkrt-labs/kakarot-rpc into dev/repl…
Browse files Browse the repository at this point in the history
…ace-blockingwrite-with-write
  • Loading branch information
ftupas committed Sep 17, 2023
2 parents aadf3b3 + 388f807 commit bc7ffbd
Show file tree
Hide file tree
Showing 51 changed files with 781 additions and 171 deletions.
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
31 changes: 7 additions & 24 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["crates/eth-rpc", "crates/core", "crates/hive-utils", "crates/test-utils"]
members = ["crates/eth-rpc", "crates/core", "crates/test-utils"]
resolver = "2"

[workspace.package]
Expand Down
80 changes: 48 additions & 32 deletions crates/core/src/client/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,17 @@ 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(|err| ConfigError::EnvironmentVariableSetWrong(var_name.into(), err.to_string()))
}

#[derive(Default, Clone, Debug)]
pub enum Network {
#[default]
Expand Down Expand Up @@ -54,26 +61,42 @@ impl Network {

#[derive(Default, Clone)]
/// Configuration for the Starknet RPC client.
pub struct StarknetConfig {
pub struct KakarotRpcConfig {
/// Starknet network.
pub network: Network,
/// Kakarot contract address.
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,
}

impl StarknetConfig {
pub fn new(network: Network, kakarot_address: FieldElement, proxy_account_class_hash: FieldElement) -> Self {
StarknetConfig { network, kakarot_address, proxy_account_class_hash }
impl KakarotRpcConfig {
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 {
KakarotRpcConfig {
network,
kakarot_address,
proxy_account_class_hash,
externally_owned_account_class_hash,
contract_account_class_hash,
}
}

/// 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 +108,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(KakarotRpcConfig::new(
network,
kakarot_address,
proxy_account_class_hash,
externally_owned_account_class_hash,
contract_account_class_hash,
))
}
}

Expand Down Expand Up @@ -139,7 +159,7 @@ impl JsonRpcClientBuilder<HttpTransport> {
/// let starknet_provider: JsonRpcClient<HttpTransport> =
/// JsonRpcClientBuilder::with_http(&config).unwrap().build();
/// ```
pub fn with_http(config: &StarknetConfig) -> Result<Self> {
pub fn with_http(config: &KakarotRpcConfig) -> Result<Self> {
let url = config.network.provider_url()?;
let transport = HttpTransport::new(url);
Ok(Self::new(transport))
Expand Down Expand Up @@ -170,18 +190,14 @@ 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 = 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}"
))
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(|err| {
ConfigError::EnvironmentVariableSetWrong("DEPLOYER_ACCOUNT_PRIVATE_KEY".into(), err.to_string())
})?;

let starknet_account_address = get_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}"
))
let starknet_account_address = env_var("DEPLOYER_ACCOUNT_ADDRESS")?;
let starknet_account_address = FieldElement::from_hex_be(&starknet_account_address).map_err(|err| {
ConfigError::EnvironmentVariableSetWrong("DEPLOYER_ACCOUNT_ADDRESS".into(), err.to_string())
})?;
(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
56 changes: 38 additions & 18 deletions crates/core/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use starknet::providers::{MaybeUnknownErrorCode, Provider, ProviderError, Starkn
use starknet::signers::LocalWallet;

use self::api::{KakarotEthApi, KakarotStarknetApi};
use self::config::{Network, StarknetConfig};
use self::config::{KakarotRpcConfig, Network};
use self::constants::gas::{BASE_FEE_PER_GAS, MAX_PRIORITY_FEE_PER_GAS, MINIMUM_GAS_FEE};
use self::constants::{
CHAIN_ID, CHUNK_SIZE_LIMIT, COUNTER_CALL_MAINNET, COUNTER_CALL_TESTNET1, COUNTER_CALL_TESTNET2,
Expand Down Expand Up @@ -71,14 +71,25 @@ pub struct KakarotClient<P: Provider + Send + Sync> {
impl<P: Provider + Send + Sync + 'static> KakarotClient<P> {
/// Create a new `KakarotClient`.
pub fn new(
starknet_config: StarknetConfig,
starknet_config: KakarotRpcConfig,
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 KakarotRpcConfig {
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,35 @@ 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
/// 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 {
// Get the implementation of the account
let account = KakarotAccount::new(starknet_address, &self.starknet_provider);
let class_hash = match account.implementation(&starknet_block_id).await {
Ok(class_hash) => class_hash,
Err(err) => match err {
EthApiError::RequestError(ProviderError::StarknetError(StarknetErrorWithMessage {
code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound),
..
}) => Ok(U256::from(0)),
_ => Err(EthApiError::from(err)),
})
})) => return Ok(U256::from(0)), // Return 0 if the account doesn't exist
_ => return Err(err), // Propagate the error
},
};

if class_hash == 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
let nonce = self.starknet_provider.get_nonce(starknet_block_id, starknet_address).await?;
Ok(Felt252Wrapper::from(nonce).into())
}
}

/// 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
22 changes: 21 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,26 @@ 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 class_hash = self.provider().call(request, block_id).await?;
let class_hash = *class_hash.first().ok_or_else(|| DataDecodingError::InvalidReturnArrayLength {
entrypoint: "get_implementation".into(),
expected: 1,
actual: 0,
})?;
Ok(class_hash)
}
}

pub struct KakarotAccount<'a, P> {
Expand Down
Loading

0 comments on commit bc7ffbd

Please sign in to comment.