Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ version = "0.1.0"
dependencies = [
"openzeppelin",
"snforge_std",
"starkware_utils_testing",
"testing_utils",
]

[[package]]
Expand Down
2 changes: 2 additions & 0 deletions eth_712_account/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ starknet.workspace = true
[dev-dependencies]
assert_macros.workspace = true
snforge_std.workspace = true
starkware_utils_testing.workspace = true
testing_utils = { path = "../testing_utils" }

[[target.starknet-contract]]
sierra = true
Expand Down
1 change: 1 addition & 0 deletions eth_712_account/src/eth_712_account.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ pub mod StarknetEth712Account {
// Register Account interface (ISRC6) so that we can receive 721/1155 tokens.
self.src5.register_interface(ISRC6_ID);
}

fn upgrade(
ref self: ContractState,
new_class_hash: ClassHash,
Expand Down
5 changes: 5 additions & 0 deletions eth_712_account/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ pub mod eth_712_account;
pub mod eth_712_utils;
pub mod interface;
pub mod register_interfaces_eic;

#[cfg(test)]
mod test;
#[cfg(test)]
mod test_utils;
126 changes: 126 additions & 0 deletions eth_712_account/src/test.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use eth_712_account::interface::{IAccount712AdminDispatcher, IAccount712AdminDispatcherTrait};
use eth_712_account::test_utils::{
TEST_ETH_ADDRESS, declare_register_interfaces_eic, deploy_eth712_account, get_invalid_signature,
get_ownership_signature,
};
use openzeppelin::account::extensions::src9::interface::ISRC9_V2_ID;
use openzeppelin::account::interface::ISRC6_ID;
use openzeppelin::introspection::interface::{ISRC5Dispatcher, ISRC5DispatcherTrait};
use snforge_std::{EventSpyTrait, load, spy_events};
use starknet::EthAddress;
use starkware_utils_testing::test_utils::cheat_caller_address_once;
use testing_utils::event_helpers::get_event_by_selector;

// ================================
// initialize tests
// ================================

#[test]
fn test_initialize_success() {
let (account_address, _) = deploy_eth712_account();
let account_contract = IAccount712AdminDispatcher { contract_address: account_address };

// Initialize with valid signature
account_contract.initialize(TEST_ETH_ADDRESS(), get_ownership_signature());

// Verify eth_address was stored correctly by reading storage directly
let stored_values = load(account_address, selector!("eth_address"), 1);
let stored_eth_address: EthAddress = (*stored_values.at(0)).try_into().unwrap();
assert!(stored_eth_address == TEST_ETH_ADDRESS(), "eth_address not stored correctly");

// Verify interfaces are registered
let src5 = ISRC5Dispatcher { contract_address: account_address };
assert!(src5.supports_interface(ISRC9_V2_ID), "ISRC9_V2_ID not registered");
assert!(src5.supports_interface(ISRC6_ID), "ISRC6_ID not registered");
}

#[test]
#[should_panic(expected: 'ALREADY_INITIALIZED')]
fn test_initialize_already_initialized_reverts() {
let (account_address, _) = deploy_eth712_account();
let account_contract = IAccount712AdminDispatcher { contract_address: account_address };

// First initialization should succeed
account_contract.initialize(TEST_ETH_ADDRESS(), get_ownership_signature());

// Second initialization should fail
account_contract.initialize(TEST_ETH_ADDRESS(), get_ownership_signature());
}

#[test]
#[should_panic(expected: 'INVALID_OWNERSHIP_SIGNATURE')]
fn test_initialize_invalid_signature_reverts() {
let (account_address, _) = deploy_eth712_account();
let account_contract = IAccount712AdminDispatcher { contract_address: account_address };

// Initialize with invalid signature
account_contract.initialize(TEST_ETH_ADDRESS(), get_invalid_signature());
}

// ================================
// upgrade tests
// ================================

const DUMMY_CLASS_HASH: felt252 = 'DUMMY_CLASS_HASH';

#[test]
#[should_panic(expected: 'UNAUTHORIZED')]
fn test_upgrade_unauthorized_reverts() {
let (account_address, _) = deploy_eth712_account();
let account_contract = IAccount712AdminDispatcher { contract_address: account_address };

// Initialize first
account_contract.initialize(TEST_ETH_ADDRESS(), get_ownership_signature());

// Try to upgrade from external caller (not self) - reverts before checking class hash
account_contract.upgrade(DUMMY_CLASS_HASH.try_into().unwrap(), Option::None);
}

#[test]
fn test_upgrade_from_self_succeeds() {
let (account_address, class_hash) = deploy_eth712_account();
let account_contract = IAccount712AdminDispatcher { contract_address: account_address };

// Initialize first
account_contract.initialize(TEST_ETH_ADDRESS(), get_ownership_signature());

// Spoof caller as self for a single call
cheat_caller_address_once(contract_address: account_address, caller_address: account_address);

let mut spy = spy_events();
account_contract.upgrade(class_hash, Option::None);

// Verify Upgraded event was emitted
let events = spy.get_events();
let event_option = get_event_by_selector(events.events.span(), selector!("Upgraded"));
assert!(event_option.is_some(), "Upgraded event not found");
let (from, event) = event_option.unwrap();
assert!(*from == account_address, "Event from wrong address");
assert!(event.data.len() > 0, "Event has no data");
let class_hash_felt: felt252 = class_hash.into();
assert!(*event.data.at(0) == class_hash_felt, "Wrong class hash in event");
}

#[test]
fn test_upgrade_with_eic() {
let (account_address, class_hash) = deploy_eth712_account();
let account_contract = IAccount712AdminDispatcher { contract_address: account_address };

// Initialize first
account_contract.initialize(TEST_ETH_ADDRESS(), get_ownership_signature());

// Spoof caller as self for a single call
cheat_caller_address_once(contract_address: account_address, caller_address: account_address);

let eic_class_hash = declare_register_interfaces_eic();

// Register a custom interface via EIC
let custom_interface_id: felt252 = 0x12345678;
let eic_data: Span<felt252> = array![custom_interface_id].span();

account_contract.upgrade(class_hash, Option::Some((eic_class_hash, eic_data)));

// Verify the custom interface was registered
let src5 = ISRC5Dispatcher { contract_address: account_address };
assert!(src5.supports_interface(custom_interface_id), "Custom interface not registered");
}
59 changes: 59 additions & 0 deletions eth_712_account/src/test_utils.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use snforge_std::{ContractClassTrait, DeclareResultTrait};
use starknet::secp256_trait::Signature;
use starknet::{ClassHash, ContractAddress, EthAddress, SyscallResultTrait};

/// Test Ethereum address corresponding to private key:
/// 0xa6d86467b6ec9e161649b27edfd8519e75a2e1cf5f4c309c628706e6999780e8
/// Address: 0xbF60187c5dFfA627249f1C3000A4168dbB9D7A1A
pub fn TEST_ETH_ADDRESS() -> EthAddress {
0xbF60187c5dFfA627249f1C3000A4168dbB9D7A1A_felt252.try_into().unwrap()
}

/// A different Ethereum address for testing invalid cases.
pub fn WRONG_ETH_ADDRESS() -> EthAddress {
0x1234567890123456789012345678901234567890_felt252.try_into().unwrap()
}

/// Returns the ownership signature for TEST_ETH_ADDRESS.
/// This signature was generated by signing the OWNERSHIP_TRANSFER_MSG_HASH
/// (0x3ce976d55131cd0bdd49f20afbded052d8e907dc6034d95cdf117a8fd7752e3c)
/// with the test private key.
///
/// Values from: split_raw_eth_sig_to_felts output
/// '0xda47d5165a4577a024d18b8d61c5fe53 0xe994c0e202b390bddaffacf04bdea826
/// 0x46dd0af587023ccad738aa0a82b05f98 0x2e117624d8cc474d0641c3168944eb67 0x1'
pub fn get_ownership_signature() -> Signature {
Signature {
r: u256 {
low: 0xda47d5165a4577a024d18b8d61c5fe53, high: 0xe994c0e202b390bddaffacf04bdea826,
},
s: u256 {
low: 0x46dd0af587023ccad738aa0a82b05f98, high: 0x2e117624d8cc474d0641c3168944eb67,
},
y_parity: true // v % 2 == 0
}
Comment thread
remollemo marked this conversation as resolved.
}

/// Returns an invalid signature for testing.
pub fn get_invalid_signature() -> Signature {
Signature {
r: u256 { low: 0x1234567890abcdef, high: 0xfedcba0987654321 },
s: u256 { low: 0xabcdef1234567890, high: 0x1234567890abcdef },
y_parity: false,
}
}

/// Declare and deploy the StarknetEth712Account contract.
/// Returns the contract address and the class hash.
pub fn deploy_eth712_account() -> (ContractAddress, ClassHash) {
let contract_class = snforge_std::declare("StarknetEth712Account")
.unwrap_syscall()
.contract_class();
let (contract_address, _) = contract_class.deploy(@array![]).unwrap_syscall();
(contract_address, *contract_class.class_hash)
}

/// Declare the RegisterInterfacesEIC contract and return its class hash.
pub fn declare_register_interfaces_eic() -> ClassHash {
*snforge_std::declare("RegisterInterfacesEIC").unwrap_syscall().contract_class().class_hash
}