1
1
use core :: num :: traits :: Zero ;
2
2
use core :: starknet :: get_tx_info;
3
- use core :: starknet :: {EthAddress , get_caller_address};
3
+ use core :: starknet :: {EthAddress , get_caller_address, ContractAddress };
4
4
use crate :: account_contract :: {IAccountDispatcher , IAccountDispatcherTrait };
5
5
use crate :: kakarot_core :: interface :: IKakarotCore ;
6
6
use crate :: kakarot_core :: kakarot :: {KakarotCore , KakarotCore :: {KakarotCoreState }};
7
7
use evm :: backend :: starknet_backend;
8
8
use evm :: backend :: validation :: validate_eth_tx;
9
+ use evm :: model :: account :: AccountTrait ;
9
10
use evm :: model :: {TransactionResult , Address };
10
11
use evm :: {EVMTrait };
11
12
use openzeppelin :: token :: erc20 :: interface :: {IERC20CamelDispatcher , IERC20CamelDispatcherTrait };
@@ -84,21 +85,6 @@ pub trait IEthRPC<T> {
84
85
/// * The estimated gas as a u64
85
86
fn eth_estimate_gas (self : @ T , origin : EthAddress , tx : Transaction ) -> (bool , Span <u8 >, u64 );
86
87
87
- // TODO: make this an internal function. The account contract should call
88
- // eth_send_raw_transaction.
89
- /// Executes a transaction and possibly modifies the state.
90
- ///
91
- /// # Arguments
92
- ///
93
- /// * `tx` - The transaction object
94
- ///
95
- /// # Returns
96
- ///
97
- /// A tuple containing:
98
- /// * A boolean indicating success
99
- /// * The return data as a Span<u8>
100
- /// * The amount of gas used as a u64
101
- fn eth_send_transaction (ref self : T , tx : Transaction ) -> (bool , Span <u8 >, u64 );
102
88
103
89
/// Executes an unsigned transaction.
104
90
///
@@ -153,9 +139,7 @@ pub impl EthRPC<
153
139
core :: panic_with_felt252 (' fn must be called, not invoked' );
154
140
};
155
141
156
- let origin = Address {
157
- evm : origin , starknet : kakarot_state . compute_starknet_address (origin )
158
- };
142
+ let origin = Address { evm : origin , starknet : kakarot_state . get_starknet_address (origin ) };
159
143
160
144
let TransactionResult { success , return_data , gas_used , state : _state } =
161
145
EVMTrait :: process_transaction (
@@ -171,16 +155,49 @@ pub impl EthRPC<
171
155
panic! (" unimplemented" )
172
156
}
173
157
174
- // TODO: make this one internal, and the eth_send_raw_unsigned_tx one public
175
- fn eth_send_transaction (
176
- ref self : TContractState , mut tx : Transaction
158
+ // TODO: we can't really unit-test this with foundry because we can't generate the RLP-encoding
159
+ // in Cairo Find another way - perhaps test-data gen with python?
160
+ fn eth_send_raw_unsigned_tx (
161
+ ref self : TContractState , mut tx_data : Span <u8 >
177
162
) -> (bool , Span <u8 >, u64 ) {
163
+ let tx = TransactionTrait :: decode_enveloped (ref tx_data ). expect (' EOA: could not decode tx' );
164
+ EthRPCInternal :: eth_send_transaction (ref self , tx )
165
+ }
166
+ }
167
+
168
+ trait EthRPCInternal <T > {
169
+ /// Executes a transaction and possibly modifies the state.
170
+ ///
171
+ /// This function implements the `eth_sendTransaction` method as described in the Ethereum
172
+ /// JSON-RPC specification.
173
+ /// The nonce is taken from the corresponding account contract.
174
+ ///
175
+ /// # Arguments
176
+ ///
177
+ /// * `tx` - A `Transaction` struct
178
+ ///
179
+ /// # Returns
180
+ ///
181
+ /// A tuple containing:
182
+ /// * A boolean indicating success (TRUE if the transaction succeeded, FALSE otherwise)
183
+ /// * The return data as a `Span<u8>`
184
+ /// * The amount of gas used by the transaction as a `u64`
185
+ fn eth_send_transaction (ref self : T , tx : Transaction ) -> (bool , Span <u8 >, u64 );
186
+ }
187
+
188
+ impl EthRPCInternalImpl <
189
+ TContractState , impl KakarotState : KakarotCoreState <TContractState >, + Drop <TContractState >
190
+ > of EthRPCInternal <TContractState > {
191
+ fn eth_send_transaction (ref self : TContractState , tx : Transaction ) -> (bool , Span <u8 >, u64 ) {
178
192
let mut kakarot_state = KakarotState :: get_state ();
179
193
let intrinsic_gas = validate_eth_tx (@ kakarot_state , tx );
180
194
181
195
let starknet_caller_address = get_caller_address ();
182
- let account = IAccountDispatcher { contract_address : starknet_caller_address };
183
- let origin = Address { evm : account . get_evm_address (), starknet : starknet_caller_address };
196
+ // panics if the caller is a spoofer of an EVM address.
197
+ // TODO: e2e test this! :) Send a transaction from an account that is not Kakarot's account
198
+ // (e.g. deploy an account but not from Kakarot)
199
+ let origin_evm_address = safe_get_evm_address (@ self , starknet_caller_address );
200
+ let origin = Address { evm : origin_evm_address , starknet : starknet_caller_address };
184
201
185
202
let TransactionResult { success , return_data , gas_used , mut state } =
186
203
EVMTrait :: process_transaction (
@@ -189,29 +206,40 @@ pub impl EthRPC<
189
206
starknet_backend :: commit (ref state ). expect (' Committing state failed' );
190
207
(success , return_data , gas_used )
191
208
}
192
-
193
- // TODO: we can't really unit-test this with foundry because we can't generate the RLP-encoding
194
- // in Cairo Find another way - perhaps test-data gen with python?
195
- fn eth_send_raw_unsigned_tx (
196
- ref self : TContractState , mut tx_data : Span <u8 >
197
- ) -> (bool , Span <u8 >, u64 ) {
198
- let tx = TransactionTrait :: decode_enveloped (ref tx_data ). expect (' EOA: could not decode tx' );
199
- Self :: eth_send_transaction (ref self , tx )
200
- }
201
209
}
202
210
203
- trait IEthRPCInternal <T > {
204
- fn eth_send_transaction (
205
- ref self : T , origin : EthAddress , tx : Transaction
206
- ) -> (bool , Span <u8 >, u64 );
207
- }
208
211
209
- impl EthRPCInternalImpl <TContractState , + Drop <TContractState >> of IEthRPCInternal <TContractState > {
210
- fn eth_send_transaction (
211
- ref self : TContractState , origin : EthAddress , tx : Transaction
212
- ) -> (bool , Span <u8 >, u64 ) {
213
- panic! (" unimplemented" )
214
- }
212
+ /// Returns the EVM address associated with a Starknet account deployed by Kakarot.
213
+ ///
214
+ /// This function prevents cases where a Starknet account has an entrypoint `get_evm_address()`
215
+ /// but isn't part of the Kakarot system. It also mitigates re-entrancy risk with the Cairo Interop
216
+ /// module.
217
+ ///
218
+ /// # Arguments
219
+ ///
220
+ /// * `starknet_address` - The Starknet address of the account
221
+ ///
222
+ /// # Returns
223
+ ///
224
+ /// * `EthAddress` - The associated EVM address
225
+ ///
226
+ /// # Panics
227
+ ///
228
+ /// Panics if the declared corresponding EVM address (retrieved with `get_evm_address`)
229
+ /// does not recompute into the actual caller address.
230
+ fn safe_get_evm_address <
231
+ TContractState , impl KakarotState : KakarotCoreState <TContractState >, + Drop <TContractState >
232
+ >(
233
+ self : @ TContractState , starknet_address : ContractAddress
234
+ ) -> EthAddress {
235
+ let account = IAccountDispatcher { contract_address : starknet_address };
236
+ let evm_address = account . get_evm_address ();
237
+ let safe_starknet_address = AccountTrait :: get_starknet_address (evm_address );
238
+ assert! (
239
+ safe_starknet_address == starknet_address ,
240
+ " Kakarot: caller contract is not a Kakarot Account"
241
+ );
242
+ evm_address
215
243
}
216
244
217
245
fn is_view (self : @ KakarotCore :: ContractState ) -> bool {
@@ -228,16 +256,19 @@ fn is_view(self: @KakarotCore::ContractState) -> bool {
228
256
229
257
#[cfg(test)]
230
258
mod tests {
259
+ use core :: ops :: DerefMut ;
260
+ use core :: starknet :: EthAddress ;
261
+ use core :: starknet :: storage :: {StoragePathEntry , StoragePointerWriteAccess };
231
262
use crate :: kakarot_core :: KakarotCore ;
232
263
use crate :: kakarot_core :: eth_rpc :: IEthRPC ;
233
- use crate :: kakarot_core :: interface :: IExtendedKakarotCoreDispatcherTrait ;
264
+ use crate :: kakarot_core :: interface :: { IKakarotCore , IExtendedKakarotCoreDispatcherTrait } ;
234
265
use crate :: test_utils :: {setup_contracts_for_testing, fund_account_with_native_token};
235
266
use evm :: test_utils :: {sequencer_evm_address, evm_address, uninitialized_account};
236
267
use snforge_std :: {
237
268
start_mock_call, start_cheat_chain_id_global, stop_cheat_chain_id_global, test_address
238
269
};
270
+ use super :: safe_get_evm_address;
239
271
use utils :: constants :: POW_2_53 ;
240
- use utils :: helpers :: compute_starknet_address;
241
272
242
273
fn set_up () -> KakarotCore :: ContractState {
243
274
// Define the kakarot state to access contract functions
@@ -253,12 +284,7 @@ mod tests {
253
284
#[test]
254
285
fn test_eth_get_transaction_count () {
255
286
let kakarot_state = set_up ();
256
- // Deployed eoa should return a zero nonce
257
- let starknet_address = compute_starknet_address (
258
- test_address (),
259
- evm_address (),
260
- 0. try_into (). unwrap () // Using 0 as the kakarot storage is empty
261
- );
287
+ let starknet_address = kakarot_state . get_starknet_address (evm_address ());
262
288
start_mock_call :: <u256 >(starknet_address , selector! (" get_nonce" ), 1 );
263
289
assert_eq! (kakarot_state . eth_get_transaction_count (evm_address ()), 1 );
264
290
}
@@ -304,4 +330,33 @@ mod tests {
304
330
);
305
331
tear_down ();
306
332
}
333
+
334
+ #[test]
335
+ fn test_safe_get_evm_address_succeeds () {
336
+ let kakarot_state = set_up ();
337
+ // no registry - returns the computed address
338
+ let starknet_address = kakarot_state . get_starknet_address (evm_address ());
339
+ start_mock_call :: <
340
+ EthAddress
341
+ >(starknet_address , selector! (" get_evm_address" ), evm_address ());
342
+ let safe_evm_address = safe_get_evm_address (@ kakarot_state , starknet_address );
343
+ assert_eq! (safe_evm_address , evm_address ());
344
+ }
345
+
346
+ #[test]
347
+ #[should_panic(expected: " Kakarot: caller contract is not a Kakarot Account" )]
348
+ fn test_safe_get_evm_address_panics_when_caller_is_not_kakarot_account () {
349
+ let mut kakarot_state = set_up ();
350
+ let mut kakarot_storage = kakarot_state . deref_mut ();
351
+
352
+ // Calling get_evm_address() on a fake starknet account that will return `evm_address()`.
353
+ // Then, when computing the deterministic starknet_address with get_starknet_address(), it
354
+ // will return a different address.
355
+ // This should fail.
356
+ let fake_starknet_account = ' fake_account' . try_into (). unwrap ();
357
+ start_mock_call :: <
358
+ EthAddress
359
+ >(fake_starknet_account , selector! (" get_evm_address" ), evm_address ());
360
+ safe_get_evm_address (@ kakarot_state , fake_starknet_account );
361
+ }
307
362
}
0 commit comments