Skip to content

Commit

Permalink
feat: execute_starknet_call (#871)
Browse files Browse the repository at this point in the history
* feat: execute_starknet_call

* doc: use rustdoc format

* style: scarb fmt

* fix: return false on syscall error
  • Loading branch information
obatirou authored Aug 28, 2024
1 parent 90878e2 commit 89760c5
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 3 deletions.
22 changes: 20 additions & 2 deletions crates/contracts/src/account_contract.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub trait IAccount<TContractState> {
fn storage(self: @TContractState, key: u256) -> u256;
fn get_nonce(self: @TContractState) -> u64;
fn set_nonce(ref self: TContractState, nonce: u64);
fn execute_starknet_call(ref self: TContractState, call: Call) -> (bool, Span<felt252>);
}

#[starknet::contract(account)]
Expand All @@ -38,7 +39,7 @@ pub mod AccountContract {
use contracts::components::ownable::ownable_component;
use contracts::errors::{
BYTECODE_READ_ERROR, BYTECODE_WRITE_ERROR, STORAGE_READ_ERROR, STORAGE_WRITE_ERROR,
NONCE_READ_ERROR, NONCE_WRITE_ERROR, KAKAROT_VALIDATION_FAILED
NONCE_READ_ERROR, NONCE_WRITE_ERROR, KAKAROT_VALIDATION_FAILED, KAKAROT_REENTRANCY
};
use contracts::kakarot_core::interface::{IKakarotCoreDispatcher, IKakarotCoreDispatcherTrait};
use contracts::storage::StorageBytecode;
Expand All @@ -53,7 +54,7 @@ pub mod AccountContract {
StoragePointerWriteAccess
};
use core::starknet::storage_access::{storage_base_address_from_felt252, StorageBaseAddress};
use core::starknet::syscalls::{replace_class_syscall};
use core::starknet::syscalls::{call_contract_syscall, replace_class_syscall};
use core::starknet::{
ContractAddress, EthAddress, ClassHash, VALIDATED, get_caller_address, get_contract_address,
get_tx_info, Store
Expand Down Expand Up @@ -271,6 +272,23 @@ pub mod AccountContract {
self.ownable.assert_only_owner();
self.Account_nonce.write(nonce);
}

/// Used to preserve caller in Cairo Precompiles
/// Reentrency check is done for Kakarot contract, only get_starknet_address is allowed
/// for Solidity contracts to be able to get the corresponding Starknet address in their
/// calldata.
fn execute_starknet_call(ref self: ContractState, call: Call) -> (bool, Span<felt252>) {
self.ownable.assert_only_owner();
let kakarot_address = self.ownable.owner();
if call.to == kakarot_address && call.selector != selector!("get_starknet_address") {
return (false, KAKAROT_REENTRANCY.span());
}
let response = call_contract_syscall(call.to, call.selector, call.calldata);
if response.is_ok() {
return (true, response.unwrap().into());
}
return (false, response.unwrap_err().into());
}
}

#[generate_trait]
Expand Down
6 changes: 6 additions & 0 deletions crates/contracts/src/errors.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ pub const KAKAROT_VALIDATION_FAILED: [
'e',
'd'
];

pub const KAKAROT_REENTRANCY: [
felt252
; 19] = [
'K', 'a', 'k', 'a', 'r', 'o', 't', ':', ' ', 'r', 'e', 'e', 'n', 't', 'r', 'a', 'n', 'c', 'y'
];
13 changes: 13 additions & 0 deletions crates/contracts/src/kakarot_core/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ pub trait IKakarotCore<TContractState> {
fn get_block_gas_limit(self: @TContractState) -> u128;
// Getter for the Base Fee
fn get_base_fee(self: @TContractState) -> u128;

// Getter for the Starknet Address
fn get_starknet_address(self: @TContractState, evm_address: EthAddress) -> ContractAddress;
}

#[starknet::interface]
Expand Down Expand Up @@ -105,6 +108,16 @@ pub trait IExtendedKakarotCore<TContractState> {
// Setter for the Generic Account Class
fn set_account_class_hash(ref self: TContractState, new_class_hash: ClassHash);

fn register_account(ref self: TContractState, evm_address: EthAddress);

// Getter for the Block Gas Limit
fn get_block_gas_limit(self: @TContractState) -> u128;
// Getter for the Base Fee
fn get_base_fee(self: @TContractState) -> u128;

// Getter for the Starknet Address
fn get_starknet_address(self: @TContractState, evm_address: EthAddress) -> ContractAddress;

fn owner(self: @TContractState) -> ContractAddress;
fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress);
fn renounce_ownership(ref self: TContractState);
Expand Down
15 changes: 15 additions & 0 deletions crates/contracts/src/kakarot_core/kakarot.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,21 @@ pub mod KakarotCore {
fn get_base_fee(self: @ContractState) -> u128 {
self.Kakarot_base_fee.read()
}

// @notice Returns the corresponding Starknet address for a given EVM address.
// @dev Returns the registered address if there is one, otherwise returns the deterministic
// address got when Kakarot deploys an account.
// @param evm_address The EVM address to transform to a starknet address
// @return starknet_address The Starknet Account Contract address
fn get_starknet_address(self: @ContractState, evm_address: EthAddress) -> ContractAddress {
let registered_starknet_address = self.address_registry(evm_address);
if (registered_starknet_address.is_zero()) {
return registered_starknet_address;
}

let computed_starknet_address = self.compute_starknet_address(evm_address);
return computed_starknet_address;
}
}

#[generate_trait]
Expand Down
71 changes: 70 additions & 1 deletion crates/contracts/tests/test_contract_account.cairo
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
use contracts::account_contract::{AccountContract, IAccountDispatcher, IAccountDispatcherTrait};
use contracts::errors::KAKAROT_REENTRANCY;
use contracts::test_data::counter_evm_bytecode;
use contracts::test_utils::{setup_contracts_for_testing, deploy_contract_account};
use contracts::test_utils::{
setup_contracts_for_testing, deploy_contract_account, fund_account_with_native_token
};
use core::starknet::ContractAddress;
use core::starknet::account::{Call};
use core::starknet::testing;
use evm::test_utils::{ca_address, native_token};
use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait};
use snforge_std::{start_cheat_caller_address, stop_cheat_caller_address};

#[test]
Expand Down Expand Up @@ -68,3 +75,65 @@ fn test_ca_storage() {

assert(storage == expected_storage, 'wrong contract storage');
}

#[test]
fn test_ca_external_starknet_call_native_token() {
let (native_token, kakarot_core) = setup_contracts_for_testing();
let ca_address = deploy_contract_account(kakarot_core, ca_address(), [].span());
let contract_account = IAccountDispatcher { contract_address: ca_address.starknet };
fund_account_with_native_token(ca_address.starknet, native_token, 0x1);

let call = Call {
to: native_token.contract_address,
selector: selector!("balanceOf"),
calldata: array![ca_address.starknet.into()].span(),
};
start_cheat_caller_address(ca_address.starknet, kakarot_core.contract_address);
let (success, data) = contract_account.execute_starknet_call(call);
stop_cheat_caller_address(ca_address.starknet);

assert(success == true, 'execute_starknet_call failed');
assert(data.len() == 2, 'wrong return data length');
let balance = native_token.balanceOf(ca_address.starknet);
assert((*data[0], *data[1]) == (balance.low.into(), balance.high.into()), 'wrong return data');
}

#[test]
fn test_ca_external_starknet_call_kakarot_get_starknet_address() {
let (_, kakarot_core) = setup_contracts_for_testing();
let ca_address = deploy_contract_account(kakarot_core, ca_address(), [].span());
let contract_account = IAccountDispatcher { contract_address: ca_address.starknet };

let call = Call {
to: kakarot_core.contract_address,
selector: selector!("get_starknet_address"),
calldata: array![ca_address.evm.address].span(),
};
start_cheat_caller_address(ca_address.starknet, kakarot_core.contract_address);
let (success, data) = contract_account.execute_starknet_call(call);
stop_cheat_caller_address(ca_address.starknet);

assert(success == true, 'execute_starknet_call failed');
assert(data.len() == 1, 'wrong return data length');
assert(*data[0] == ca_address.starknet.try_into().unwrap(), 'wrong return data');
}

#[test]
fn test_ca_external_starknet_call_cannot_call_kakarot_other_selector() {
let (_, kakarot_core) = setup_contracts_for_testing();
let ca_address = deploy_contract_account(kakarot_core, ca_address(), [].span());
let contract_account = IAccountDispatcher { contract_address: ca_address.starknet };

let call = Call {
to: kakarot_core.contract_address,
selector: selector!("get_native_token"),
calldata: array![].span(),
};
start_cheat_caller_address(ca_address.starknet, kakarot_core.contract_address);
let (success, data) = contract_account.execute_starknet_call(call);
stop_cheat_caller_address(ca_address.starknet);

assert(success == false, 'execute_starknet_call failed');
assert(data.len() == 19, 'wrong return data length');
assert(data == KAKAROT_REENTRANCY.span(), 'wrong return data');
}

0 comments on commit 89760c5

Please sign in to comment.