Skip to content
This repository was archived by the owner on Feb 12, 2025. It is now read-only.

feat: execute from outside #723

Merged
merged 16 commits into from
Jul 25, 2024
Merged
Changes from all commits
Commits
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
21 changes: 16 additions & 5 deletions crates/ef-testing/src/evm_sequencer/constants.rs
Original file line number Diff line number Diff line change
@@ -3,14 +3,16 @@ use std::collections::HashMap;
use lazy_static::lazy_static;
use reth_primitives::alloy_primitives::{address, Address};
use serde::de::DeserializeOwned;
use starknet::core::types::contract::legacy::LegacyContractClass;
use starknet::core::types::contract::CompiledClass;
use starknet_api::felt;
use starknet::signers::VerifyingKey;
use starknet::{core::types::contract::legacy::LegacyContractClass, signers::SigningKey};
use starknet_api::{
contract_address,
core::{ClassHash, ContractAddress, PatriciaKey},
patricia_key,
};
use starknet_crypto::Felt;

fn load_contract_class<T>(path: &str) -> Result<T, eyre::Error>
where
@@ -50,13 +52,18 @@ lazy_static! {
pub static ref STRK_FEE_TOKEN_ADDRESS: ContractAddress = contract_address!("0xCa14007Eff0dB1f8135f4C25B34De49AB0d42766");
pub static ref KAKAROT_ADDRESS: ContractAddress = ContractAddress(1_u128.into());
pub static ref KAKAROT_OWNER_ADDRESS: ContractAddress = ContractAddress(2_u128.into());
pub static ref RELAYER_ADDRESS: ContractAddress = ContractAddress(3_u128.into());

pub static ref FEE_TOKEN_CLASS: LegacyContractClass = load_contract_class("../../build/common/ERC20.json").expect("Failed to load FeeToken contract class");
pub static ref FEE_TOKEN_CLASS_HASH: ClassHash = ClassHash(FEE_TOKEN_CLASS.class_hash().unwrap());

pub static ref CAIRO1_HELPERS_CLASS: CompiledClass = load_contract_class("../../build/common/cairo1_helpers.json").expect("Failed to load precompiles contract class");
pub static ref CAIRO1_HELPERS_CLASS_HASH: ClassHash = ClassHash(CAIRO1_HELPERS_CLASS.class_hash().unwrap());

pub static ref RELAYER_SIGNING_KEY: SigningKey = SigningKey::from_random();
pub static ref RELAYER_VERIFYING_KEY: VerifyingKey = RELAYER_SIGNING_KEY.verifying_key();
pub static ref RELAYER_BALANCE: Felt = Felt::from(1_000_000_000_000_000_000_000_000u128);

}

#[cfg(feature = "v0")]
@@ -65,11 +72,13 @@ lazy_static! {
pub static ref KAKAROT_CLASS: LegacyContractClass = load_contract_class("../../build/v0/kakarot.json").expect("Failed to load Kakarot contract class");
pub static ref ACCOUNT_CONTRACT_CLASS: LegacyContractClass = load_contract_class("../../build/v0/account_contract.json").expect("Failed to load ContractAccount contract class");
pub static ref UNINITIALIZED_ACCOUNT_CLASS: LegacyContractClass = load_contract_class("../../build/v0/uninitialized_account.json").expect("Failed to load uninitialized account c contract class");
pub static ref OPENZEPPELIN_ACCOUNT_CLASS: LegacyContractClass = load_contract_class("../../build/v0/OpenzeppelinAccount.json").expect("Failed to load openzeppelin account contract class");

// Main class hashes
pub static ref KAKAROT_CLASS_HASH: ClassHash = ClassHash(KAKAROT_CLASS.class_hash().unwrap());
pub static ref ACCOUNT_CONTRACT_CLASS_HASH: ClassHash = ClassHash(ACCOUNT_CONTRACT_CLASS.class_hash().unwrap());
pub static ref UNINITIALIZED_ACCOUNT_CLASS_HASH: ClassHash = ClassHash(UNINITIALIZED_ACCOUNT_CLASS.class_hash().unwrap());
pub static ref OPENZEPPELIN_ACCOUNT_CLASS_HASH: ClassHash = ClassHash(OPENZEPPELIN_ACCOUNT_CLASS.class_hash().unwrap());
}

