Skip to content

Commit

Permalink
feat: add __validate__ to EOA (#576)
Browse files Browse the repository at this point in the history
* feat: add `__validate__` for eoa (#564)

* draft commit __execute__

* feat: implement __execute__ for EOA

* feat: update tests __execute__

* dev: renmae `call_data` to `calldata`, use `deploy_contract_account` util

* dev: change `test_execute*` to `test___execute__*`

* dev: remove unused imports

* fix: fix test

* dev: add script used for generating RLP tests

* dev: add rlp encoding generation script

* dev: replace `unwrap` with `expect` in `eoa.cairo`

* dev: make script user-friendly

* dev: change `to_bytes` to `try_into_bytes`

* fix: sn address compute test

---------

Co-authored-by: Harsh Bajpai <[email protected]>
Co-authored-by: Harsh Bajpai <[email protected]>
Co-authored-by: enitrat <[email protected]>
Co-authored-by: Mathieu <[email protected]>

* dev: formatting

* dev: typo + use assert

* feat: add usage commit for generating signatures

* dev: use strings to make panic message better

* dev: fix typo

* dev: use try_into for felt252 -> Signature

* feat: remove panic from try_into

* dev: use into for bool -> felt252 conversion

* dev: inline array creation

* dev: use cheatcode to create 0 address

* dev: implement trait for numberic to bool

* dev: revert back from using the numeric trait

---------

Co-authored-by: Harsh Bajpai <[email protected]>
Co-authored-by: Harsh Bajpai <[email protected]>
Co-authored-by: enitrat <[email protected]>
Co-authored-by: Mathieu <[email protected]>
  • Loading branch information
5 people authored Nov 28, 2023
1 parent a8ab1c1 commit 261b177
Show file tree
Hide file tree
Showing 7 changed files with 436 additions and 76 deletions.
57 changes: 39 additions & 18 deletions crates/contracts/src/eoa.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ mod ExternallyOwnedAccount {
use starknet::account::{Call, AccountContract};

use starknet::{
ContractAddress, EthAddress, ClassHash, VALIDATED, get_caller_address, get_contract_address
ContractAddress, EthAddress, ClassHash, VALIDATED, get_caller_address, get_contract_address,
get_tx_info
};
use utils::eth_transaction::{EthTransactionTrait, EthereumTransaction, TransactionMetadata};
use utils::helpers::{
Felt252SpanExTrait, U8SpanExTrait, EthAddressSignatureTrait, TryIntoEthSignature
};
use utils::eth_transaction::{EthTransactionTrait, EthereumTransaction};
use utils::helpers::{Felt252SpanExTrait, U8SpanExTrait};

component!(path: upgradeable_component, storage: upgradeable, event: UpgradeableEvent);

Expand Down Expand Up @@ -79,21 +82,39 @@ mod ExternallyOwnedAccount {
#[abi(embed_v0)]
impl AccountContractImpl of AccountContract<ContractState> {
fn __validate__(ref self: ContractState, calls: Array<Call>) -> felt252 {
assert(get_caller_address().is_zero(), 'Caller not zero');
// TODO
// Steps:
// Receive a payload formed as:
// Starknet Transaction Signature field: r, s, v (EVM Signature fields)
// Calldata field: an RLP-encoded EVM transaction, without r, s, v

// Step 1:
// Hash RLP-encoded EVM transaction
// Step 2:
// Verify EVM signature using get_tx_info().signature field against the keccak hash of the EVM tx
// Step 3:
// If valid signature, decode the RLP-encoded payload
// Step 4:
// Return ok
assert(get_caller_address().is_zero(), 'Caller not 0');

let tx_info = get_tx_info().unbox();

let call_len = calls.len();
assert(call_len == 1, 'call len is not 1');

let call = calls.at(0);
assert(*call.to == self.kakarot_core_address(), 'to is not kakarot core');
assert!(
*call.selector == selector!("eth_send_transaction"),
"Validate: selector must be eth_send_transaction"
);

let signature = tx_info.signature;

let tx_metadata = TransactionMetadata {
address: self.evm_address(),
chain_id: self.chain_id(),
account_nonce: tx_info.nonce.try_into().unwrap(),
signature: signature.try_into().expect('signature extraction failed')
};

let encoded_tx = (call.calldata)
.span()
.try_into_bytes()
.expect('conversion to Span<u8> failed');
let validation_result = EthTransactionTrait::validate_eth_tx(
tx_metadata, encoded_tx.span()
)
.unwrap();

assert(validation_result, 'transaction validation failed');

VALIDATED
}
Expand Down
180 changes: 169 additions & 11 deletions crates/contracts/src/tests/test_eoa.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,21 @@ mod test_external_owned_account {
use core::array::SpanTrait;
use core::starknet::account::{Call, AccountContractDispatcher, AccountContractDispatcherTrait};


//todo(harsh): remove
use debug::PrintTrait;

use evm::model::{Address, AddressTrait, ContractAccountTrait};
use evm::tests::test_utils::{
kakarot_address, evm_address, other_evm_address, eoa_address, chain_id, gas_limit, gas_price
kakarot_address, evm_address, other_evm_address, other_starknet_address, eoa_address,
chain_id, gas_limit, gas_price
};
use openzeppelin::token::erc20::interface::IERC20CamelDispatcherTrait;
use starknet::class_hash::Felt252TryIntoClassHash;
use starknet::testing::{set_caller_address, set_contract_address};
use starknet::testing::{set_caller_address, set_contract_address, set_signature};
use starknet::{
deploy_syscall, ContractAddress, ClassHash, get_contract_address, contract_address_const,
EthAddress
deploy_syscall, ContractAddress, ClassHash, VALIDATED, get_contract_address,
contract_address_const, EthAddress, eth_signature::{Signature}
};
use utils::helpers::EthAddressSignatureTrait;
use utils::helpers::{U8SpanExTrait, u256_to_bytes_array};
use utils::tests::test_data::{legacy_rlp_encoded_tx, eip_2930_encoded_tx, eip_1559_encoded_tx};


#[test]
Expand Down Expand Up @@ -110,9 +109,6 @@ mod test_external_owned_account {
let evm_address = evm_address();
let eoa = kakarot_core.deploy_eoa(evm_address);

//todo(harsh): remove
other_evm_address().print();

let kakarot_address = kakarot_core.contract_address;

let account = deploy_contract_account(other_evm_address(), counter_evm_bytecode());
Expand Down Expand Up @@ -176,4 +172,166 @@ mod test_external_owned_account {

eoa_contract.__execute__(array![]);
}

#[test]
#[available_gas(2000000000)]
#[should_panic(expected: ('Caller not 0', 'ENTRYPOINT_FAILED'))]
fn test___validate__fail__caller_not_0() {
let (_, kakarot_core) = setup_contracts_for_testing();
let evm_address = evm_address();
let eoa = kakarot_core.deploy_eoa(evm_address);
let eoa_contract = AccountContractDispatcher { contract_address: eoa };

set_contract_address(other_starknet_address());

let calls = array![];
eoa_contract.__validate__(calls);
}

#[test]
#[available_gas(2000000000)]
#[should_panic(expected: ('call len is not 1', 'ENTRYPOINT_FAILED'))]
fn test___validate__fail__call_data_len_not_1() {
let (_, kakarot_core) = setup_contracts_for_testing();
let evm_address = evm_address();
let eoa = kakarot_core.deploy_eoa(evm_address);
let eoa_contract = AccountContractDispatcher { contract_address: eoa };

set_contract_address(contract_address_const::<0>());

let calls = array![];
eoa_contract.__validate__(calls);
}

#[test]
#[available_gas(2000000000)]
#[should_panic(expected: ('to is not kakarot core', 'ENTRYPOINT_FAILED'))]
fn test___validate__fail__to_address_not_kakarot_core() {
let (_, kakarot_core) = setup_contracts_for_testing();
let evm_address = evm_address();
let eoa = kakarot_core.deploy_eoa(evm_address);
let eoa_contract = AccountContractDispatcher { contract_address: eoa };

set_contract_address(contract_address_const::<0>());

let call = Call {
to: other_starknet_address(),
selector: selector!("eth_send_transaction"),
calldata: array![]
};

eoa_contract.__validate__(array![call]);
}

#[test]
#[available_gas(2000000000)]
#[should_panic(
expected: ("Validate: selector must be eth_send_transaction", 'ENTRYPOINT_FAILED')
)]
fn test___validate__fail__selector_not_eth_send_transaction() {
let (_, kakarot_core) = setup_contracts_for_testing();
let evm_address = evm_address();
let eoa = kakarot_core.deploy_eoa(evm_address);
let eoa_contract = AccountContractDispatcher { contract_address: eoa };

set_contract_address(contract_address_const::<0>());

let call = Call {
to: kakarot_core.contract_address, selector: selector!("eth_call"), calldata: array![]
};

eoa_contract.__validate__(array![call]);
}

#[test]
#[available_gas(2000000000)]
fn test___validate__legacy_transaction() {
let (_, kakarot_core) = setup_contracts_for_testing();
let evm_address: EthAddress = 0x6Bd85F39321B00c6d603474C5B2fddEB9c92A466_u256.into();
let eoa = kakarot_core.deploy_eoa(evm_address);
let eoa_contract = AccountContractDispatcher { contract_address: eoa };

// to reproduce locally:
// run: cp .env.example .env
// bun install & bun run scripts/compute_rlp_encoding.ts
let signature = Signature {
r: 0xaae7c4f6e4caa03257e37a6879ed5b51a6f7db491d559d10a0594f804aa8d797,
s: 0x2f3d9634f8cb9b9a43b048ee3310be91c2d3dc3b51a3313b473ef2260bbf6bc7,
y_parity: true
};
set_signature(signature.to_felt252_array().span());

set_contract_address(contract_address_const::<0>());

let call = Call {
to: kakarot_core.contract_address,
selector: selector!("eth_send_transaction"),
calldata: legacy_rlp_encoded_tx().to_felt252_array()
};

let result = eoa_contract.__validate__(array![call]);
assert(result == VALIDATED, 'validation failed');
}

#[test]
#[available_gas(2000000000)]
fn test___validate__eip_2930_transaction() {
let (_, kakarot_core) = setup_contracts_for_testing();
let evm_address: EthAddress = 0x6Bd85F39321B00c6d603474C5B2fddEB9c92A466_u256.into();
let eoa = kakarot_core.deploy_eoa(evm_address);
let eoa_contract = AccountContractDispatcher { contract_address: eoa };

// to reproduce locally:
// run: cp .env.example .env
// bun install & bun run scripts/compute_rlp_encoding.ts
let signature = Signature {
r: 0x96a5512ce388874338c3825959674c130a7cde2317ab0c2312e9e687d15fc373,
s: 0x12d0b91acc6c7683186f746b8d0a39991911cca2ab99fc84b2a1652792a15249,
y_parity: true
};

set_signature(signature.to_felt252_array().span());

set_contract_address(contract_address_const::<0>());

let call = Call {
to: kakarot_core.contract_address,
selector: selector!("eth_send_transaction"),
calldata: eip_2930_encoded_tx().to_felt252_array()
};

let result = eoa_contract.__validate__(array![call]);
assert(result == VALIDATED, 'validation failed');
}

#[test]
#[available_gas(2000000000)]
fn test___validate__eip_1559_transaction() {
let (_, kakarot_core) = setup_contracts_for_testing();
let evm_address: EthAddress = 0x6Bd85F39321B00c6d603474C5B2fddEB9c92A466_u256.into();
let eoa = kakarot_core.deploy_eoa(evm_address);
let eoa_contract = AccountContractDispatcher { contract_address: eoa };

// to reproduce locally:
// run: cp .env.example .env
// bun install & bun run scripts/compute_rlp_encoding.ts
let signature = Signature {
r: 0x3e1d21af857363cb69f565cf5a791b6e326186250815570c80bd2b7f465802f8,
s: 0x37a9cec24f7d5c8916ded76f702fcf2b93a20b28a7db8f27d7f4e6e11288bda4,
y_parity: true
};

set_signature(signature.to_felt252_array().span());

set_contract_address(contract_address_const::<0>());

let call = Call {
to: kakarot_core.contract_address,
selector: selector!("eth_send_transaction"),
calldata: eip_1559_encoded_tx().to_felt252_array()
};

let result = eoa_contract.__validate__(array![call]);
assert(result == VALIDATED, 'validation failed');
}
}
44 changes: 40 additions & 4 deletions crates/utils/src/helpers.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use core::pedersen::{HashState, PedersenTrait};
use integer::U32TryIntoNonZero;
use integer::u32_as_non_zero;
use keccak::{cairo_keccak, u128_split};
use starknet::{EthAddress, EthAddressIntoFelt252, ContractAddress, ClassHash};
use starknet::{
EthAddress, EthAddressIntoFelt252, ContractAddress, ClassHash,
eth_signature::{Signature as EthSignature}
};
use traits::DivRem;
use utils::constants::{
POW_256_0, POW_256_1, POW_256_2, POW_256_3, POW_256_4, POW_256_5, POW_256_6, POW_256_7,
Expand All @@ -15,9 +18,9 @@ use utils::constants::{
};
use utils::constants::{CONTRACT_ADDRESS_PREFIX, MAX_ADDRESS};
use utils::math::{Bitshift, WrappingBitshift};
use utils::num::{SizeOf};
use utils::traits::TryIntoResult;
use utils::traits::{U256TryIntoContractAddress, EthAddressIntoU256, U256TryIntoEthAddress};
use utils::traits::{
U256TryIntoContractAddress, EthAddressIntoU256, U256TryIntoEthAddress, TryIntoResult,
};
/// Ceils a number of bits to the next word (32 bytes)
///
/// # Arguments
Expand Down Expand Up @@ -1084,3 +1087,36 @@ impl EthAddressExImpl of EthAddressExTrait {
result.try_into().unwrap()
}
}

impl TryIntoEthSignature of TryInto<Span<felt252>, EthSignature> {
// format: [r_low, r_high, s_low, s_high, yParity]
fn try_into(self: Span<felt252>) -> Option<EthSignature> {
if (self.len() != 5) {
return Option::None;
}

let r_low: u128 = (*self.at(0)).try_into()?;
let r_high: u128 = (*self.at(1)).try_into()?;

let s_low: u128 = (*self.at(2)).try_into()?;
let s_high: u128 = (*self.at(3)).try_into()?;

let y_parity = (*self.at(4)) == 1;

let r = u256 { low: r_low, high: r_high };
let s = u256 { low: s_low, high: s_high };

Option::Some(EthSignature { r, s, y_parity })
}
}

#[generate_trait]
impl EthAddressSignatureTraitImpl of EthAddressSignatureTrait {
fn to_felt252_array(self: EthSignature) -> Array<felt252> {
let y_parity: felt252 = self.y_parity.into();

array![
self.r.low.into(), self.r.high.into(), self.s.low.into(), self.s.high.into(), y_parity,
]
}
}
Loading

0 comments on commit 261b177

Please sign in to comment.