Skip to content

Commit

Permalink
Test execute (#116)
Browse files Browse the repository at this point in the history
* add mock erc20

* refac

* add tests

* clean up + fmt

* rename craft_call to craft_erc20_transfer_call
  • Loading branch information
manlikeHB authored Nov 2, 2024
1 parent d393991 commit 173bd31
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 21 deletions.
6 changes: 6 additions & 0 deletions snphone-contracts/Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ version = 1
name = "contracts"
version = "0.1.0"
dependencies = [
"openzeppelin",
"snforge_std",
]

[[package]]
name = "openzeppelin"
version = "0.10.0"
source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.10.0#d77082732daab2690ba50742ea41080eb23299d3"

[[package]]
name = "snforge_std"
version = "0.20.0"
Expand Down
1 change: 1 addition & 0 deletions snphone-contracts/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ version = "0.1.0"

[dependencies]
starknet = "2.6.3"
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.10.0" }

[dev-dependencies]
snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.20.0" }
Expand Down
1 change: 1 addition & 0 deletions snphone-contracts/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod account;
pub mod mocks;
1 change: 1 addition & 0 deletions snphone-contracts/src/mocks.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod erc20_mock;
36 changes: 36 additions & 0 deletions snphone-contracts/src/mocks/erc20_mock.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#[starknet::contract]
pub mod ERC20Mock {
use openzeppelin::token::erc20::{ERC20Component};
use starknet::ContractAddress;

component!(path: ERC20Component, storage: erc20, event: ERC20Event);

#[abi(embed_v0)]
impl ERC20Impl = ERC20Component::ERC20Impl<ContractState>;
impl InternalImpl = ERC20Component::InternalImpl<ContractState>;

#[storage]
struct Storage {
#[substorage(v0)]
erc20: ERC20Component::Storage
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
ERC20Event: ERC20Component::Event
}

#[constructor]
fn constructor(
ref self: ContractState,
name: ByteArray,
symbol: ByteArray,
initial_supply: u256,
recipient: ContractAddress
) {
self.erc20.initializer(name, symbol);
self.erc20._mint(recipient, initial_supply);
}
}
186 changes: 165 additions & 21 deletions snphone-contracts/tests/test_account.cairo
Original file line number Diff line number Diff line change
@@ -1,31 +1,83 @@
use snforge_std::{declare, ContractClassTrait};
use snforge_std::{start_prank, stop_prank, CheatTarget};
use snforge_std::{declare, ContractClassTrait, ContractClass};
use snforge_std::{start_prank, stop_prank, CheatTarget, prank, CheatSpan};
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
use starknet::contract_address_const;
use starknet::{ContractAddress, {account::Call}};
use contracts::account::{IStarknetPhoneAccountDispatcher, IStarknetPhoneAccountDispatcherTrait};

#[test]
fn test_deploy() {
const PUB_KEY: felt252 = 'pub_key';
const INITIAL_SUPPLY: u256 = 1000;

fn OWNER() -> ContractAddress {
'OWNER'.try_into().unwrap()
}

fn RECIPIENT() -> ContractAddress {
'RECIPIENT'.try_into().unwrap()
}

fn BOB() -> ContractAddress {
'BOB'.try_into().unwrap()
}

fn ALICE() -> ContractAddress {
'ALICE'.try_into().unwrap()
}

fn deploy_erc20_mock(name: ByteArray, symbol: ByteArray,) -> IERC20Dispatcher {
let class = declare("ERC20Mock");
let mut calldata = array![];
name.serialize(ref calldata);
symbol.serialize(ref calldata);
INITIAL_SUPPLY.serialize(ref calldata);
OWNER().serialize(ref calldata);

let contract_address = class.deploy(@calldata).unwrap();

IERC20Dispatcher { contract_address }
}

fn deploy_wallet() -> IStarknetPhoneAccountDispatcher {
let class_hash = declare("StarknetPhoneAccount");
let _pub_key = 'pub_key';
let contract_address = class_hash.deploy(@array![_pub_key]).unwrap();
let contract_address = class_hash.deploy(@array![PUB_KEY]).unwrap();
let wallet = IStarknetPhoneAccountDispatcher { contract_address };
wallet
}

fn _setup() -> (IStarknetPhoneAccountDispatcher, IERC20Dispatcher) {
let mock_erc20_dispatcher = deploy_erc20_mock("mock one", "MCK1");
let wallet_dispatcher = deploy_wallet();

(wallet_dispatcher, mock_erc20_dispatcher)
}

fn craft_erc20_transfer_call(
to: ContractAddress, recipient: ContractAddress, amount: u256
) -> Call {
let mut calldata = array![];
recipient.serialize(ref calldata);
amount.serialize(ref calldata);

Call { to, selector: selector!("transfer"), calldata: calldata.span() }
}

#[test]
fn test_deploy() {
let (wallet, _) = _setup();

let pub_key = wallet.get_public_key();
assert(pub_key == _pub_key, 'Pub key not set');
assert(pub_key == PUB_KEY, 'Pub key not set');
}

// Test that only the contract owner can change the public key
#[test]
fn test_only_account_can_change_public_key() {
let class_hash = declare("StarknetPhoneAccount");
let _pub_key = 'pub_key';
let contract_address = class_hash.deploy(@array![_pub_key]).unwrap();
let wallet = IStarknetPhoneAccountDispatcher { contract_address };
let (wallet, _) = _setup();

// Other contract calls function
let new_pub_key = 'new_pub_key';

start_prank(CheatTarget::One(wallet.contract_address), contract_address);
start_prank(CheatTarget::One(wallet.contract_address), wallet.contract_address);
wallet.set_public_key(new_pub_key);
stop_prank(CheatTarget::One(wallet.contract_address));

Expand All @@ -36,10 +88,7 @@ fn test_only_account_can_change_public_key() {
#[test]
#[should_panic]
fn test_other_account_cannot_change_public_key() {
let class_hash = declare("StarknetPhoneAccount");
let _pub_key = 'pub_key';
let contract_address = class_hash.deploy(@array![_pub_key]).unwrap();
let wallet = IStarknetPhoneAccountDispatcher { contract_address };
let (wallet, _) = _setup();

// Other contract calls function
let not_wallet = contract_address_const::<'not_wallet'>();
Expand All @@ -55,10 +104,105 @@ fn test_other_account_cannot_change_public_key() {
//fn test_is_valid_signature() { // TODO: Test is_valid_signature() works as expected (valid returns true, anything else returns false (check 0 hash and empty sigs as well))
//}

//#[test]
//fn test_execute() { // TODO: Test __execute__() works as expected (solo and multi-calls should work as expected)
// - Might need to create a mock erc20 contract to test calls (see if the wallet is able to do a multi call (try sending eth to 2 accounts from the
// deployed wallet, both accounts' balance should update)
//}
#[test]
#[should_panic(expected: ('invalid caller',))]
fn test_execute_with_invalid_caller() {
let (wallet, mock_erc20) = _setup();

// Other contract calls function
let not_wallet = contract_address_const::<'not_wallet'>();

// Craft call and add to calls array
let amount = 200;
let mut calldata = array![];
mock_erc20.contract_address.serialize(ref calldata);
RECIPIENT().serialize(ref calldata);
amount.serialize(ref calldata);

let call = Call {
to: mock_erc20.contract_address, selector: selector!("transfer"), calldata: calldata.span()
};

let calls = array![call];

start_prank(CheatTarget::One(wallet.contract_address), not_wallet);
wallet.__execute__(calls);
stop_prank(CheatTarget::One(wallet.contract_address));
}

#[test]
fn test_execute() {
let (wallet, mock_erc20) = _setup();

// fund wallet
start_prank(CheatTarget::One(mock_erc20.contract_address), OWNER());
mock_erc20.transfer(wallet.contract_address, INITIAL_SUPPLY);
stop_prank(CheatTarget::One(mock_erc20.contract_address));

// Craft call and add to calls array
let amount = 200_u256;
let call = craft_erc20_transfer_call(mock_erc20.contract_address, RECIPIENT(), amount);

let calls = array![call];

let zero = contract_address_const::<0>();

let wallet_ballance_before = mock_erc20.balance_of(wallet.contract_address);

// execute
start_prank(CheatTarget::One(wallet.contract_address), zero);
wallet.__execute__(calls);
stop_prank(CheatTarget::One(wallet.contract_address));

let wallet_ballance_after = mock_erc20.balance_of(wallet.contract_address);

assert((wallet_ballance_before - amount) == wallet_ballance_after, 'wrong wallet balance');
assert(mock_erc20.balance_of(RECIPIENT()) == amount, 'wrong recipient balance');
}

#[test]
fn test_multicall() {
let (wallet, mock_erc20) = _setup();

// fund wallet
start_prank(CheatTarget::One(mock_erc20.contract_address), OWNER());
mock_erc20.transfer(wallet.contract_address, INITIAL_SUPPLY);
stop_prank(CheatTarget::One(mock_erc20.contract_address));

let first_amount = 300_u256;
let second_amount = 100_u256;
let third_amount = 150_u256;
let forth_amount = 50_u256;

// Craft call and add to calls array
let first_call = craft_erc20_transfer_call(
mock_erc20.contract_address, RECIPIENT(), first_amount
);
let second_call = craft_erc20_transfer_call(
mock_erc20.contract_address, OWNER(), second_amount
);
let third_call = craft_erc20_transfer_call(mock_erc20.contract_address, BOB(), third_amount);
let forth_call = craft_erc20_transfer_call(mock_erc20.contract_address, ALICE(), forth_amount);

let calls = array![first_call, second_call, third_call, forth_call];

let zero = contract_address_const::<0>();

// execute
start_prank(CheatTarget::One(wallet.contract_address), zero);
wallet.__execute__(calls);
stop_prank(CheatTarget::One(wallet.contract_address));

let wallet_ballance_after = mock_erc20.balance_of(wallet.contract_address);
let expected_wallet_balance = INITIAL_SUPPLY
- first_amount
- second_amount
- third_amount
- forth_amount;

assert(wallet_ballance_after == expected_wallet_balance, 'wrong wallet balance');
assert(mock_erc20.balance_of(RECIPIENT()) == first_amount, 'wrong recipient balance');
assert(mock_erc20.balance_of(OWNER()) == second_amount, 'wrong owner balance');
assert(mock_erc20.balance_of(BOB()) == third_amount, 'wrong bob balance');
assert(mock_erc20.balance_of(ALICE()) == forth_amount, 'wrong alice balance');
}

0 comments on commit 173bd31

Please sign in to comment.