#[cfg(feature = "v1")]
@@ -107,9 +116,9 @@ pub mod storage_variables {
pub const ACCOUNT_NONCE: &str = "Account_nonce";
pub const ACCOUNT_KAKAROT_ADDRESS: &str = "Account_kakarot_address";
pub const ACCOUNT_IMPLEMENTATION: &str = "Account_implementation";
pub const ACCOUNT_CAIRO1_HELPERS_CLASS: &str = "Account_cairo1_helpers_class_hash";
pub const ACCOUNT_VALID_JUMPDESTS: &str = "Account_valid_jumpdests";
pub const ACCOUNT_JUMPDESTS_INITIALIZED: &str = "Account_jumpdests_initialized";
pub const ACCOUNT_VALID_JUMPDESTS : &str = "Account_valid_jumpdests";
pub const ACCOUNT_JUMPDESTS_INITIALIZED : &str = "Account_jumpdests_initialized";
pub const ACCOUNT_PUBLIC_KEY: &str = "Account_public_key";

pub const KAKAROT_COINBASE: &str = "Kakarot_coinbase";
pub const KAKAROT_BASE_FEE: &str = "Kakarot_base_fee";
@@ -123,6 +132,8 @@ pub mod storage_variables {
pub const KAKAROT_PREV_RANDAO: &str = "Kakarot_prev_randao";

pub const OWNABLE_OWNER: &str = "Ownable_owner";

pub const ERC20_BALANCES: &str = "ERC20_balances";
}

#[cfg(test)]
@@ -141,4 +152,4 @@ pub mod tests {
pub static ref TEST_CONTRACT_ADDRESS: Address =
Address::left_padding_from(&0xdeadbeefu64.to_be_bytes());
}
}
}
28 changes: 15 additions & 13 deletions crates/ef-testing/src/evm_sequencer/evm_state/v0.rs
Original file line number Diff line number Diff line change
@@ -13,11 +13,11 @@ use starknet_api::state::StorageKey;
use super::Evm;
use crate::evm_sequencer::account::KakarotAccount;
use crate::evm_sequencer::constants::storage_variables::{
ACCOUNT_BYTECODE_LEN, ACCOUNT_CAIRO1_HELPERS_CLASS, ACCOUNT_IMPLEMENTATION, ACCOUNT_NONCE,
ACCOUNT_STORAGE, KAKAROT_BASE_FEE, KAKAROT_BLOCK_GAS_LIMIT, KAKAROT_COINBASE,
KAKAROT_EVM_TO_STARKNET_ADDRESS, KAKAROT_PREV_RANDAO, OWNABLE_OWNER,
ACCOUNT_BYTECODE_LEN, ACCOUNT_IMPLEMENTATION, ACCOUNT_NONCE, ACCOUNT_STORAGE, KAKAROT_BASE_FEE,
KAKAROT_BLOCK_GAS_LIMIT, KAKAROT_COINBASE, KAKAROT_EVM_TO_STARKNET_ADDRESS,
KAKAROT_PREV_RANDAO, OWNABLE_OWNER,
};
use crate::evm_sequencer::constants::ETH_FEE_TOKEN_ADDRESS;
use crate::evm_sequencer::constants::{ETH_FEE_TOKEN_ADDRESS, RELAYER_ADDRESS};
use crate::evm_sequencer::sequencer::KakarotSequencer;
use crate::evm_sequencer::types::felt::FeltSequencer;
use crate::evm_sequencer::utils::{felt_to_bytes, split_u256, to_broadcasted_starknet_transaction};
@@ -93,10 +93,6 @@ impl Evm for KakarotSequencer {
ACCOUNT_IMPLEMENTATION,
self.environment.account_contract_class_hash.0
),
starknet_storage!(
ACCOUNT_CAIRO1_HELPERS_CLASS,
self.environment.cairo1_helpers_class_hash.0
), // both EOA and CA CH are the same (for now)
starknet_storage!(OWNABLE_OWNER, *self.environment.kakarot_address.0.key()),
]);

