Skip to content

Commit 400eb9d

Browse files
committed
test: Orderbook TXE tests
1 parent 267e2b0 commit 400eb9d

3 files changed

Lines changed: 333 additions & 3 deletions

File tree

noir-projects/noir-contracts/contracts/app/orderbook_contract/src/main.nr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod config;
22
mod order;
3+
mod test;
34

45
use aztec::macros::aztec;
56

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
use crate::Orderbook;
2+
use aztec::{
3+
protocol_types::{address::AztecAddress, traits::FromField},
4+
test::helpers::{
5+
authwit::add_private_authwit_from_call_interface, test_environment::TestEnvironment,
6+
},
7+
};
8+
use token::Token;
9+
use uint_note::uint_note::PartialUintNote;
10+
11+
// Utility function to setup two token contracts
12+
unconstrained fn setup_tokens(
13+
env: &mut TestEnvironment,
14+
owner: AztecAddress,
15+
) -> (AztecAddress, AztecAddress) {
16+
// Deploy first token contract
17+
let token0_initializer = Token::interface().constructor(
18+
owner,
19+
"Token00000000000000000000000000",
20+
"TK00000000000000000000000000000",
21+
18,
22+
);
23+
let token0_address =
24+
env.deploy("@token_contract/Token").with_public_initializer(owner, token0_initializer);
25+
26+
// Deploy second token contract
27+
let token1_initializer = Token::interface().constructor(
28+
owner,
29+
"Token11111111111111111111111111",
30+
"TK11111111111111111111111111111",
31+
18,
32+
);
33+
let token1_address =
34+
env.deploy("@token_contract/Token").with_public_initializer(owner, token1_initializer);
35+
36+
(token0_address, token1_address)
37+
}
38+
39+
// Utility function to setup orderbook with two tokens
40+
unconstrained fn setup_orderbook_with_tokens(
41+
with_account_contracts: bool,
42+
) -> (TestEnvironment, AztecAddress, AztecAddress, AztecAddress, AztecAddress, AztecAddress) {
43+
let mut env = TestEnvironment::new();
44+
let owner = if with_account_contracts {
45+
env.create_contract_account()
46+
} else {
47+
env.create_light_account()
48+
};
49+
50+
let (token0_address, token1_address) = setup_tokens(&mut env, owner);
51+
52+
// Deploy orderbook contract
53+
let orderbook_initializer = Orderbook::interface().constructor(token0_address, token1_address);
54+
let orderbook_address =
55+
env.deploy("Orderbook").with_public_initializer(owner, orderbook_initializer);
56+
57+
(env, orderbook_address, token0_address, token1_address, owner, owner)
58+
}
59+
60+
// Utility function to mint tokens to private balance
61+
unconstrained fn mint_to_private(
62+
env: TestEnvironment,
63+
token_address: AztecAddress,
64+
owner: AztecAddress,
65+
to: AztecAddress,
66+
amount: u128,
67+
) {
68+
env.call_private(owner, Token::at(token_address).mint_to_private(to, amount));
69+
}
70+
71+
// Utility function to check private balance
72+
unconstrained fn check_private_balance(
73+
env: TestEnvironment,
74+
token_address: AztecAddress,
75+
address: AztecAddress,
76+
expected_amount: u128,
77+
) {
78+
assert_eq(
79+
env.simulate_utility(Token::at(token_address).balance_of_private(address)),
80+
expected_amount,
81+
);
82+
}
83+
84+
// Utility function to check public balance
85+
unconstrained fn check_public_balance(
86+
env: TestEnvironment,
87+
token_address: AztecAddress,
88+
address: AztecAddress,
89+
expected_amount: u128,
90+
) {
91+
assert_eq(
92+
env.view_public(Token::at(token_address).balance_of_public(address)),
93+
expected_amount,
94+
);
95+
}
96+
97+
// Happy path tests
98+
99+
#[test]
100+
unconstrained fn create_order_happy_path() {
101+
let (mut env, orderbook_address, token0_address, token1_address, owner, _) =
102+
setup_orderbook_with_tokens(true);
103+
104+
let bid_amount = 1000 as u128;
105+
let ask_amount = 2000 as u128;
106+
let authwit_nonce = 1;
107+
108+
// Mint tokens to owner
109+
mint_to_private(env, token0_address, owner, owner, bid_amount);
110+
111+
// Create authwit for transferring tokens to orderbook
112+
let transfer_call_interface = Token::at(token0_address).transfer_to_public(
113+
owner,
114+
orderbook_address,
115+
bid_amount,
116+
authwit_nonce,
117+
);
118+
add_private_authwit_from_call_interface(owner, orderbook_address, transfer_call_interface);
119+
120+
// Create order
121+
let _order_id = env.call_private(
122+
owner,
123+
Orderbook::at(orderbook_address).create_order(
124+
token0_address,
125+
token1_address,
126+
bid_amount,
127+
ask_amount,
128+
authwit_nonce,
129+
),
130+
);
131+
132+
// Verify tokens were transferred to orderbook's public balance
133+
check_public_balance(env, token0_address, orderbook_address, bid_amount);
134+
check_private_balance(env, token0_address, owner, 0);
135+
}
136+
137+
#[test]
138+
unconstrained fn full_flow() {
139+
let (mut env, orderbook_address, token0_address, token1_address, maker, _) =
140+
setup_orderbook_with_tokens(true);
141+
142+
let taker = env.create_contract_account();
143+
let bid_amount = 1000 as u128;
144+
let ask_amount = 2000 as u128;
145+
let authwit_nonce_create = 1;
146+
let authwit_nonce_fulfill = 2;
147+
148+
// Setup: mint tokens to maker and taker
149+
mint_to_private(env, token0_address, maker, maker, bid_amount);
150+
mint_to_private(env, token1_address, maker, taker, ask_amount);
151+
152+
// Create order first
153+
let transfer_call_interface = Token::at(token0_address).transfer_to_public(
154+
maker,
155+
orderbook_address,
156+
bid_amount,
157+
authwit_nonce_create,
158+
);
159+
add_private_authwit_from_call_interface(maker, orderbook_address, transfer_call_interface);
160+
161+
let order_id = env.call_private(
162+
maker,
163+
Orderbook::at(orderbook_address).create_order(
164+
token0_address,
165+
token1_address,
166+
bid_amount,
167+
ask_amount,
168+
authwit_nonce_create,
169+
),
170+
);
171+
172+
// Get order and verify it's active
173+
let (order, is_fulfilled) =
174+
env.simulate_utility(Orderbook::at(orderbook_address).get_order(order_id));
175+
176+
assert_eq(order.bid_amount, bid_amount);
177+
assert_eq(order.ask_amount, ask_amount);
178+
assert_eq(order.bid_token_is_zero, true); // token0 -> token1
179+
assert_eq(is_fulfilled, false);
180+
181+
// Create authwit for taker to transfer ask tokens
182+
// Convert order_id back to PartialUintNote as the orderbook does
183+
let maker_partial_note = PartialUintNote::from_field(order_id);
184+
let fulfill_transfer_call_interface = Token::at(token1_address)
185+
.finalize_transfer_to_private_from_private(
186+
taker,
187+
maker_partial_note,
188+
ask_amount,
189+
authwit_nonce_fulfill,
190+
);
191+
add_private_authwit_from_call_interface(
192+
taker,
193+
orderbook_address,
194+
fulfill_transfer_call_interface,
195+
);
196+
197+
// Fulfill order
198+
env.call_private(
199+
taker,
200+
Orderbook::at(orderbook_address).fulfill_order(order_id, authwit_nonce_fulfill),
201+
);
202+
203+
// Verify final balances
204+
check_private_balance(env, token0_address, maker, 0);
205+
check_private_balance(env, token1_address, maker, ask_amount);
206+
check_private_balance(env, token0_address, taker, bid_amount);
207+
check_private_balance(env, token1_address, taker, 0);
208+
209+
// Get order and verify it's fulfilled
210+
let (order, is_fulfilled) =
211+
env.simulate_utility(Orderbook::at(orderbook_address).get_order(order_id));
212+
213+
assert_eq(order.bid_amount, bid_amount);
214+
assert_eq(order.ask_amount, ask_amount);
215+
assert_eq(order.bid_token_is_zero, true); // token0 -> token1
216+
assert_eq(is_fulfilled, true);
217+
}
218+
219+
// Unhappy path tests for create_order
220+
221+
#[test(should_fail_with = "ZERO_BID_AMOUNT")]
222+
unconstrained fn create_order_zero_bid_amount() {
223+
let (mut env, orderbook_address, token0_address, token1_address, owner, _) =
224+
setup_orderbook_with_tokens(false);
225+
226+
let bid_amount = 0 as u128;
227+
let ask_amount = 2000 as u128;
228+
let authwit_nonce = 1;
229+
230+
// This should fail when creating the order due to zero bid amount
231+
let _order_id = env.call_private(
232+
owner,
233+
Orderbook::at(orderbook_address).create_order(
234+
token0_address,
235+
token1_address,
236+
bid_amount,
237+
ask_amount,
238+
authwit_nonce,
239+
),
240+
);
241+
}
242+
243+
#[test(should_fail_with = "ZERO_ASK_AMOUNT")]
244+
unconstrained fn create_order_zero_ask_amount() {
245+
let (mut env, orderbook_address, token0_address, token1_address, owner, _) =
246+
setup_orderbook_with_tokens(false);
247+
248+
let bid_amount = 1000 as u128;
249+
let ask_amount = 0 as u128;
250+
let authwit_nonce = 1;
251+
252+
// This should fail when creating the order due to zero ask amount
253+
let _order_id = env.call_private(
254+
owner,
255+
Orderbook::at(orderbook_address).create_order(
256+
token0_address,
257+
token1_address,
258+
bid_amount,
259+
ask_amount,
260+
authwit_nonce,
261+
),
262+
);
263+
}
264+
265+
#[test(should_fail_with = "BID_TOKEN_IS_INVALID")]
266+
unconstrained fn create_order_invalid_bid_token() {
267+
let (mut env, orderbook_address, _token0_address, token1_address, owner, _) =
268+
setup_orderbook_with_tokens(false);
269+
270+
let invalid_token = AztecAddress::from_field(999);
271+
let bid_amount = 1000 as u128;
272+
let ask_amount = 2000 as u128;
273+
let authwit_nonce = 1;
274+
275+
// This should fail due to invalid bid token
276+
let _order_id = env.call_private(
277+
owner,
278+
Orderbook::at(orderbook_address).create_order(
279+
invalid_token,
280+
token1_address,
281+
bid_amount,
282+
ask_amount,
283+
authwit_nonce,
284+
),
285+
);
286+
}
287+
288+
#[test(should_fail_with = "ASK_TOKEN_IS_INVALID")]
289+
unconstrained fn create_order_invalid_ask_token() {
290+
let (mut env, orderbook_address, token0_address, _token1_address, owner, _) =
291+
setup_orderbook_with_tokens(false);
292+
293+
let invalid_token = AztecAddress::from_field(999);
294+
let bid_amount = 1000 as u128;
295+
let ask_amount = 2000 as u128;
296+
let authwit_nonce = 1;
297+
298+
// This should fail due to invalid ask token
299+
let _order_id = env.call_private(
300+
owner,
301+
Orderbook::at(orderbook_address).create_order(
302+
token0_address,
303+
invalid_token,
304+
bid_amount,
305+
ask_amount,
306+
authwit_nonce,
307+
),
308+
);
309+
}
310+
311+
#[test(should_fail_with = "SAME_TOKEN_TRADE")]
312+
unconstrained fn create_order_same_tokens() {
313+
let (mut env, orderbook_address, token0_address, _token1_address, owner, _) =
314+
setup_orderbook_with_tokens(false);
315+
316+
let bid_amount = 1000 as u128;
317+
let ask_amount = 2000 as u128;
318+
let authwit_nonce = 1;
319+
320+
// This should fail due to same bid and ask tokens
321+
let _order_id = env.call_private(
322+
owner,
323+
Orderbook::at(orderbook_address).create_order(
324+
token0_address,
325+
token0_address,
326+
bid_amount,
327+
ask_amount,
328+
authwit_nonce,
329+
),
330+
);
331+
}

yarn-project/end-to-end/src/e2e_orderbook.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ import { setup } from './fixtures/utils.js';
99

1010
const TIMEOUT = 120_000;
1111

12-
// TODO(#14525): Write thorough Orderbook tests. Currently we test only a happy path here because we will migrate these
13-
// tests to TXE once TXE 2.0 is ready. Didn't write it in TXE now as there is no way to obtain public events and all of
14-
// TXE will be rewritten soon.
12+
// Unhappy path tests are written only in Noir.
1513
describe('Orderbook', () => {
1614
jest.setTimeout(TIMEOUT);
1715

0 commit comments

Comments
 (0)