Skip to content

Commit 4942030

Browse files
test(payments): add trade tests
1 parent 104e9e9 commit 4942030

File tree

2 files changed

+299
-3
lines changed

2 files changed

+299
-3
lines changed

src/tests/test_payments.cairo

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
use core::num::traits::Zero;
2+
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
3+
use openzeppelin::utils::snip12::OffchainMessageHash;
24
use snforge_std::cheatcodes::events::{EventSpyTrait, EventsFilterTrait};
5+
use snforge_std::signature::stark_curve::{StarkCurveKeyPairImpl, StarkCurveSignerImpl};
36
use snforge_std::{map_entry_address, store};
47
use starknet::ContractAddress;
58
use starknet_payments::errors;
69
use starknet_payments::interface::{
710
IPaymentsDispatcher, IPaymentsDispatcherTrait, IPaymentsSafeDispatcher,
811
IPaymentsSafeDispatcherTrait,
912
};
13+
use starkware_utils::constants::MAX_U128;
1014
use starkware_utils::time::time::Timestamp;
1115
use starkware_utils_testing::constants as testing_constants;
1216
use starkware_utils_testing::test_utils::{
@@ -15,6 +19,7 @@ use starkware_utils_testing::test_utils::{
1519
};
1620
use crate::events;
1721
use crate::order::Order;
22+
use crate::payments::payments::SNIP12MetadataImpl;
1823
use crate::tests::test_utils::*;
1924

2025
fn default_order() -> Order {
@@ -243,3 +248,291 @@ fn test_failed_handle_order() {
243248
let result = dispatcher.cancel_orders(orders: array![default_order()].span());
244249
assert_panic_with_error(:result, expected_error: "ONLY_OPERATOR");
245250
}
251+
252+
#[test]
253+
fn test_successful_trade() {
254+
// Setup:
255+
let (contract_address, token_a, token_b, user_a, user_b, key_pair_a, key_pair_b) = test_setup(
256+
initial_balance: constants::INITIAL_BALANCE,
257+
);
258+
let dispatcher = IPaymentsDispatcher { contract_address };
259+
let mut spy = snforge_std::spy_events();
260+
261+
// Add tokens.
262+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::APP_GOVERNOR);
263+
dispatcher.register_token(token: token_a);
264+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::APP_GOVERNOR);
265+
dispatcher.register_token(token: token_b);
266+
267+
let order_a = Order {
268+
salt: 1,
269+
expiry: Timestamp { seconds: 100 },
270+
user: user_a,
271+
sell_token: token_a,
272+
buy_token: token_b,
273+
sell_amount: 10000,
274+
buy_amount: 1000,
275+
allowed_addresses: array![].span(),
276+
};
277+
278+
let order_b = Order {
279+
user: user_b,
280+
sell_token: token_b,
281+
buy_token: token_a,
282+
sell_amount: 900,
283+
buy_amount: 8000,
284+
allowed_addresses: array![user_a].span(),
285+
..order_a,
286+
};
287+
288+
let message_hash_a = order_a.get_message_hash(user_a);
289+
let (r, s) = key_pair_a.sign(message_hash_a).unwrap();
290+
let signature_a = array![r, s].span();
291+
292+
let message_hash_b = order_b.get_message_hash(user_b);
293+
let (r, s) = key_pair_b.sign(message_hash_b).unwrap();
294+
let signature_b = array![r, s].span();
295+
296+
// Approve tokens:
297+
let token_a_dispatcher = IERC20Dispatcher { contract_address: token_a };
298+
cheat_caller_address_once(token_a, caller_address: user_a);
299+
token_a_dispatcher.approve(spender: contract_address, amount: 10000);
300+
301+
let token_b_dispatcher = IERC20Dispatcher { contract_address: token_b };
302+
cheat_caller_address_once(token_b, caller_address: user_b);
303+
token_b_dispatcher.approve(spender: contract_address, amount: 10000);
304+
305+
// Test:
306+
// 8000/900 <= 7560/850 <= 10000/1000
307+
dispatcher
308+
.trade(
309+
:order_a,
310+
:order_b,
311+
:signature_a,
312+
:signature_b,
313+
order_a_actual_sell_amount: 7560,
314+
order_a_actual_buy_amount: 850,
315+
);
316+
317+
// Checks:
318+
assert_eq!(token_a_dispatcher.balance_of(user_a), 2440);
319+
assert_eq!(token_a_dispatcher.balance_of(user_b), 7484);
320+
assert_eq!(token_a_dispatcher.balance_of(constants::FEE_RECIPIENT), 76);
321+
322+
assert_eq!(token_b_dispatcher.balance_of(user_a), 841);
323+
assert_eq!(token_b_dispatcher.balance_of(user_b), 9150);
324+
assert_eq!(token_b_dispatcher.balance_of(constants::FEE_RECIPIENT), 9);
325+
326+
// Catch the events.
327+
let events = spy.get_events().emitted_by(contract_address).events;
328+
let expected_event = events::TradeExecuted {
329+
user_a,
330+
user_b,
331+
sell_token: token_a,
332+
buy_token: token_b,
333+
order_a_sell_amount: 7560,
334+
order_a_buy_amount: 850,
335+
};
336+
assert_expected_event_emitted(
337+
spied_event: events[2],
338+
expected_event: expected_event,
339+
expected_event_selector: @selector!("TradeExecuted"),
340+
expected_event_name: "TradeExecuted",
341+
);
342+
}
343+
344+
#[test]
345+
fn test_successful_trade_large_numbers() {
346+
// Setup:
347+
let (contract_address, token_a, token_b, user_a, user_b, key_pair_a, key_pair_b) = test_setup(
348+
initial_balance: MAX_U128.into(),
349+
);
350+
let dispatcher = IPaymentsDispatcher { contract_address };
351+
352+
// Add tokens.
353+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::APP_GOVERNOR);
354+
dispatcher.register_token(token: token_a);
355+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::APP_GOVERNOR);
356+
dispatcher.register_token(token: token_b);
357+
358+
let order_a = Order {
359+
salt: 1,
360+
expiry: Timestamp { seconds: 100 },
361+
user: user_a,
362+
sell_token: token_a,
363+
buy_token: token_b,
364+
sell_amount: MAX_U128,
365+
buy_amount: MAX_U128,
366+
allowed_addresses: array![user_b].span(),
367+
};
368+
369+
let order_b = Order {
370+
user: user_b,
371+
sell_token: token_b,
372+
buy_token: token_a,
373+
sell_amount: MAX_U128,
374+
buy_amount: MAX_U128,
375+
allowed_addresses: array![user_a].span(),
376+
..order_a,
377+
};
378+
379+
let message_hash_a = order_a.get_message_hash(user_a);
380+
let (r, s) = key_pair_a.sign(message_hash_a).unwrap();
381+
let signature_a = array![r, s].span();
382+
383+
let message_hash_b = order_b.get_message_hash(user_b);
384+
let (r, s) = key_pair_b.sign(message_hash_b).unwrap();
385+
let signature_b = array![r, s].span();
386+
387+
// Approve tokens:
388+
let token_a_dispatcher = IERC20Dispatcher { contract_address: token_a };
389+
cheat_caller_address_once(token_a, caller_address: user_a);
390+
token_a_dispatcher.approve(spender: contract_address, amount: MAX_U128.into());
391+
392+
let token_b_dispatcher = IERC20Dispatcher { contract_address: token_b };
393+
cheat_caller_address_once(token_b, caller_address: user_b);
394+
token_b_dispatcher.approve(spender: contract_address, amount: MAX_U128.into());
395+
396+
// Test:
397+
dispatcher
398+
.trade(
399+
:order_a,
400+
:order_b,
401+
:signature_a,
402+
:signature_b,
403+
order_a_actual_sell_amount: MAX_U128,
404+
order_a_actual_buy_amount: MAX_U128,
405+
);
406+
407+
// Checks:
408+
// Plus 1 for rounding up.
409+
let fee: u256 = (MAX_U128 / 100 + 1).into();
410+
assert_eq!(token_a_dispatcher.balance_of(user_a), 0);
411+
assert_eq!(token_a_dispatcher.balance_of(user_b), MAX_U128.into() - fee);
412+
assert_eq!(token_a_dispatcher.balance_of(constants::FEE_RECIPIENT), fee);
413+
414+
assert_eq!(token_b_dispatcher.balance_of(user_a), MAX_U128.into() - fee);
415+
assert_eq!(token_b_dispatcher.balance_of(user_b), 0);
416+
assert_eq!(token_b_dispatcher.balance_of(constants::FEE_RECIPIENT), fee);
417+
}
418+
419+
#[test]
420+
fn test_successful_flow() {
421+
// Setup:
422+
let (contract_address, token_a, token_b, user_a, user_b, key_pair_a, key_pair_b) = test_setup(
423+
initial_balance: constants::INITIAL_BALANCE,
424+
);
425+
let dispatcher = IPaymentsDispatcher { contract_address };
426+
427+
// Add tokens.
428+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::APP_GOVERNOR);
429+
dispatcher.register_token(token: token_a);
430+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::APP_GOVERNOR);
431+
dispatcher.register_token(token: token_b);
432+
433+
// Set fee:
434+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::APP_GOVERNOR);
435+
dispatcher.set_fee_limit(fee_limit: 1000);
436+
437+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::OPERATOR);
438+
dispatcher.set_fee(fee: 900); // 9%, in this case it would be rounded to 10%.
439+
440+
let order_a = Order {
441+
salt: 1,
442+
expiry: Timestamp { seconds: 100 },
443+
user: user_a,
444+
sell_token: token_a,
445+
buy_token: token_b,
446+
sell_amount: 100,
447+
buy_amount: 10,
448+
allowed_addresses: array![].span(),
449+
};
450+
451+
let order_b = Order {
452+
user: user_b,
453+
sell_token: token_b,
454+
buy_token: token_a,
455+
sell_amount: 5,
456+
buy_amount: 50,
457+
allowed_addresses: array![user_a].span(),
458+
..order_a,
459+
};
460+
461+
let message_hash_a = order_a.get_message_hash(user_a);
462+
let (r, s) = key_pair_a.sign(message_hash_a).unwrap();
463+
let signature_a = array![r, s].span();
464+
465+
let message_hash_b = order_b.get_message_hash(user_b);
466+
let (r, s) = key_pair_b.sign(message_hash_b).unwrap();
467+
let signature_b = array![r, s].span();
468+
469+
// Approve tokens:
470+
let token_a_dispatcher = IERC20Dispatcher { contract_address: token_a };
471+
cheat_caller_address_once(token_a, caller_address: user_a);
472+
token_a_dispatcher.approve(spender: contract_address, amount: 10000);
473+
474+
let token_b_dispatcher = IERC20Dispatcher { contract_address: token_b };
475+
cheat_caller_address_once(token_b, caller_address: user_b);
476+
token_b_dispatcher.approve(spender: contract_address, amount: 10000);
477+
478+
// Stage 1:
479+
480+
// Test:
481+
dispatcher
482+
.trade(
483+
:order_a,
484+
:order_b,
485+
:signature_a,
486+
:signature_b,
487+
order_a_actual_sell_amount: 50,
488+
order_a_actual_buy_amount: 5,
489+
);
490+
491+
// Checks:
492+
assert_eq!(token_a_dispatcher.balance_of(user_a), 9950);
493+
assert_eq!(token_a_dispatcher.balance_of(user_b), 45);
494+
assert_eq!(token_a_dispatcher.balance_of(constants::FEE_RECIPIENT), 5);
495+
496+
assert_eq!(token_b_dispatcher.balance_of(user_a), 4);
497+
assert_eq!(token_b_dispatcher.balance_of(user_b), 9995);
498+
assert_eq!(token_b_dispatcher.balance_of(constants::FEE_RECIPIENT), 1);
499+
500+
assert_eq!(dispatcher.get_order_fulfillment(message_hash_a), 50);
501+
assert_eq!(dispatcher.get_order_fulfillment(message_hash_b), 5);
502+
503+
// Stage 2:
504+
505+
let new_fee_recipient: ContractAddress = 'NEW_FEE_RECIPIENT'.try_into().unwrap();
506+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::OPERATOR);
507+
dispatcher.set_fee_recipient(recipient: new_fee_recipient);
508+
509+
let order_b = Order { salt: 2, ..order_b };
510+
let message_hash_b = order_b.get_message_hash(user_b);
511+
let (r, s) = key_pair_b.sign(message_hash_b).unwrap();
512+
let signature_b = array![r, s].span();
513+
514+
dispatcher
515+
.trade(
516+
:order_a,
517+
:order_b,
518+
:signature_a,
519+
:signature_b,
520+
order_a_actual_sell_amount: 50,
521+
order_a_actual_buy_amount: 5,
522+
);
523+
524+
// Checks:
525+
assert_eq!(token_a_dispatcher.balance_of(user_a), 9900);
526+
assert_eq!(token_a_dispatcher.balance_of(user_b), 90);
527+
assert_eq!(token_a_dispatcher.balance_of(constants::FEE_RECIPIENT), 5);
528+
assert_eq!(token_a_dispatcher.balance_of(new_fee_recipient), 5);
529+
530+
assert_eq!(token_b_dispatcher.balance_of(user_a), 8);
531+
assert_eq!(token_b_dispatcher.balance_of(user_b), 9990);
532+
assert_eq!(token_b_dispatcher.balance_of(constants::FEE_RECIPIENT), 1);
533+
assert_eq!(token_b_dispatcher.balance_of(new_fee_recipient), 1);
534+
535+
assert_eq!(dispatcher.get_order_fulfillment(message_hash_a), 100);
536+
assert_eq!(dispatcher.get_order_fulfillment(message_hash_b), 5);
537+
}
538+

