Skip to content

Commit 9a2a6cc

Browse files
feat(payments): add address whitelist
1 parent 104e9e9 commit 9a2a6cc

File tree

5 files changed

+131
-6
lines changed

5 files changed

+131
-6
lines changed

src/errors.cairo

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use starknet::ContractAddress;
22

3+
pub const ADDRESS_ALREADY_WHITELISTED: felt252 = 'ADDRESS_ALREADY_WHITELISTED';
4+
pub const ADDRESS_NOT_WHITELISTED: felt252 = 'ADDRESS_NOT_WHITELISTED';
35
pub const INVALID_AMOUNT_RATIO: felt252 = 'INVALID_AMOUNT_RATIO';
46
pub const INVALID_AMOUNT_TOO_LARGE: felt252 = 'INVALID_AMOUNT_TOO_LARGE';
57
pub const INVALID_DOWNCAST_AFTER_DIVISION: felt252 = 'INVALID_DOWNCAST_AFTER_DIVISION';

src/events.cairo

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ pub struct TokenRemoved {
2525
pub token: ContractAddress,
2626
}
2727

28+
#[derive(Debug, Drop, PartialEq, starknet::Event)]
29+
pub struct AddressWhitelisted {
30+
#[key]
31+
pub address: ContractAddress,
32+
}
33+
34+
#[derive(Debug, Drop, PartialEq, starknet::Event)]
35+
pub struct AddressRemovedFromWhitelist {
36+
#[key]
37+
pub address: ContractAddress,
38+
}
39+
2840
#[derive(Debug, Drop, PartialEq, starknet::Event)]
2941
pub struct TradeExecuted {
3042
#[key]

src/interface.cairo

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ pub trait IPayments<TContractState> {
1818
fn remove_token(ref self: TContractState, token: ContractAddress);
1919
fn is_token_registered(self: @TContractState, token: ContractAddress) -> bool;
2020

21+
fn whitelist_address(ref self: TContractState, address: ContractAddress);
22+
fn remove_from_whitelist(ref self: TContractState, address: ContractAddress);
23+
fn is_whitelisted(self: @TContractState, address: ContractAddress) -> bool;
24+
2125
fn cancel_orders(ref self: TContractState, orders: Span<Order>);
2226

2327
// Setters:

src/payments.cairo

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ pub mod payments {
2121
use starkware_utils::signature::stark::{HashType, Signature};
2222
use starkware_utils::time::time::Time;
2323
use crate::errors::{
24-
INVALID_AMOUNT_RATIO, INVALID_AMOUNT_TOO_LARGE, INVALID_DOWNCAST_AFTER_DIVISION,
25-
INVALID_HIGH_FEE, INVALID_HIGH_FEE_LIMIT, INVALID_TOKEN_PAIR, INVALID_TRADE_SAME_USER,
26-
INVALID_ZERO_ADDRESS, INVALID_ZERO_AMOUNT, INVALID_ZERO_TOKEN, ORDER_EXPIRED,
27-
TOKEN_ALREADY_REGISTERED, TOKEN_NOT_REGISTERED, UNALLOWED_ADDRESS, transfer_failed_error,
24+
ADDRESS_ALREADY_WHITELISTED, ADDRESS_NOT_WHITELISTED, INVALID_AMOUNT_RATIO,
25+
INVALID_AMOUNT_TOO_LARGE, INVALID_DOWNCAST_AFTER_DIVISION, INVALID_HIGH_FEE,
26+
INVALID_HIGH_FEE_LIMIT, INVALID_TOKEN_PAIR, INVALID_TRADE_SAME_USER, INVALID_ZERO_ADDRESS,
27+
INVALID_ZERO_AMOUNT, INVALID_ZERO_TOKEN, ORDER_EXPIRED, TOKEN_ALREADY_REGISTERED,
28+
TOKEN_NOT_REGISTERED, UNALLOWED_ADDRESS, transfer_failed_error,
2829
};
2930
use crate::events::{
30-
FeeRecipientSet, FeeSet, OrderCanceled, TokenRegistered, TokenRemoved, TradeExecuted,
31+
AddressRemovedFromWhitelist, AddressWhitelisted, FeeRecipientSet, FeeSet, OrderCanceled,
32+
TokenRegistered, TokenRemoved, TradeExecuted,
3133
};
3234
use crate::interface::IPayments;
3335
use crate::order::Order;
@@ -85,6 +87,8 @@ pub mod payments {
8587
fee: u128,
8688
// Whitelisted tokens.
8789
tokens: Map<ContractAddress, bool>,
90+
// Whitelisted addresses.
91+
whitelist: Map<ContractAddress, bool>,
8892
// Order hash to fulfilled sell amount.
8993
fulfillment: Map<HashType, u128>,
9094
}
@@ -106,6 +110,8 @@ pub mod payments {
106110
FeeRecipientSet: FeeRecipientSet,
107111
TokenRegistered: TokenRegistered,
108112
TokenRemoved: TokenRemoved,
113+
AddressWhitelisted: AddressWhitelisted,
114+
AddressRemovedFromWhitelist: AddressRemovedFromWhitelist,
109115
TradeExecuted: TradeExecuted,
110116
OrderCanceled: OrderCanceled,
111117
}
@@ -282,6 +288,35 @@ pub mod payments {
282288
self.tokens.read(token)
283289
}
284290

291+
// These functions are used by the contract to control who is allowed to take part in order
292+
// matching and settlement. Only whitelisted addresses are permitted to trade.
293+
294+
fn whitelist_address(ref self: ContractState, address: ContractAddress) {
295+
self.roles.only_app_governor();
296+
297+
assert(!self.is_whitelisted(address), ADDRESS_ALREADY_WHITELISTED);
298+
self.whitelist.write(address, true);
299+
300+
// Emit an event.
301+
self.emit(AddressWhitelisted { address });
302+
}
303+
304+
fn remove_from_whitelist(ref self: ContractState, address: ContractAddress) {
305+
self.roles.only_app_governor();
306+
307+
assert(self.is_whitelisted(address), ADDRESS_NOT_WHITELISTED);
308+
self.whitelist.write(address, false);
309+
310+
// Emit an event.
311+
self.emit(AddressRemovedFromWhitelist { address });
312+
}
313+
314+
fn is_whitelisted(self: @ContractState, address: ContractAddress) -> bool {
315+
assert(address.is_non_zero(), INVALID_ZERO_ADDRESS);
316+
self.whitelist.read(address)
317+
}
318+
319+
285320
fn cancel_orders(ref self: ContractState, orders: Span<Order>) {
286321
self.roles.only_operator();
287322

@@ -370,7 +405,7 @@ pub mod payments {
370405
/// amounts.
371406
fn _validate_order(self: @ContractState, order: Order) {
372407
assert(order.expiry >= Time::now(), ORDER_EXPIRED);
373-
assert(order.user.is_non_zero(), INVALID_ZERO_ADDRESS);
408+
assert(self.is_whitelisted(order.user), ADDRESS_NOT_WHITELISTED);
374409

375410
assert(order.sell_amount.is_non_zero(), INVALID_ZERO_AMOUNT);
376411
assert(order.buy_amount.is_non_zero(), INVALID_ZERO_AMOUNT);

src/tests/test_payments.cairo

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,78 @@ fn test_failed_register_token() {
102102
assert_panic_with_felt_error(:result, expected_error: errors::TOKEN_ALREADY_REGISTERED);
103103
}
104104

105+
#[test]
106+
fn test_successful_address_whitelist() {
107+
let contract_address = init_contract_with_roles();
108+
let dispatcher = IPaymentsDispatcher { contract_address };
109+
let mut spy = snforge_std::spy_events();
110+
111+
let user_a: ContractAddress = 'user_a'.try_into().unwrap();
112+
let user_b: ContractAddress = 'user_b'.try_into().unwrap();
113+
114+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::APP_GOVERNOR);
115+
dispatcher.whitelist_address(address: user_a);
116+
assert!(dispatcher.is_whitelisted(address: user_a));
117+
assert!(!dispatcher.is_whitelisted(address: user_b));
118+
119+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::APP_GOVERNOR);
120+
dispatcher.whitelist_address(address: user_b);
121+
assert!(dispatcher.is_whitelisted(address: user_a));
122+
assert!(dispatcher.is_whitelisted(address: user_b));
123+
124+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::APP_GOVERNOR);
125+
dispatcher.remove_from_whitelist(address: user_a);
126+
assert!(!dispatcher.is_whitelisted(address: user_a));
127+
assert!(dispatcher.is_whitelisted(address: user_b));
128+
129+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::APP_GOVERNOR);
130+
dispatcher.remove_from_whitelist(address: user_b);
131+
assert!(!dispatcher.is_whitelisted(address: user_a));
132+
assert!(!dispatcher.is_whitelisted(address: user_b));
133+
134+
// Catch the events.
135+
let events = spy.get_events().emitted_by(contract_address).events;
136+
let expected_register_token_event = events::AddressWhitelisted { address: user_a };
137+
assert_expected_event_emitted(
138+
spied_event: events[0],
139+
expected_event: expected_register_token_event,
140+
expected_event_selector: @selector!("AddressWhitelisted"),
141+
expected_event_name: "AddressWhitelisted",
142+
);
143+
let expected_remove_token_event = events::AddressRemovedFromWhitelist { address: user_b };
144+
assert_expected_event_emitted(
145+
spied_event: events[3],
146+
expected_event: expected_remove_token_event,
147+
expected_event_selector: @selector!("AddressRemovedFromWhitelist"),
148+
expected_event_name: "AddressRemovedFromWhitelist",
149+
);
150+
}
151+
152+
#[test]
153+
#[feature("safe_dispatcher")]
154+
fn test_failed_address_whitelist() {
155+
let contract_address = init_contract_with_roles();
156+
let dispatcher = IPaymentsSafeDispatcher { contract_address };
157+
let user: ContractAddress = 'user'.try_into().unwrap();
158+
159+
let result = dispatcher.remove_from_whitelist(address: user);
160+
assert_panic_with_error(:result, expected_error: "ONLY_APP_GOVERNOR");
161+
162+
let result = dispatcher.whitelist_address(address: user);
163+
assert_panic_with_error(:result, expected_error: "ONLY_APP_GOVERNOR");
164+
165+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::APP_GOVERNOR);
166+
let result = dispatcher.remove_from_whitelist(address: user);
167+
assert_panic_with_felt_error(:result, expected_error: errors::ADDRESS_NOT_WHITELISTED);
168+
169+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::APP_GOVERNOR);
170+
dispatcher.whitelist_address(address: user).unwrap();
171+
172+
cheat_caller_address_once(:contract_address, caller_address: testing_constants::APP_GOVERNOR);
173+
let result = dispatcher.whitelist_address(address: user);
174+
assert_panic_with_felt_error(:result, expected_error: errors::ADDRESS_ALREADY_WHITELISTED);
175+
}
176+
105177
#[test]
106178
fn test_successful_set_fee() {
107179
let contract_address = init_contract_with_roles();

0 commit comments

Comments
 (0)