|
| 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 | +} |
0 commit comments