src/tests/test_utils.cairo

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub mod constants {
1313
pub const FEE_LIMIT: u128 = 1000;
1414
pub const FEE_RECIPIENT: ContractAddress = 'FEE_RECIPIENT'.try_into().unwrap();
1515
pub const FEE: u128 = 100;
16+
pub const INITIAL_BALANCE: u256 = 10_000;
1617
}
1718

1819
fn deploy_contract() -> ContractAddress {
@@ -33,7 +34,9 @@ pub fn init_contract_with_roles() -> ContractAddress {
3334
contract_address
3435
}
3536

36-
pub fn test_setup() -> (
37+
pub fn test_setup(
38+
initial_balance: u256,
39+
) -> (
3740
ContractAddress,
3841
ContractAddress,
3942
ContractAddress,
@@ -57,8 +60,8 @@ pub fn test_setup() -> (
5760
let token_a = Token::STRK.contract_address();
5861
let token_b = Token::ETH.contract_address();
5962

60-
set_balance(user_a, 10_000, Token::STRK);
61-
set_balance(user_b, 10_000, Token::ETH);
63+
set_balance(user_a, initial_balance, Token::STRK);
64+
set_balance(user_b, initial_balance, Token::ETH);
6265

6366
(contract_address, token_a, token_b, user_a, user_b, key_pair_a, key_pair_b)
6467
}

0 commit comments

Comments
 (0)