@@ -251,19 +247,26 @@ impl Evm for KakarotSequencer {
}
})?;
let starknet_address = self.compute_starknet_address(&evm_address)?;
let relayer_nonce = self.state_mut().get_nonce_at(*RELAYER_ADDRESS).unwrap();

let starknet_transaction =
BroadcastedTransactionWrapper::new(BroadcastedTransaction::Invoke(
to_broadcasted_starknet_transaction(&transaction, *starknet_address.0.key())
.map_err(|err| TransactionExecutionError::ValidateTransactionError {
to_broadcasted_starknet_transaction(
&transaction,
Felt::from(starknet_address),
relayer_nonce.0.into(),
)
.map_err(|err| {
TransactionExecutionError::ValidateTransactionError {
error: EntryPointExecutionError::InvalidExecutionInput {
input_descriptor: String::from("Signed transaction"),
info: err.to_string(),
},
class_hash: Default::default(),
storage_address: Default::default(),
selector: Default::default(),
})?,
}
})?,
));

let chain_id = self.chain_id();
@@ -312,7 +315,7 @@ mod tests {
coinbase_address,
CHAIN_ID,
0,
0,
1,
);

let mut transaction = TransactionSigned {
@@ -349,7 +352,6 @@ mod tests {
sequencer.setup_account(contract).unwrap();
sequencer.setup_account(eoa).unwrap();
let execution_result = sequencer.execute_transaction(transaction);

// Update the output with the execution result of the current transaction
let tx_output = extract_output_and_log_execution_result(
&execution_result,
12 changes: 9 additions & 3 deletions crates/ef-testing/src/evm_sequencer/evm_state/v1.rs
Original file line number Diff line number Diff line change
@@ -284,16 +284,22 @@ impl Evm for KakarotSequencer {

let starknet_transaction =
BroadcastedTransactionWrapper::new(BroadcastedTransaction::Invoke(
to_broadcasted_starknet_transaction(&transaction, *starknet_address.0.key())
.map_err(|err| TransactionExecutionError::ValidateTransactionError {
to_broadcasted_starknet_transaction(
&transaction,
Felt::from(starknet_address),
None,
)
.map_err(|err| {
TransactionExecutionError::ValidateTransactionError {
error: EntryPointExecutionError::InvalidExecutionInput {
input_descriptor: String::from("Failed to convert transaction"),
info: err.to_string(),
},
class_hash: Default::default(),
storage_address: Default::default(),
selector: Default::default(),
})?,
}
})?,
));

let chain_id = self.chain_id();
18 changes: 14 additions & 4 deletions crates/ef-testing/src/evm_sequencer/sequencer/v0.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use crate::evm_sequencer::{
constants::{
storage_variables::{
KAKAROT_ACCOUNT_CONTRACT_CLASS_HASH, KAKAROT_BLOCK_GAS_LIMIT,
KAKAROT_CAIRO1_HELPERS_CLASS_HASH, KAKAROT_NATIVE_TOKEN_ADDRESS,
KAKAROT_UNINITIALIZED_ACCOUNT_CLASS_HASH, OWNABLE_OWNER,
ACCOUNT_PUBLIC_KEY, ERC20_BALANCES, KAKAROT_ACCOUNT_CONTRACT_CLASS_HASH,
KAKAROT_BLOCK_GAS_LIMIT, KAKAROT_CAIRO1_HELPERS_CLASS_HASH,
KAKAROT_NATIVE_TOKEN_ADDRESS, KAKAROT_UNINITIALIZED_ACCOUNT_CLASS_HASH, OWNABLE_OWNER,
},
ACCOUNT_CONTRACT_CLASS, ACCOUNT_CONTRACT_CLASS_HASH, BLOCK_GAS_LIMIT, CAIRO1_HELPERS_CLASS,
CAIRO1_HELPERS_CLASS_HASH, ETH_FEE_TOKEN_ADDRESS, FEE_TOKEN_CLASS, FEE_TOKEN_CLASS_HASH,
KAKAROT_ADDRESS, KAKAROT_CLASS, KAKAROT_CLASS_HASH, KAKAROT_OWNER_ADDRESS,
UNINITIALIZED_ACCOUNT_CLASS, UNINITIALIZED_ACCOUNT_CLASS_HASH,
OPENZEPPELIN_ACCOUNT_CLASS, OPENZEPPELIN_ACCOUNT_CLASS_HASH, RELAYER_ADDRESS,
RELAYER_BALANCE, RELAYER_VERIFYING_KEY, UNINITIALIZED_ACCOUNT_CLASS,
UNINITIALIZED_ACCOUNT_CLASS_HASH,
},
sequencer::{convert_contract_class_v0, convert_contract_class_v1},
};
@@ -59,6 +61,14 @@ lazy_static! {
(&mut state).set_class_hash_at(*ETH_FEE_TOKEN_ADDRESS, *FEE_TOKEN_CLASS_HASH).expect("failed to set fee token class hash");
(&mut state).set_contract_class(*CAIRO1_HELPERS_CLASS_HASH, convert_contract_class_v1(&CAIRO1_HELPERS_CLASS).expect("failed to convert CAIRO1_HELPERS Class to contract class")).expect("failed to set cairo1_helpers contract class");

(&mut state).set_contract_class(
*OPENZEPPELIN_ACCOUNT_CLASS_HASH,
convert_contract_class_v0(&OPENZEPPELIN_ACCOUNT_CLASS).expect("failed to convert OPENZEPPELIN ACCOUNT CLASS to contract class")
).expect("failed to set openzeppelin account contract class");
(&mut state).set_class_hash_at(*RELAYER_ADDRESS, *OPENZEPPELIN_ACCOUNT_CLASS_HASH).expect("failed to set relayer class hash");
(&mut state).set_storage_at(*RELAYER_ADDRESS, get_storage_var_address(ACCOUNT_PUBLIC_KEY, &[]), RELAYER_VERIFYING_KEY.scalar().into()).expect("failed to set relayer public key");
(&mut state).set_storage_at(*ETH_FEE_TOKEN_ADDRESS, get_storage_var_address(ERC20_BALANCES, &[*RELAYER_ADDRESS.0.key()]), *RELAYER_BALANCE).expect("failed to set relayer balance");

state
};
}
136 changes: 99 additions & 37 deletions crates/ef-testing/src/evm_sequencer/utils.rs
Original file line number Diff line number Diff line change
@@ -38,13 +38,16 @@ pub fn felt_to_bytes(felt: &Felt, start: usize) -> Bytes {
}

/// Converts an signed transaction and a signature to a Starknet-rs transaction.
#[allow(unused_variables)] // necessary for starknet_address which is behind a flag
pub fn to_broadcasted_starknet_transaction(
transaction: &TransactionSigned,
signer_starknet_address: Felt,
starknet_address: Felt,
relayer_nonce: Option<Felt>,
) -> Result<BroadcastedInvokeTransaction, eyre::Error> {
let mut bytes = BytesMut::new();
transaction.transaction.encode_without_signature(&mut bytes);

#[allow(unused_mut)]
let mut calldata: Vec<Felt> = {
// Pack the calldata in 31-byte chunks.
#[cfg(feature = "v0")]
@@ -62,59 +65,118 @@ pub fn to_broadcasted_starknet_transaction(
}
};

let mut execute_calldata = {
let signature = transaction.signature();
let [r_low, r_high] = split_u256(signature.r);
let [s_low, s_high] = split_u256(signature.s);
let v = match transaction.transaction.tx_type() {
TxType::Legacy => signature.v(transaction.chain_id()),
_ => signature.odd_y_parity as u64,
};

#[allow(unused_mut)]
let mut signature: Vec<Felt> = vec![
r_low.into(),
r_high.into(),
s_low.into(),
s_high.into(),
v.into(),
];

let execute_calldata = {
#[cfg(feature = "v0")]
{
use super::constants::RELAYER_ADDRESS;
use crate::evm_sequencer::constants::KAKAROT_ADDRESS;
vec![
Felt::ONE, // call array length
*KAKAROT_ADDRESS.0.key(), // contract address
selector!("eth_send_transaction"), // selector
Felt::ZERO, // data offset
calldata.len().into(), // data length
calldata.len().into(), // calldata length
]

let mut execute_from_outside_calldata = vec![
*RELAYER_ADDRESS.0.key(), // OutsideExecution caller
Felt::ZERO, // OutsideExecution nonce
Felt::ZERO, // OutsideExecution execute_after
Felt::from(10_000_000_000_000u128), // OutsideExecution execute_before
Felt::ONE, // call_array_len
*KAKAROT_ADDRESS.0.key(), // CallArray to
selector!("eth_send_transaction"), // CallArray selector
Felt::ZERO, // CallArray data_offset
calldata.len().into(), // CallArray data_len
calldata.len().into(), // calldata_len
];
execute_from_outside_calldata.append(&mut calldata);
execute_from_outside_calldata.push(signature.len().into());
execute_from_outside_calldata.append(&mut signature);

let mut execute_entrypoint_calldata = vec![
Felt::ONE, // call_array_len
starknet_address, // CallArray to
selector!("execute_from_outside"), // CallArray selector
Felt::ZERO, // CallArray data_offset
(execute_from_outside_calldata.len()).into(), // CallArraydata data_len
(execute_from_outside_calldata.len()).into(), // calldata length
];
execute_entrypoint_calldata.append(&mut execute_from_outside_calldata);
execute_entrypoint_calldata
}
#[cfg(feature = "v1")]
{
use crate::evm_sequencer::constants::KAKAROT_ADDRESS;
vec![
Felt::ONE, // call array length
*KAKAROT_ADDRESS.0.key(), // contract address
selector!("eth_send_transaction"), // selector
calldata.len().into(), // calldata length
Felt::ONE, // call_array_len
*KAKAROT_ADDRESS.0.key(), // CallArray to
selector!("eth_send_transaction"), // CallArray selector
calldata.len().into(), // CallArray data_len
]
}
#[cfg(not(any(feature = "v0", feature = "v1")))]
{
vec![]
}
};
execute_calldata.append(&mut calldata);

let signature = transaction.signature();
let [r_low, r_high] = split_u256(signature.r);
let [s_low, s_high] = split_u256(signature.s);
let v = match transaction.transaction.tx_type() {
TxType::Legacy => signature.v(transaction.chain_id()),
_ => signature.odd_y_parity as u64,
let request = {
#[cfg(feature = "v0")]
{
use super::constants::{RELAYER_ADDRESS, RELAYER_SIGNING_KEY};
use starknet::core::crypto::compute_hash_on_elements;

let relayer_address = *RELAYER_ADDRESS.0.key();
let relayer_nonce = relayer_nonce.expect("Relayer nonce not provided");
let invoke_v1_tx = vec![
Felt::from_bytes_be_slice(b"invoke"), // "invoke"
Felt::ONE, // version
relayer_address, // sender_address
Felt::ZERO, // 0
compute_hash_on_elements(&execute_calldata), // h(calldata)
Felt::ZERO, // max_fee
transaction.chain_id().unwrap().into(), // chain_id
relayer_nonce, // nonce
];
let transaction_hash = compute_hash_on_elements(&invoke_v1_tx);
let signature_relayer = RELAYER_SIGNING_KEY
.sign(&transaction_hash)
.expect("Signature starknet failed");
let signature_relayer = vec![signature_relayer.r, signature_relayer.s];

BroadcastedInvokeTransaction::V1(BroadcastedInvokeTransactionV1 {
max_fee: Felt::ZERO,
signature: signature_relayer,
nonce: relayer_nonce,
sender_address: relayer_address,
calldata: execute_calldata,
is_query: false,
})
}
#[cfg(not(feature = "v0"))]
{
let nonce = Felt::from(transaction.nonce());
BroadcastedInvokeTransaction::V1(BroadcastedInvokeTransactionV1 {
max_fee: Felt::ZERO,
signature,
nonce,
sender_address: starknet_address,
calldata: execute_calldata,
is_query: false,
})
}
};
let signature = vec![
r_low.into(),
r_high.into(),
s_low.into(),
s_high.into(),
v.into(),
];

let request = BroadcastedInvokeTransaction::V1(BroadcastedInvokeTransactionV1 {
max_fee: Felt::ZERO,
signature,
nonce: transaction.nonce().into(),
sender_address: signer_starknet_address,
calldata: execute_calldata,
is_query: false,
});

Ok(request)
}
Loading