Skip to content

Commit

Permalink
feat: execute from outside (#723)
Browse files Browse the repository at this point in the history
* feat: use execute_from_outside entrypoint for tx

* feat: remove ACCOUNT_CAIRO1_HELPERS_CLASS

* feat: nonce is increased after validation only

* fix: wrong arguments

* Update crates/ef-testing/src/evm_sequencer/utils.rs

Co-authored-by: Thomas Coratger <[email protected]>

* fix: use relayer nonce for starknet tx

* style: ignore trunk warning due to flags

* test: increase execute_before OutsideExecution

* style: fix warnings

* fix: implement Default EVMoutput

success was always false due to its
default value

* refactor: remove unecessary into

* fix: use feature flags and improve readiblity

* fix: v1 compilation

* refactor: make RELAYER_ADDRESS identifiable

* refactor: remove unecessary comment

* style: remove new line

---------

Co-authored-by: Thomas Coratger <[email protected]>
  • Loading branch information
obatirou and tcoratger authored Jul 25, 2024
1 parent 6d57cfd commit 9966a97
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 76 deletions.
21 changes: 16 additions & 5 deletions crates/ef-testing/src/evm_sequencer/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")]
Expand All @@ -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")]
Expand Down Expand Up @@ -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";
Expand All @@ -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)]
Expand All @@ -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
Expand Up @@ -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};
Expand Down Expand Up @@ -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()),
]);

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -312,7 +315,7 @@ mod tests {
coinbase_address,
CHAIN_ID,
0,
0,
1,
);

let mut transaction = TransactionSigned {
Expand Down Expand Up @@ -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,
Expand Down
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
Expand Up @@ -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();
Expand Down
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},
};
Expand Down Expand Up @@ -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
Expand Up @@ -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")]
Expand All @@ -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)
}
Expand Down
Loading

0 comments on commit 9966a97

Please sign in to comment.