templates.fa2_lib_testing
1import smartpy as sp 2 3admin = sp.test_account("Administrator") 4admin2 = sp.test_account("Administrator2") 5alice = sp.test_account("Alice") 6bob = sp.test_account("Bob") 7charlie = sp.test_account("Charlie") 8 9 10def make_metadata(symbol, name, decimals): 11 """Helper function to build metadata JSON bytes values.""" 12 return sp.map( 13 l={ 14 "decimals": sp.scenario_utils.bytes_of_string("%d" % decimals), 15 "name": sp.scenario_utils.bytes_of_string(name), 16 "symbol": sp.scenario_utils.bytes_of_string(symbol), 17 } 18 ) 19 20 21tok0_md = make_metadata(name="Token Zero", decimals=1, symbol="Tok0") 22tok1_md = make_metadata(name="Token One", decimals=1, symbol="Tok1") 23tok2_md = make_metadata(name="Token Two", decimals=1, symbol="Tok2") 24 25 26@sp.module 27def helpers(): 28 t_balance_of_response: type = sp.record( 29 request=sp.record(owner=sp.address, token_id=sp.nat).layout( 30 ("owner", "token_id") 31 ), 32 balance=sp.nat, 33 ).layout(("request", "balance")) 34 35 class TestReceiverBalanceOf(sp.Contract): 36 """Helper used to test the `balance_of` entrypoint. 37 38 Don't use it on-chain as it can be tricked. 39 """ 40 41 def __init__(self): 42 self.data.last_received_balances = [] 43 44 @sp.entrypoint 45 def receive_balances(self, params): 46 sp.cast(params, sp.list[t_balance_of_response]) 47 self.data.last_received_balances = params 48 49 class Wallet(sp.Contract): 50 @sp.entrypoint 51 def default(self): 52 pass 53 54 55################################################################################ 56 57# Standard features tests 58 59 60def _get_balance(fa2, arg): 61 return sp.View(fa2, "get_balance")(arg) 62 63 64def _get_balance_of(fa2, arg): 65 return sp.View(fa2, "get_balance_of")(arg) 66 67 68def _total_supply(fa2, arg): 69 return sp.View(fa2, "total_supply")(arg) 70 71 72def test_core_interfaces(class_, kwargs, ledger_type, test_name="", modules=[]): 73 """Test that each core interface has the right type and layout. 74 75 Args: 76 test_name (string): Name of the test 77 fa2 (sp.Contract): The FA2 contract on which the tests occur. 78 79 The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md. 80 81 For NFT contracts, `alice` must own the three tokens. 82 83 For Fungible contracts, `alice` must own 42 of each token types. 84 85 Tests: 86 87 - Entrypoints: `balance_of`, `transfer`, `update_operators` 88 - Storage: test of `token_metadata` 89 """ 90 test_name = "test_core_interfaces_" + ledger_type + "_" + test_name 91 92 @sp.add_test() 93 def test(): 94 sc = sp.test_scenario(test_name, modules) 95 sc.add_module(helpers) 96 sc.h1(test_name) 97 sc.p("A call to all the standard entrypoints and off-chain views.") 98 99 sc.h2("Accounts") 100 sc.show([admin, alice, bob]) 101 102 sc.h2("FA2 contract") 103 fa2 = class_(**kwargs) 104 sc += fa2 105 106 # Entrypoints 107 108 sc.h2("Entrypoint: update_operators") 109 fa2.update_operators( 110 sp.set_type_expr( 111 [ 112 sp.variant( 113 "add_operator", 114 sp.record( 115 owner=alice.address, operator=alice.address, token_id=0 116 ), 117 ) 118 ], 119 sp.list[ 120 sp.variant( 121 add_operator=sp.record( 122 owner=sp.address, operator=sp.address, token_id=sp.nat 123 ).layout(("owner", ("operator", "token_id"))), 124 remove_operator=sp.record( 125 owner=sp.address, operator=sp.address, token_id=sp.nat 126 ).layout(("owner", ("operator", "token_id"))), 127 ) 128 ], 129 ), 130 _sender=alice, 131 ) 132 133 sc.h2("Entrypoint: transfer") 134 fa2.transfer( 135 sp.set_type_expr( 136 [ 137 sp.record( 138 from_=alice.address, 139 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 140 ) 141 ], 142 sp.list[ 143 sp.record( 144 from_=sp.address, 145 txs=sp.list[ 146 sp.record( 147 to_=sp.address, token_id=sp.nat, amount=sp.nat 148 ).layout(("to_", ("token_id", "amount"))) 149 ], 150 ).layout(("from_", "txs")) 151 ], 152 ), 153 _sender=alice, 154 ) 155 156 sc.h2("Entrypoint: balance_of") 157 sc.h3("Receiver contract") 158 c2 = helpers.TestReceiverBalanceOf() 159 sc += c2 160 161 sc.h3("Call to balance_of") 162 fa2.balance_of( 163 sp.set_type_expr( 164 sp.record( 165 callback=sp.contract( 166 sp.list[helpers.t_balance_of_response], 167 c2.address, 168 entrypoint="receive_balances", 169 ).unwrap_some(), 170 requests=[sp.record(owner=alice.address, token_id=0)], 171 ), 172 sp.record( 173 requests=sp.list[ 174 sp.record(owner=sp.address, token_id=sp.nat).layout( 175 ("owner", "token_id") 176 ) 177 ], 178 callback=sp.contract[ 179 sp.list[ 180 sp.record( 181 request=sp.record( 182 owner=sp.address, token_id=sp.nat 183 ).layout(("owner", "token_id")), 184 balance=sp.nat, 185 ).layout(("request", "balance")) 186 ] 187 ], 188 ).layout(("requests", "callback")), 189 ), 190 _sender=alice, 191 ) 192 193 # Storage 194 195 sc.h2("Storage: token_metadata") 196 sc.verify_equal( 197 fa2.data.token_metadata[0], sp.record(token_id=0, token_info=tok0_md) 198 ) 199 200 201def test_transfer(class_, kwargs, ledger_type, test_name="", modules=[]): 202 """Test that transfer entrypoint works as expected. 203 204 Do not test transfer permission policies. 205 206 Args: 207 test_name (string): Name of the test 208 fa2 (sp.Contract): The FA2 contract on which the tests occur. 209 210 SingleAsset contract: The contract must contains the token 0: tok0_md. 211 Others: The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md. 212 213 NFT contract: `sp.test_account("Alice").address` must own all the tokens. 214 Fungible contract: `sp.test_account("Alice").address` must own 42 of each token. 215 216 Tests: 217 - initial minting works as expected. 218 - `get_balance` returns `balance = 0` for non owned tokens. 219 - transfer of 0 tokens works when not owning tokens. 220 - transfer of 0 doesn't change `ledger` storage. 221 - transfer of 0 tokens works when owning tokens. 222 - transfers with multiple operations and transactions works as expected. 223 - fails with `FA2_INSUFFICIENT_BALANCE` when not enough balance. 224 - transfer to self doesn't change anything. 225 - transfer to self with more than balance gives `FA2_INSUFFICIENT_BALANCE`. 226 - transfer to self of undefined token gives `FA2_TOKEN_UNDEFINED`. 227 - transfer to someone else of undefined token gives `FA2_TOKEN_UNDEFINED`. 228 """ 229 test_name = "test_transfer_" + ledger_type + "_" + test_name 230 231 @sp.add_test() 232 def test(): 233 sc = sp.test_scenario(test_name, modules) 234 sc.h1(test_name) 235 236 sc.h2("Accounts") 237 sc.show([admin, alice, bob]) 238 239 sc.h2("Contract") 240 fa2 = class_(**kwargs) 241 sc += fa2 242 243 if ledger_type == "NFT": 244 ICO = 1 # Initial coin offering. 245 TX = 1 # How much we transfer at a time during the tests. 246 else: 247 ICO = 42 # Initial coin offering. 248 TX = 12 # How much we transfer at a time during the tests. 249 250 # Check that the contract storage is correctly initialized. 251 sc.verify(_get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == ICO) 252 if not ledger_type == "SingleAsset": 253 sc.verify( 254 _get_balance(fa2, sp.record(owner=alice.address, token_id=1)) == ICO 255 ) 256 sc.verify( 257 _get_balance(fa2, sp.record(owner=alice.address, token_id=2)) == ICO 258 ) 259 260 # Check that the balance is interpreted as zero when the owner doesn't hold any. 261 # TZIP-12: If the token owner does not hold any tokens of type token_id, 262 # the owner's balance is interpreted as zero. 263 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 264 265 sc.h2("Zero amount transfer") 266 sc.p("TZIP-12: Transfers of zero amount MUST be treated as normal transfers.") 267 268 # Check that someone with 0 token can transfer 0 token. 269 fa2.transfer( 270 [ 271 sp.record( 272 from_=bob.address, 273 txs=[sp.record(to_=alice.address, amount=0, token_id=0)], 274 ), 275 ], 276 _sender=bob, 277 ) 278 279 # Check that the contract storage is unchanged. 280 sc.verify(_get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == ICO) 281 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 282 283 # Check that someone with some tokens can transfer 0 token. 284 fa2.transfer( 285 [ 286 sp.record( 287 from_=alice.address, 288 txs=[sp.record(to_=bob.address, amount=0, token_id=0)], 289 ), 290 ], 291 _sender=alice, 292 ) 293 294 # Check that the contract storage is unchanged. 295 sc.verify(_get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == ICO) 296 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 297 298 sc.h2("Transfers Alice -> Bob") 299 sc.p( 300 """TZIP-12: Each transfer in the batch MUST decrement token balance 301 of the source (from_) address by the amount of the transfer and 302 increment token balance of the destination (to_) address by the 303 amount of the transfer.""" 304 ) 305 306 # Perform a complex transfer with 2 operations, one of which contains 2 transactions. 307 if ledger_type == "SingleAsset": 308 fa2.transfer( 309 [ 310 sp.record( 311 from_=alice.address, 312 txs=[ 313 sp.record(to_=bob.address, amount=TX // 3, token_id=0), 314 sp.record(to_=bob.address, amount=TX // 3, token_id=0), 315 ], 316 ), 317 sp.record( 318 from_=alice.address, 319 txs=[sp.record(to_=bob.address, amount=TX // 3, token_id=0)], 320 ), 321 ], 322 _sender=alice, 323 ) 324 else: 325 fa2.transfer( 326 [ 327 sp.record( 328 from_=alice.address, 329 txs=[ 330 sp.record(to_=bob.address, amount=TX, token_id=0), 331 sp.record(to_=bob.address, amount=TX, token_id=1), 332 ], 333 ), 334 sp.record( 335 from_=alice.address, 336 txs=[sp.record(to_=bob.address, amount=TX, token_id=2)], 337 ), 338 ], 339 _sender=alice, 340 ) 341 342 # Check that the contract storage is correctly updated. 343 sc.verify( 344 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == ICO - TX 345 ) 346 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == TX) 347 348 if not ledger_type == "SingleAsset": 349 sc.verify( 350 _get_balance(fa2, sp.record(owner=alice.address, token_id=1)) 351 == ICO - TX 352 ) 353 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=1)) == TX) 354 355 # Check without using get_balance because the ledger interface 356 # differs between NFT and fungible. 357 if ledger_type == "NFT": 358 sc.verify(fa2.data.ledger[0] == bob.address) 359 else: 360 if ledger_type == "Fungible": 361 sc.verify(fa2.data.ledger[(alice.address, 0)] == ICO - TX) 362 sc.verify(fa2.data.ledger[(bob.address, 0)] == TX) 363 else: 364 sc.verify(fa2.data.ledger[alice.address] == ICO - TX) 365 sc.verify(fa2.data.ledger[bob.address] == TX) 366 367 # Error tests 368 369 # test of FA2_INSUFFICIENT_BALANCE. 370 sc.h2("Insufficient balance") 371 sc.p( 372 """TIP-12: If the transfer amount exceeds current token balance of 373 the source address, the whole transfer operation MUST fail with 374 the error mnemonic "FA2_INSUFFICIENT_BALANCE".""" 375 ) 376 377 # Compute bob_balance to transfer 1 more token. 378 bob_balance = sc.compute( 379 _get_balance(fa2, sp.record(owner=bob.address, token_id=0)) 380 ) 381 382 # Test that a complex transfer with only one insufficient 383 # balance fails. 384 fa2.transfer( 385 [ 386 sp.record( 387 from_=bob.address, 388 txs=[ 389 sp.record( 390 to_=alice.address, amount=bob_balance + 1, token_id=0 391 ), 392 sp.record(to_=alice.address, amount=0, token_id=0), 393 ], 394 ), 395 sp.record( 396 from_=bob.address, 397 txs=[sp.record(to_=alice.address, amount=0, token_id=0)], 398 ), 399 ], 400 _sender=bob, 401 _valid=False, 402 _exception="FA2_INSUFFICIENT_BALANCE", 403 ) 404 405 sc.h2("Same address transfer") 406 sc.p( 407 """TZIP-12: Transfers with the same address (from_ equals to_) MUST 408 be treated as normal transfers.""" 409 ) 410 411 # Test that someone can transfer all his balance to itself 412 # without problem. 413 fa2.transfer( 414 [ 415 sp.record( 416 from_=bob.address, 417 txs=[sp.record(to_=bob.address, amount=bob_balance, token_id=0)], 418 ), 419 ], 420 _sender=bob, 421 ) 422 423 # Check that the contract storage is unchanged. 424 sc.verify( 425 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == ICO - TX 426 ) 427 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == TX) 428 if not ledger_type == "SingleAsset": 429 sc.verify( 430 _get_balance(fa2, sp.record(owner=alice.address, token_id=1)) 431 == ICO - TX 432 ) 433 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=1)) == TX) 434 435 # Test that someone cannot transfer more tokens than he holds 436 # even to himself. 437 fa2.transfer( 438 [ 439 sp.record( 440 from_=bob.address, 441 txs=[ 442 sp.record(to_=bob.address, amount=bob_balance + 1, token_id=0) 443 ], 444 ), 445 ], 446 _sender=bob, 447 _valid=False, 448 _exception="FA2_INSUFFICIENT_BALANCE", 449 ) 450 451 # test of FA2_TOKEN_UNDEFINED. 452 sc.h2("Not defined token") 453 sc.p( 454 """TZIP-12: If one of the specified token_ids is not defined within 455 the FA2 contract, the entrypoint MUST fail with the error 456 mnemonic "FA2_TOKEN_UNDEFINED".""" 457 ) 458 459 # A transfer of 0 tokens to self gives FA2_TOKEN_UNDEFINED if 460 # not defined. 461 fa2.transfer( 462 [ 463 sp.record( 464 from_=bob.address, 465 txs=[sp.record(to_=bob.address, amount=0, token_id=4)], 466 ), 467 ], 468 _sender=bob, 469 _valid=False, 470 _exception="FA2_TOKEN_UNDEFINED", 471 ) 472 473 # A transfer of 1 token to someone else gives 474 # FA2_TOKEN_UNDEFINED if not defined. 475 fa2.transfer( 476 [ 477 sp.record( 478 from_=alice.address, 479 txs=[sp.record(to_=bob.address, amount=1, token_id=4)], 480 ), 481 ], 482 _sender=bob, 483 _valid=False, 484 _exception="FA2_TOKEN_UNDEFINED", 485 ) 486 487 488def test_balance_of(class_, kwargs, ledger_type, test_name="", modules=[]): 489 """Test that balance_of entrypoint works as expected. 490 491 Args: 492 test_name (string): Name of the test 493 fa2 (sp.Contract): The FA2 contract on which the tests occur. 494 495 SingleAsset contract: The contract must contains the token 0: tok0_md. 496 Others: The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md. 497 498 NFT contract: `sp.test_account("Alice").address` must own all the tokens. 499 Fungible contract: `sp.test_account("Alice").address` must own 42 of each token. 500 501 Tests: 502 503 - `balance_of` calls back with valid results. 504 - `balance_of` fails with `FA2_TOKEN_UNDEFINED` when token is undefined. 505 """ 506 test_name = "test_balance_of_" + ledger_type + "_" + test_name 507 508 @sp.add_test() 509 def test(): 510 sc = sp.test_scenario(test_name, modules) 511 sc.add_module(helpers) 512 sc.h1(test_name) 513 514 sc.h2("Accounts") 515 sc.show([admin, alice, bob]) 516 517 # We initialize the contract with an initial mint. 518 sc.h2("Contract") 519 fa2 = class_(**kwargs) 520 sc += fa2 521 522 sc.h3("Receiver contract") 523 c2 = helpers.TestReceiverBalanceOf() 524 sc += c2 525 526 ICO = 1 if ledger_type == "NFT" else 42 # Initial coin offering. 527 last_token_id = 0 if ledger_type == "SingleAsset" else 2 528 529 requests = [ 530 sp.record(owner=alice.address, token_id=0), 531 sp.record(owner=alice.address, token_id=0), 532 sp.record(owner=bob.address, token_id=0), 533 sp.record(owner=alice.address, token_id=last_token_id), 534 ] 535 expected = [ 536 sp.record(balance=ICO, request=sp.record(owner=alice.address, token_id=0)), 537 sp.record(balance=ICO, request=sp.record(owner=alice.address, token_id=0)), 538 sp.record(balance=0, request=sp.record(owner=bob.address, token_id=0)), 539 sp.record( 540 balance=ICO, 541 request=sp.record(owner=alice.address, token_id=last_token_id), 542 ), 543 ] 544 545 # Call to balance_of. 546 fa2.balance_of( 547 callback=sp.contract( 548 sp.list[helpers.t_balance_of_response], 549 c2.address, 550 entrypoint="receive_balances", 551 ).unwrap_some(), 552 requests=requests, 553 _sender=alice, 554 ) 555 556 # Check that balance_of returns the correct balances. 557 # This test non-deduplication, non-reordering, on multiple tokens. 558 sc.verify_equal(c2.data.last_received_balances, expected) 559 560 # Expected errors 561 sc.h2("FA2_TOKEN_UNDEFINED error") 562 fa2.balance_of( 563 callback=sp.contract( 564 sp.list[helpers.t_balance_of_response], 565 c2.address, 566 entrypoint="receive_balances", 567 ).unwrap_some(), 568 requests=[ 569 sp.record(owner=alice.address, token_id=0), 570 sp.record(owner=alice.address, token_id=5), 571 ], 572 _sender=alice, 573 _valid=False, 574 _exception="FA2_TOKEN_UNDEFINED", 575 ) 576 577 578def test_no_transfer(class_, kwargs, ledger_type, test_name="", modules=[]): 579 """Test that the `no-transfer` policy works as expected. 580 581 Args: 582 test_name (string): Name of the test 583 fa2 (sp.Contract): The FA2 contract on which the tests occur. 584 585 The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md. 586 587 For NFT contracts, `alice` must own the three tokens. 588 589 For Fungible contracts, `alice` must own 42 of each token types. 590 591 Tests: 592 593 - transfer fails with FA2_TX_DENIED. 594 - transfer fails with FA2_TX_DENIED even for admin. 595 - update_operators fails with FA2_OPERATORS_UNSUPPORTED. 596 """ 597 test_name = "test_no-transfer_" + ledger_type + "_" + test_name 598 599 @sp.add_test() 600 def test(): 601 sc = sp.test_scenario(test_name, modules) 602 sc.h1(test_name) 603 604 sc.h2("Accounts") 605 sc.show([admin, alice, bob]) 606 607 sc.h2("FA2 with NoTransfer policy") 608 sc.p("No transfer are allowed.") 609 fa2 = class_(**kwargs) 610 sc += fa2 611 612 # Transfer fails as expected. 613 sc.h2("Alice cannot transfer: FA2_TX_DENIED") 614 fa2.transfer( 615 [ 616 sp.record( 617 from_=alice.address, 618 txs=[sp.record(to_=bob.address, amount=1, token_id=0)], 619 ) 620 ], 621 _sender=alice, 622 _valid=False, 623 _exception="FA2_TX_DENIED", 624 ) 625 626 # Even Admin cannot transfer. 627 sc.h2("Admin cannot transfer alice's token: FA2_TX_DENIED") 628 fa2.transfer( 629 [ 630 sp.record( 631 from_=alice.address, 632 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 633 ) 634 ], 635 _sender=admin, 636 _valid=False, 637 _exception="FA2_TX_DENIED", 638 ) 639 640 # update_operators is unsupported. 641 sc.h2("Alice cannot add operator: FA2_OPERATORS_UNSUPPORTED") 642 fa2.update_operators( 643 [ 644 sp.variant( 645 "add_operator", 646 sp.record(owner=alice.address, operator=bob.address, token_id=0), 647 ) 648 ], 649 _sender=alice, 650 _valid=False, 651 _exception="FA2_OPERATORS_UNSUPPORTED", 652 ) 653 654 655def test_owner_transfer(class_, kwargs, ledger_type, test_name="", modules=[]): 656 """Test that the `owner-transfer` policy works as expected. 657 658 Args: 659 test_name (string): Name of the test 660 fa2 (sp.Contract): the FA2 contract on which the tests occur. 661 662 The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md. 663 664 For NFT contracts, `alice` must own the three tokens. 665 666 For Fungible contracts, `alice` must own 42 of each token types. 667 668 Tests: 669 670 - owner can transfer. 671 - transfer fails with FA2_NOT_OWNER for non owner, even admin. 672 - update_operators fails with FA2_OPERATORS_UNSUPPORTED. 673 """ 674 test_name = "test_owner-transfer_" + ledger_type + "_" + test_name 675 676 @sp.add_test() 677 def test(): 678 sc = sp.test_scenario(test_name, modules) 679 sc.h1(test_name) 680 681 sc.h2("Accounts") 682 sc.show([admin, alice, bob]) 683 684 sc.h2("FA2 with OwnerTransfer policy") 685 sc.p("Only owner can transfer, no operator allowed.") 686 fa2 = class_(**kwargs) 687 sc += fa2 688 689 # The owner can transfer its tokens. 690 sc.h2("Alice can transfer") 691 fa2.transfer( 692 [ 693 sp.record( 694 from_=alice.address, 695 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 696 ) 697 ], 698 _sender=alice, 699 ) 700 701 # Admin cannot transfer someone else tokens. 702 sc.h2("Admin cannot transfer alice's token: FA2_NOT_OWNER") 703 fa2.transfer( 704 [ 705 sp.record( 706 from_=alice.address, 707 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 708 ) 709 ], 710 _sender=admin, 711 _valid=False, 712 _exception="FA2_NOT_OWNER", 713 ) 714 715 # Someone cannot transfer someone else tokens. 716 sc.h2("Admin cannot transfer alice's token: FA2_NOT_OWNER") 717 fa2.transfer( 718 [ 719 sp.record( 720 from_=alice.address, 721 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 722 ) 723 ], 724 _sender=bob, 725 _valid=False, 726 _exception="FA2_NOT_OWNER", 727 ) 728 729 # Someone cannot add operator. 730 sc.h2("Alice cannot add operator: FA2_OPERATORS_UNSUPPORTED") 731 fa2.update_operators( 732 [ 733 sp.variant( 734 "add_operator", 735 sp.record(owner=alice.address, operator=bob.address, token_id=0), 736 ) 737 ], 738 _sender=alice, 739 _valid=False, 740 _exception="FA2_OPERATORS_UNSUPPORTED", 741 ) 742 743 744def test_owner_or_operator_transfer( 745 class_, kwargs, ledger_type, test_name="", modules=[] 746): 747 """Test that the `owner-or-operator-transfer` policy works as expected. 748 749 Args: 750 test_name (string): Name of the test 751 fa2 (sp.Contract): The FA2 contract on which the tests occur. 752 753 SingleAsset contract: The contract must contains the token 0: tok0_md. 754 Others: The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md. 755 756 NFT contract: `sp.test_account("Alice").address` must own all the tokens. 757 Fungible contract: `sp.test_account("Alice").address` must own 42 of each token. 758 759 Tests: 760 761 - owner can transfer. 762 - transfer fails with FA2_NOT_OPERATOR for non operator, even admin. 763 - update_operators fails with FA2_OPERATORS_UNSUPPORTED. 764 - owner can add operator. 765 - operator can transfer. 766 - operator cannot transfer for non allowed `token_id`. 767 - owner can remove operator and add operator in a batch. 768 - removed operator cannot transfer anymore. 769 - operator added in a batch can transfer. 770 - add then remove the same operator doesn't change the storage. 771 - remove then add the same operator does change the storage. 772 """ 773 test_name = "test_owner-or-operator-transfer_" + ledger_type + "_" + test_name 774 775 @sp.add_test() 776 def test(): 777 operator_bob = sp.record(owner=alice.address, operator=bob.address, token_id=0) 778 operator_charlie = sp.record( 779 owner=alice.address, operator=charlie.address, token_id=0 780 ) 781 782 sc = sp.test_scenario(test_name, modules) 783 sc.h1(test_name) 784 785 sc.h2("Accounts") 786 sc.show([admin, alice, bob]) 787 788 sc.h2("FA2 with OwnerOrOperatorTransfer policy") 789 sc.p("Owner or operators can transfer.") 790 fa2 = class_(**kwargs) 791 sc += fa2 792 793 # Owner can transfer his tokens. 794 sc.h2("Alice can transfer") 795 fa2.transfer( 796 [ 797 sp.record( 798 from_=alice.address, 799 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 800 ) 801 ], 802 _sender=alice, 803 ) 804 805 # Admin can transfer others tokens. 806 sc.h2("Admin cannot transfer alice's token: FA2_NOT_OPERATOR") 807 fa2.transfer( 808 [ 809 sp.record( 810 from_=alice.address, 811 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 812 ) 813 ], 814 _sender=admin, 815 _valid=False, 816 _exception="FA2_NOT_OPERATOR", 817 ) 818 819 # Update operator works. 820 sc.h2("Alice adds Bob as operator") 821 fa2.update_operators([sp.variant.add_operator(operator_bob)], _sender=alice) 822 823 # The contract is updated as expected. 824 sc.verify(fa2.data.operators.contains(operator_bob)) 825 826 # Operator can transfer allowed tokens on behalf of owner. 827 sc.h2("Bob can transfer Alice's token id 0") 828 fa2.transfer( 829 [ 830 sp.record( 831 from_=alice.address, 832 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 833 ) 834 ], 835 _sender=bob, 836 ) 837 838 if not ledger_type == "SingleAsset": 839 # Operator cannot transfer not allowed tokens on behalf of owner. 840 sc.h2("Bob cannot transfer Alice's token id 1") 841 fa2.transfer( 842 [ 843 sp.record( 844 from_=alice.address, 845 txs=[sp.record(to_=alice.address, amount=1, token_id=1)], 846 ) 847 ], 848 _sender=bob, 849 _valid=False, 850 _exception="FA2_NOT_OPERATOR", 851 ) 852 853 # Batch of update_operators actions. 854 sc.h2("Alice can remove Bob as operator and add Charlie") 855 fa2.update_operators( 856 [ 857 sp.variant.remove_operator(operator_bob), 858 sp.variant.add_operator(operator_charlie), 859 ], 860 _sender=alice, 861 ) 862 863 # The contract is updated as expected. 864 sc.verify(~fa2.data.operators.contains(operator_bob)) 865 sc.verify(fa2.data.operators.contains(operator_charlie)) 866 867 # A removed operator lose its rights. 868 sc.h2("Bob cannot transfer Alice's token 0 anymore") 869 fa2.transfer( 870 [ 871 sp.record( 872 from_=alice.address, 873 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 874 ) 875 ], 876 _sender=bob, 877 _valid=False, 878 _exception="FA2_NOT_OPERATOR", 879 ) 880 881 # The new added operator can now do the transfer. 882 sc.h2("Charlie can transfer Alice's token") 883 fa2.transfer( 884 [ 885 sp.record( 886 from_=alice.address, 887 txs=[sp.record(to_=charlie.address, amount=1, token_id=0)], 888 ) 889 ], 890 _sender=charlie, 891 ) 892 893 # The contract is updated as expected. 894 sc.verify(_get_balance(fa2, sp.record(owner=charlie.address, token_id=0)) == 1) 895 896 # Remove after a Add does nothing. 897 sc.h2("Add then Remove in the same batch is transparent") 898 sc.p( 899 """TZIP-12: If two different commands in the list add and remove an 900 operator for the same token owner and token ID, the last command 901 in the list MUST take effect.""" 902 ) 903 fa2.update_operators( 904 [ 905 sp.variant.add_operator(operator_bob), 906 sp.variant.remove_operator(operator_bob), 907 ], 908 _sender=alice, 909 ) 910 sc.verify(~fa2.data.operators.contains(operator_bob)) 911 912 # Add after remove works 913 sc.h2("Remove then Add do add the operator") 914 fa2.update_operators( 915 [ 916 sp.variant.remove_operator(operator_bob), 917 sp.variant.add_operator(operator_bob), 918 ], 919 _sender=alice, 920 ) 921 sc.verify(fa2.data.operators.contains(operator_bob)) 922 923 924################################################################################ 925 926# Optional features tests 927 928 929class NS: 930 """Non standard features of FA2_lib 931 932 Mixin tested: 933 934 - Admin, 935 - WithdrawMutez 936 - ChangeMetadata 937 - OffchainviewTokenMetadata 938 - OnchainviewBalanceOf 939 - Mint* 940 - Burn* 941 """ 942 943 def test_admin(class_, kwargs, ledger_type, test_name="", modules=[]): 944 """Test `Admin` mixin 945 946 - non admin cannot set admin 947 - admin can set admin 948 - new admin can set admin 949 """ 950 test_name = "FA2_optional_interfaces_admin" + ledger_type + test_name 951 952 @sp.add_test() 953 def test(): 954 sc = sp.test_scenario(test_name, modules) 955 fa2 = class_(**kwargs) 956 sc += fa2 957 sc.h1(test_name) 958 sc.h2("Non admin cannot set admin") 959 fa2.set_administrator( 960 alice.address, _sender=alice, _valid=False, _exception="FA2_NOT_ADMIN" 961 ) 962 sc.verify(fa2.data.administrator == admin.address) 963 fa2.set_administrator(admin2.address, _sender=admin) 964 sc.verify(~(fa2.data.administrator == admin.address)) 965 sc.verify(fa2.data.administrator == admin2.address) 966 fa2.set_administrator(admin.address, _sender=admin2) 967 968 def test_mint(class_, kwargs, ledger_type, test_name="", modules=[]): 969 """Test `Mint*` mixin. 970 971 - `mint` fails with `FA2_NOT_ADMIN` for non-admin. 972 - `mint` adds the tokens. 973 - `mint` update the supply. 974 - `mint` works for existing tokens in fungible contracts. 975 """ 976 test_name = "FA2_mint_" + ledger_type + test_name 977 978 def mint_nft(sc, fa2): 979 sc.h2("Mint entrypoint") 980 # Non admin cannot mint a new NFT token. 981 sc.h3("NFT mint failure") 982 fa2.mint( 983 [sp.record(metadata=tok0_md, to_=alice.address)], 984 _sender=alice, 985 _valid=False, 986 _exception="FA2_NOT_ADMIN", 987 ) 988 989 sc.h3("Mint") 990 # Mint of a new NFT token. 991 fa2.mint( 992 [ 993 sp.record(metadata=tok0_md, to_=alice.address), 994 sp.record(metadata=tok1_md, to_=alice.address), 995 sp.record(metadata=tok2_md, to_=bob.address), 996 ], 997 _sender=admin, 998 ) 999 1000 # Check that the balance is updated. 1001 sc.verify( 1002 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 1 1003 ) 1004 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 1005 sc.verify( 1006 _get_balance(fa2, sp.record(owner=alice.address, token_id=1)) == 1 1007 ) 1008 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=1)) == 0) 1009 sc.verify( 1010 _get_balance(fa2, sp.record(owner=alice.address, token_id=2)) == 0 1011 ) 1012 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=2)) == 1) 1013 1014 # Check that the supply is updated. 1015 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 1) 1016 sc.verify(_total_supply(fa2, sp.record(token_id=1)) == 1) 1017 sc.verify(_total_supply(fa2, sp.record(token_id=2)) == 1) 1018 1019 def mint_fungible(sc, fa2): 1020 sc.h2("Mint entrypoint") 1021 # Non admin cannot mint a new fungible token. 1022 sc.h3("Fungible mint failure") 1023 fa2.mint( 1024 [ 1025 sp.record( 1026 token=sp.variant.new(tok0_md), to_=alice.address, amount=1000 1027 ) 1028 ], 1029 _sender=alice, 1030 _valid=False, 1031 _exception="FA2_NOT_ADMIN", 1032 ) 1033 1034 sc.h3("Mint") 1035 # Mint of a new fungible token. 1036 fa2.mint( 1037 [ 1038 sp.record( 1039 token=sp.variant.new(tok0_md), to_=alice.address, amount=1000 1040 ) 1041 ], 1042 _sender=admin, 1043 ) 1044 1045 # Check ledger update. 1046 sc.verify( 1047 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 1000 1048 ) 1049 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 1050 # Check supply update. 1051 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 1000) 1052 1053 # Mint a new and existing token. 1054 fa2.mint( 1055 [ 1056 sp.record( 1057 token=sp.variant.new(tok1_md), to_=alice.address, amount=1000 1058 ), 1059 sp.record( 1060 token=sp.variant.existing(0), to_=alice.address, amount=1000 1061 ), 1062 sp.record( 1063 token=sp.variant.existing(1), to_=bob.address, amount=1000 1064 ), 1065 ], 1066 _sender=admin, 1067 ) 1068 1069 # Check ledger update. 1070 sc.verify( 1071 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 2000 1072 ) 1073 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 1074 sc.verify( 1075 _get_balance(fa2, sp.record(owner=alice.address, token_id=1)) == 1000 1076 ) 1077 sc.verify( 1078 _get_balance(fa2, sp.record(owner=bob.address, token_id=1)) == 1000 1079 ) 1080 # Check supply update. 1081 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 2000) 1082 sc.verify(_total_supply(fa2, sp.record(token_id=1)) == 2000) 1083 1084 def mint_single_asset(sc, fa2): 1085 sc.h2("Mint entrypoint") 1086 # Non admin cannot mint a new fungible token. 1087 sc.h3("Single asset mint failure") 1088 fa2.mint( 1089 [sp.record(to_=alice.address, amount=1000)], 1090 _sender=alice, 1091 _valid=False, 1092 _exception="FA2_NOT_ADMIN", 1093 ) 1094 1095 sc.h3("Mint") 1096 # Mint some tokens 1097 fa2.mint([sp.record(to_=alice.address, amount=1000)], _sender=admin) 1098 1099 # Check ledger update. 1100 sc.verify( 1101 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 1000 1102 ) 1103 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 1104 # Check supply update. 1105 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 1000) 1106 1107 # multiple mint 1108 fa2.mint( 1109 [ 1110 sp.record(to_=alice.address, amount=1000), 1111 sp.record(to_=bob.address, amount=1000), 1112 sp.record(to_=bob.address, amount=1000), 1113 ], 1114 _sender=admin, 1115 ) 1116 1117 # Check ledger update. 1118 sc.verify( 1119 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 2000 1120 ) 1121 sc.verify( 1122 _get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 2000 1123 ) 1124 # Check supply update. 1125 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 4000) 1126 1127 @sp.add_test() 1128 def test(): 1129 sc = sp.test_scenario(test_name, modules) 1130 fa2 = class_(**kwargs) 1131 sc += fa2 1132 sc.h1(test_name) 1133 if ledger_type == "NFT": 1134 mint_nft(sc, fa2) 1135 elif ledger_type == "Fungible": 1136 mint_fungible(sc, fa2) 1137 elif ledger_type == "SingleAsset": 1138 mint_single_asset(sc, fa2) 1139 else: 1140 raise Exception( 1141 'fa2.ledger type must be "NFT", "Fungible" or "SingleAsset".' 1142 ) 1143 1144 def test_burn( 1145 class_, 1146 kwargs, 1147 ledger_type, 1148 supports_transfer, 1149 supports_operator, 1150 modules=[], 1151 test_name="", 1152 ): 1153 """Test `Burn*` mixin. 1154 1155 - non operator cannot burn, it fails appropriately. 1156 - owner can burn. 1157 - burn fails with `FA2_INSUFFICIENT_BALANCE` when needed. 1158 - operator can burn if the policy allows it. 1159 """ 1160 test_name = "FA2_burn_" + ledger_type + test_name 1161 1162 @sp.add_test() 1163 def test(): 1164 amount = 1 if ledger_type == "NFT" else 20 1165 sc = sp.test_scenario(test_name, modules) 1166 fa2 = class_(**kwargs) 1167 sc += fa2 1168 sc.h1(test_name) 1169 sc.h2("Burn entrypoint") 1170 1171 # Check that non operator cannot burn others tokens. 1172 sc.h3("Cannot burn others tokens") 1173 exception = "FA2_NOT_OPERATOR" if supports_transfer else "FA2_TX_DENIED" 1174 fa2.burn( 1175 [sp.record(token_id=0, from_=alice.address, amount=1)], 1176 _sender=bob, 1177 _valid=False, 1178 _exception=exception, 1179 ) 1180 1181 # Not allowed transfers 1182 if not supports_transfer: 1183 fa2.burn( 1184 [sp.record(token_id=0, from_=alice.address, amount=amount)], 1185 _sender=alice, 1186 _valid=False, 1187 _exception="FA2_TX_DENIED", 1188 ) 1189 return 1190 1191 # Owner can burn. 1192 sc.h3("Owner burns his nft tokens") 1193 fa2.burn( 1194 [sp.record(token_id=0, from_=alice.address, amount=amount)], 1195 _sender=alice, 1196 ) 1197 1198 if ledger_type == "NFT": 1199 # Check that the contract storage is updated. 1200 sc.verify(~fa2.data.ledger.contains(0)) 1201 # Check that burning an nft removes token_metadata. 1202 sc.verify(~fa2.data.token_metadata.contains(0)) 1203 else: 1204 # Check ledger update. 1205 sc.verify( 1206 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 22 1207 ) 1208 # Check that burning doesn't remove token_metadata. 1209 sc.verify(fa2.data.token_metadata.contains(0)) 1210 # Check supply update. 1211 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 22) 1212 1213 # Check burn of FA2_INSUFFICIENT_BALANCE. 1214 sc.h3("Burn with insufficient balance") 1215 token_id = 0 if ledger_type == "SingleAsset" else 1 1216 fa2.burn( 1217 [sp.record(token_id=token_id, from_=alice.address, amount=43)], 1218 _sender=alice, 1219 _valid=False, 1220 _exception="FA2_INSUFFICIENT_BALANCE", 1221 ) 1222 1223 if supports_operator: 1224 # Add operator to test if he can burn on behalf of the owner. 1225 sc.h3("Operator can burn on behalf of the owner") 1226 operator_bob = sp.record( 1227 owner=alice.address, operator=bob.address, token_id=token_id 1228 ) 1229 fa2.update_operators( 1230 [sp.variant.add_operator(operator_bob)], _sender=alice 1231 ) 1232 # Operator can burn nft on behalf of the owner. 1233 fa2.burn( 1234 [sp.record(token_id=token_id, from_=alice.address, amount=0)], 1235 _sender=bob, 1236 ) 1237 1238 def test_withdraw_mutez(class_, kwargs, ledger_type, test_name="", modules=[]): 1239 """Test of WithdrawMutez. 1240 1241 - non admin cannot withdraw mutez: FA2_NOT_ADMIN. 1242 - admin can withdraw mutez. 1243 """ 1244 test_name = "FA2_withdraw_mutez" + ledger_type + test_name 1245 1246 @sp.add_test() 1247 def test(): 1248 sc = sp.test_scenario(test_name, modules) 1249 sc.add_module(helpers) 1250 fa2 = class_(**kwargs) 1251 sc += fa2 1252 sc.h1(test_name) 1253 sc.h2("Mutez receiver contract") 1254 1255 wallet = helpers.Wallet() 1256 sc += wallet 1257 1258 # Non admin cannot withdraw mutez. 1259 sc.h2("Non admin cannot withdraw_mutez") 1260 fa2.withdraw_mutez( 1261 destination=wallet.address, 1262 amount=sp.tez(10), 1263 _sender=alice, 1264 _amount=sp.tez(42), 1265 _valid=False, 1266 _exception="FA2_NOT_ADMIN", 1267 ) 1268 1269 # Admin can withdraw mutez. 1270 sc.h3("Admin withdraw_mutez") 1271 fa2.withdraw_mutez( 1272 destination=wallet.address, 1273 amount=sp.tez(10), 1274 _sender=admin, 1275 _amount=sp.tez(42), 1276 ) 1277 1278 # Check that the mutez has been transferred. 1279 sc.verify(fa2.balance == sp.tez(32)) 1280 sc.verify(wallet.balance == sp.tez(10)) 1281 1282 def test_change_metadata(class_, kwargs, ledger_type, test_name="", modules=[]): 1283 """Test of ChangeMetadata. 1284 1285 - non admin cannot set metadata 1286 - `set_metadata` works as expected 1287 """ 1288 test_name = "FA2_change_metadata" + ledger_type + test_name 1289 1290 @sp.add_test() 1291 def test(): 1292 sc = sp.test_scenario(test_name, modules) 1293 fa2 = class_(**kwargs) 1294 sc += fa2 1295 sc.h1(test_name) 1296 sc.h2("Change metadata") 1297 sc.h3("Non admin cannot set metadata") 1298 fa2.set_metadata( 1299 sp.scenario_utils.metadata_of_url("http://example.com"), 1300 _sender=alice, 1301 _valid=False, 1302 _exception="FA2_NOT_ADMIN", 1303 ) 1304 1305 sc.h3("Admin set metadata") 1306 fa2.set_metadata( 1307 sp.scenario_utils.metadata_of_url("http://example.com"), _sender=admin 1308 ) 1309 1310 # Check that the metadata has been updated. 1311 sc.verify_equal( 1312 fa2.data.metadata[""], 1313 sp.scenario_utils.metadata_of_url("http://example.com")[""], 1314 ) 1315 1316 def test_get_balance_of(class_, kwargs, ledger_type, test_name="", modules=[]): 1317 """Test of `OnchainviewBalanceOf` 1318 1319 - `get_balance_of` doesn't deduplicate nor reorder on nft. 1320 - `get_balance_of` doesn't deduplicate nor reorder on fungible. 1321 - `get_balance_of` fails with `FA2_TOKEN_UNDEFINED` when needed on nft. 1322 - `get_balance_of` fails with `FA2_TOKEN_UNDEFINED` when needed on fungible. 1323 """ 1324 test_name = "FA2_get_balance_of" + ledger_type + test_name 1325 1326 @sp.add_test() 1327 def test(): 1328 sc = sp.test_scenario(test_name, modules) 1329 fa2 = class_(**kwargs) 1330 sc += fa2 1331 sc.h1(test_name) 1332 1333 # get_balance_of on fungible 1334 # We deliberately give multiple identical params to check for 1335 # non-deduplication and non-reordering. 1336 1337 ICO = 1 if ledger_type == "NFT" else 42 # Initial coin offering. 1338 last_token_id = 0 if ledger_type == "SingleAsset" else 2 1339 1340 requests = [ 1341 sp.record(owner=alice.address, token_id=0), 1342 sp.record(owner=alice.address, token_id=0), 1343 sp.record(owner=bob.address, token_id=0), 1344 sp.record(owner=alice.address, token_id=last_token_id), 1345 ] 1346 expected = [ 1347 sp.record( 1348 balance=ICO, request=sp.record(owner=alice.address, token_id=0) 1349 ), 1350 sp.record( 1351 balance=ICO, request=sp.record(owner=alice.address, token_id=0) 1352 ), 1353 sp.record(balance=0, request=sp.record(owner=bob.address, token_id=0)), 1354 sp.record( 1355 balance=ICO, 1356 request=sp.record(owner=alice.address, token_id=last_token_id), 1357 ), 1358 ] 1359 1360 sc.verify_equal(_get_balance_of(fa2, requests), expected) 1361 1362 # Check that on-chain view fails on undefined tokens. 1363 sc.verify( 1364 sp.catch_exception( 1365 _get_balance_of(fa2, [sp.record(owner=alice.address, token_id=5)]) 1366 ) 1367 == sp.Some("FA2_TOKEN_UNDEFINED") 1368 ) 1369 1370 def test_offchain_token_metadata( 1371 class_, kwargs, ledger_type, test_name="", modules=[] 1372 ): 1373 """Test `OffchainviewTokenMetadata`. 1374 1375 Tests: 1376 1377 - `token_metadata` works as expected on nft and fungible. 1378 """ 1379 test_name = "FA2_offchain_token_metadata" + ledger_type + test_name 1380 1381 @sp.add_test() 1382 def test(): 1383 sc = sp.test_scenario(test_name, modules) 1384 fa2 = class_(**kwargs) 1385 sc += fa2 1386 sc.h1(test_name) 1387 sc.verify_equal( 1388 fa2.token_metadata(0), sp.record(token_id=0, token_info=tok0_md) 1389 ) 1390 1391 def test_pause(class_, kwargs, ledger_type, test_name="", modules=[]): 1392 """Test the `Pause` policy decorator. 1393 1394 - transfer works without pause 1395 - transfer update_operators without pause 1396 - non admin cannot set_pause 1397 - admin can set pause 1398 - transfer fails with ('FA2_TX_DENIED', 'FA2_PAUSED') when paused. 1399 - update_operators fails with 1400 ('FA2_OPERATORS_UNSUPPORTED', 'FA2_PAUSED') when paused. 1401 """ 1402 test_name = "FA2_pause_" + ledger_type + test_name 1403 1404 @sp.add_test() 1405 def test(): 1406 sc = sp.test_scenario(test_name, modules) 1407 fa2 = class_(**kwargs) 1408 sc.h1(test_name) 1409 1410 sc.h2("Accounts") 1411 sc.show([admin, alice, bob]) 1412 sc.h2("FA2 Contract") 1413 sc += fa2 1414 1415 sc.h2("Transfer without pause") 1416 fa2.transfer( 1417 [ 1418 sp.record( 1419 from_=alice.address, 1420 txs=[sp.record(to_=alice.address, amount=0, token_id=0)], 1421 ), 1422 ], 1423 _sender=alice, 1424 ) 1425 1426 sc.h2("Update_operator without pause") 1427 fa2.update_operators( 1428 [ 1429 sp.variant( 1430 "add_operator", 1431 sp.record( 1432 owner=alice.address, operator=alice.address, token_id=0 1433 ), 1434 ), 1435 sp.variant( 1436 "remove_operator", 1437 sp.record( 1438 owner=alice.address, operator=alice.address, token_id=0 1439 ), 1440 ), 1441 ], 1442 _sender=alice, 1443 ) 1444 1445 sc.h2("Pause entrypoint") 1446 sc.h3("Non admin cannot set pause") 1447 fa2.set_pause(True, _sender=alice, _valid=False, _exception="FA2_NOT_ADMIN") 1448 1449 sc.h3("Admin set pause") 1450 fa2.set_pause(True, _sender=admin) 1451 1452 sc.h2("Transfer fails with pause") 1453 fa2.transfer( 1454 [ 1455 sp.record( 1456 from_=alice.address, 1457 txs=[sp.record(to_=alice.address, amount=0, token_id=0)], 1458 ), 1459 ], 1460 _sender=alice, 1461 _valid=False, 1462 _exception=("FA2_TX_DENIED", "FA2_PAUSED"), 1463 ) 1464 1465 sc.h2("Update_operator fails with pause") 1466 fa2.update_operators( 1467 [ 1468 sp.variant( 1469 "add_operator", 1470 sp.record( 1471 owner=alice.address, operator=alice.address, token_id=0 1472 ), 1473 ), 1474 sp.variant( 1475 "remove_operator", 1476 sp.record( 1477 owner=alice.address, operator=alice.address, token_id=0 1478 ), 1479 ), 1480 ], 1481 _sender=alice, 1482 _valid=False, 1483 _exception=("FA2_OPERATORS_UNSUPPORTED", "FA2_PAUSED"), 1484 )
11def make_metadata(symbol, name, decimals): 12 """Helper function to build metadata JSON bytes values.""" 13 return sp.map( 14 l={ 15 "decimals": sp.scenario_utils.bytes_of_string("%d" % decimals), 16 "name": sp.scenario_utils.bytes_of_string(name), 17 "symbol": sp.scenario_utils.bytes_of_string(symbol), 18 } 19 )
Helper function to build metadata JSON bytes values.
73def test_core_interfaces(class_, kwargs, ledger_type, test_name="", modules=[]): 74 """Test that each core interface has the right type and layout. 75 76 Args: 77 test_name (string): Name of the test 78 fa2 (sp.Contract): The FA2 contract on which the tests occur. 79 80 The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md. 81 82 For NFT contracts, `alice` must own the three tokens. 83 84 For Fungible contracts, `alice` must own 42 of each token types. 85 86 Tests: 87 88 - Entrypoints: `balance_of`, `transfer`, `update_operators` 89 - Storage: test of `token_metadata` 90 """ 91 test_name = "test_core_interfaces_" + ledger_type + "_" + test_name 92 93 @sp.add_test() 94 def test(): 95 sc = sp.test_scenario(test_name, modules) 96 sc.add_module(helpers) 97 sc.h1(test_name) 98 sc.p("A call to all the standard entrypoints and off-chain views.") 99 100 sc.h2("Accounts") 101 sc.show([admin, alice, bob]) 102 103 sc.h2("FA2 contract") 104 fa2 = class_(**kwargs) 105 sc += fa2 106 107 # Entrypoints 108 109 sc.h2("Entrypoint: update_operators") 110 fa2.update_operators( 111 sp.set_type_expr( 112 [ 113 sp.variant( 114 "add_operator", 115 sp.record( 116 owner=alice.address, operator=alice.address, token_id=0 117 ), 118 ) 119 ], 120 sp.list[ 121 sp.variant( 122 add_operator=sp.record( 123 owner=sp.address, operator=sp.address, token_id=sp.nat 124 ).layout(("owner", ("operator", "token_id"))), 125 remove_operator=sp.record( 126 owner=sp.address, operator=sp.address, token_id=sp.nat 127 ).layout(("owner", ("operator", "token_id"))), 128 ) 129 ], 130 ), 131 _sender=alice, 132 ) 133 134 sc.h2("Entrypoint: transfer") 135 fa2.transfer( 136 sp.set_type_expr( 137 [ 138 sp.record( 139 from_=alice.address, 140 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 141 ) 142 ], 143 sp.list[ 144 sp.record( 145 from_=sp.address, 146 txs=sp.list[ 147 sp.record( 148 to_=sp.address, token_id=sp.nat, amount=sp.nat 149 ).layout(("to_", ("token_id", "amount"))) 150 ], 151 ).layout(("from_", "txs")) 152 ], 153 ), 154 _sender=alice, 155 ) 156 157 sc.h2("Entrypoint: balance_of") 158 sc.h3("Receiver contract") 159 c2 = helpers.TestReceiverBalanceOf() 160 sc += c2 161 162 sc.h3("Call to balance_of") 163 fa2.balance_of( 164 sp.set_type_expr( 165 sp.record( 166 callback=sp.contract( 167 sp.list[helpers.t_balance_of_response], 168 c2.address, 169 entrypoint="receive_balances", 170 ).unwrap_some(), 171 requests=[sp.record(owner=alice.address, token_id=0)], 172 ), 173 sp.record( 174 requests=sp.list[ 175 sp.record(owner=sp.address, token_id=sp.nat).layout( 176 ("owner", "token_id") 177 ) 178 ], 179 callback=sp.contract[ 180 sp.list[ 181 sp.record( 182 request=sp.record( 183 owner=sp.address, token_id=sp.nat 184 ).layout(("owner", "token_id")), 185 balance=sp.nat, 186 ).layout(("request", "balance")) 187 ] 188 ], 189 ).layout(("requests", "callback")), 190 ), 191 _sender=alice, 192 ) 193 194 # Storage 195 196 sc.h2("Storage: token_metadata") 197 sc.verify_equal( 198 fa2.data.token_metadata[0], sp.record(token_id=0, token_info=tok0_md) 199 )
Test that each core interface has the right type and layout.
Args: test_name (string): Name of the test fa2 (sp.Contract): The FA2 contract on which the tests occur.
The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md.
For NFT contracts, alice
must own the three tokens.
For Fungible contracts, alice
must own 42 of each token types.
Tests:
- Entrypoints:
balance_of
,transfer
,update_operators
- Storage: test of
token_metadata
202def test_transfer(class_, kwargs, ledger_type, test_name="", modules=[]): 203 """Test that transfer entrypoint works as expected. 204 205 Do not test transfer permission policies. 206 207 Args: 208 test_name (string): Name of the test 209 fa2 (sp.Contract): The FA2 contract on which the tests occur. 210 211 SingleAsset contract: The contract must contains the token 0: tok0_md. 212 Others: The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md. 213 214 NFT contract: `sp.test_account("Alice").address` must own all the tokens. 215 Fungible contract: `sp.test_account("Alice").address` must own 42 of each token. 216 217 Tests: 218 - initial minting works as expected. 219 - `get_balance` returns `balance = 0` for non owned tokens. 220 - transfer of 0 tokens works when not owning tokens. 221 - transfer of 0 doesn't change `ledger` storage. 222 - transfer of 0 tokens works when owning tokens. 223 - transfers with multiple operations and transactions works as expected. 224 - fails with `FA2_INSUFFICIENT_BALANCE` when not enough balance. 225 - transfer to self doesn't change anything. 226 - transfer to self with more than balance gives `FA2_INSUFFICIENT_BALANCE`. 227 - transfer to self of undefined token gives `FA2_TOKEN_UNDEFINED`. 228 - transfer to someone else of undefined token gives `FA2_TOKEN_UNDEFINED`. 229 """ 230 test_name = "test_transfer_" + ledger_type + "_" + test_name 231 232 @sp.add_test() 233 def test(): 234 sc = sp.test_scenario(test_name, modules) 235 sc.h1(test_name) 236 237 sc.h2("Accounts") 238 sc.show([admin, alice, bob]) 239 240 sc.h2("Contract") 241 fa2 = class_(**kwargs) 242 sc += fa2 243 244 if ledger_type == "NFT": 245 ICO = 1 # Initial coin offering. 246 TX = 1 # How much we transfer at a time during the tests. 247 else: 248 ICO = 42 # Initial coin offering. 249 TX = 12 # How much we transfer at a time during the tests. 250 251 # Check that the contract storage is correctly initialized. 252 sc.verify(_get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == ICO) 253 if not ledger_type == "SingleAsset": 254 sc.verify( 255 _get_balance(fa2, sp.record(owner=alice.address, token_id=1)) == ICO 256 ) 257 sc.verify( 258 _get_balance(fa2, sp.record(owner=alice.address, token_id=2)) == ICO 259 ) 260 261 # Check that the balance is interpreted as zero when the owner doesn't hold any. 262 # TZIP-12: If the token owner does not hold any tokens of type token_id, 263 # the owner's balance is interpreted as zero. 264 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 265 266 sc.h2("Zero amount transfer") 267 sc.p("TZIP-12: Transfers of zero amount MUST be treated as normal transfers.") 268 269 # Check that someone with 0 token can transfer 0 token. 270 fa2.transfer( 271 [ 272 sp.record( 273 from_=bob.address, 274 txs=[sp.record(to_=alice.address, amount=0, token_id=0)], 275 ), 276 ], 277 _sender=bob, 278 ) 279 280 # Check that the contract storage is unchanged. 281 sc.verify(_get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == ICO) 282 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 283 284 # Check that someone with some tokens can transfer 0 token. 285 fa2.transfer( 286 [ 287 sp.record( 288 from_=alice.address, 289 txs=[sp.record(to_=bob.address, amount=0, token_id=0)], 290 ), 291 ], 292 _sender=alice, 293 ) 294 295 # Check that the contract storage is unchanged. 296 sc.verify(_get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == ICO) 297 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 298 299 sc.h2("Transfers Alice -> Bob") 300 sc.p( 301 """TZIP-12: Each transfer in the batch MUST decrement token balance 302 of the source (from_) address by the amount of the transfer and 303 increment token balance of the destination (to_) address by the 304 amount of the transfer.""" 305 ) 306 307 # Perform a complex transfer with 2 operations, one of which contains 2 transactions. 308 if ledger_type == "SingleAsset": 309 fa2.transfer( 310 [ 311 sp.record( 312 from_=alice.address, 313 txs=[ 314 sp.record(to_=bob.address, amount=TX // 3, token_id=0), 315 sp.record(to_=bob.address, amount=TX // 3, token_id=0), 316 ], 317 ), 318 sp.record( 319 from_=alice.address, 320 txs=[sp.record(to_=bob.address, amount=TX // 3, token_id=0)], 321 ), 322 ], 323 _sender=alice, 324 ) 325 else: 326 fa2.transfer( 327 [ 328 sp.record( 329 from_=alice.address, 330 txs=[ 331 sp.record(to_=bob.address, amount=TX, token_id=0), 332 sp.record(to_=bob.address, amount=TX, token_id=1), 333 ], 334 ), 335 sp.record( 336 from_=alice.address, 337 txs=[sp.record(to_=bob.address, amount=TX, token_id=2)], 338 ), 339 ], 340 _sender=alice, 341 ) 342 343 # Check that the contract storage is correctly updated. 344 sc.verify( 345 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == ICO - TX 346 ) 347 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == TX) 348 349 if not ledger_type == "SingleAsset": 350 sc.verify( 351 _get_balance(fa2, sp.record(owner=alice.address, token_id=1)) 352 == ICO - TX 353 ) 354 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=1)) == TX) 355 356 # Check without using get_balance because the ledger interface 357 # differs between NFT and fungible. 358 if ledger_type == "NFT": 359 sc.verify(fa2.data.ledger[0] == bob.address) 360 else: 361 if ledger_type == "Fungible": 362 sc.verify(fa2.data.ledger[(alice.address, 0)] == ICO - TX) 363 sc.verify(fa2.data.ledger[(bob.address, 0)] == TX) 364 else: 365 sc.verify(fa2.data.ledger[alice.address] == ICO - TX) 366 sc.verify(fa2.data.ledger[bob.address] == TX) 367 368 # Error tests 369 370 # test of FA2_INSUFFICIENT_BALANCE. 371 sc.h2("Insufficient balance") 372 sc.p( 373 """TIP-12: If the transfer amount exceeds current token balance of 374 the source address, the whole transfer operation MUST fail with 375 the error mnemonic "FA2_INSUFFICIENT_BALANCE".""" 376 ) 377 378 # Compute bob_balance to transfer 1 more token. 379 bob_balance = sc.compute( 380 _get_balance(fa2, sp.record(owner=bob.address, token_id=0)) 381 ) 382 383 # Test that a complex transfer with only one insufficient 384 # balance fails. 385 fa2.transfer( 386 [ 387 sp.record( 388 from_=bob.address, 389 txs=[ 390 sp.record( 391 to_=alice.address, amount=bob_balance + 1, token_id=0 392 ), 393 sp.record(to_=alice.address, amount=0, token_id=0), 394 ], 395 ), 396 sp.record( 397 from_=bob.address, 398 txs=[sp.record(to_=alice.address, amount=0, token_id=0)], 399 ), 400 ], 401 _sender=bob, 402 _valid=False, 403 _exception="FA2_INSUFFICIENT_BALANCE", 404 ) 405 406 sc.h2("Same address transfer") 407 sc.p( 408 """TZIP-12: Transfers with the same address (from_ equals to_) MUST 409 be treated as normal transfers.""" 410 ) 411 412 # Test that someone can transfer all his balance to itself 413 # without problem. 414 fa2.transfer( 415 [ 416 sp.record( 417 from_=bob.address, 418 txs=[sp.record(to_=bob.address, amount=bob_balance, token_id=0)], 419 ), 420 ], 421 _sender=bob, 422 ) 423 424 # Check that the contract storage is unchanged. 425 sc.verify( 426 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == ICO - TX 427 ) 428 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == TX) 429 if not ledger_type == "SingleAsset": 430 sc.verify( 431 _get_balance(fa2, sp.record(owner=alice.address, token_id=1)) 432 == ICO - TX 433 ) 434 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=1)) == TX) 435 436 # Test that someone cannot transfer more tokens than he holds 437 # even to himself. 438 fa2.transfer( 439 [ 440 sp.record( 441 from_=bob.address, 442 txs=[ 443 sp.record(to_=bob.address, amount=bob_balance + 1, token_id=0) 444 ], 445 ), 446 ], 447 _sender=bob, 448 _valid=False, 449 _exception="FA2_INSUFFICIENT_BALANCE", 450 ) 451 452 # test of FA2_TOKEN_UNDEFINED. 453 sc.h2("Not defined token") 454 sc.p( 455 """TZIP-12: If one of the specified token_ids is not defined within 456 the FA2 contract, the entrypoint MUST fail with the error 457 mnemonic "FA2_TOKEN_UNDEFINED".""" 458 ) 459 460 # A transfer of 0 tokens to self gives FA2_TOKEN_UNDEFINED if 461 # not defined. 462 fa2.transfer( 463 [ 464 sp.record( 465 from_=bob.address, 466 txs=[sp.record(to_=bob.address, amount=0, token_id=4)], 467 ), 468 ], 469 _sender=bob, 470 _valid=False, 471 _exception="FA2_TOKEN_UNDEFINED", 472 ) 473 474 # A transfer of 1 token to someone else gives 475 # FA2_TOKEN_UNDEFINED if not defined. 476 fa2.transfer( 477 [ 478 sp.record( 479 from_=alice.address, 480 txs=[sp.record(to_=bob.address, amount=1, token_id=4)], 481 ), 482 ], 483 _sender=bob, 484 _valid=False, 485 _exception="FA2_TOKEN_UNDEFINED", 486 )
Test that transfer entrypoint works as expected.
Do not test transfer permission policies.
Args: test_name (string): Name of the test fa2 (sp.Contract): The FA2 contract on which the tests occur.
SingleAsset contract: The contract must contains the token 0: tok0_md. Others: The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md.
NFT contract: sp.test_account("Alice").address
must own all the tokens.
Fungible contract: sp.test_account("Alice").address
must own 42 of each token.
Tests:
- initial minting works as expected.
- get_balance
returns balance = 0
for non owned tokens.
- transfer of 0 tokens works when not owning tokens.
- transfer of 0 doesn't change ledger
storage.
- transfer of 0 tokens works when owning tokens.
- transfers with multiple operations and transactions works as expected.
- fails with FA2_INSUFFICIENT_BALANCE
when not enough balance.
- transfer to self doesn't change anything.
- transfer to self with more than balance gives FA2_INSUFFICIENT_BALANCE
.
- transfer to self of undefined token gives FA2_TOKEN_UNDEFINED
.
- transfer to someone else of undefined token gives FA2_TOKEN_UNDEFINED
.
489def test_balance_of(class_, kwargs, ledger_type, test_name="", modules=[]): 490 """Test that balance_of entrypoint works as expected. 491 492 Args: 493 test_name (string): Name of the test 494 fa2 (sp.Contract): The FA2 contract on which the tests occur. 495 496 SingleAsset contract: The contract must contains the token 0: tok0_md. 497 Others: The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md. 498 499 NFT contract: `sp.test_account("Alice").address` must own all the tokens. 500 Fungible contract: `sp.test_account("Alice").address` must own 42 of each token. 501 502 Tests: 503 504 - `balance_of` calls back with valid results. 505 - `balance_of` fails with `FA2_TOKEN_UNDEFINED` when token is undefined. 506 """ 507 test_name = "test_balance_of_" + ledger_type + "_" + test_name 508 509 @sp.add_test() 510 def test(): 511 sc = sp.test_scenario(test_name, modules) 512 sc.add_module(helpers) 513 sc.h1(test_name) 514 515 sc.h2("Accounts") 516 sc.show([admin, alice, bob]) 517 518 # We initialize the contract with an initial mint. 519 sc.h2("Contract") 520 fa2 = class_(**kwargs) 521 sc += fa2 522 523 sc.h3("Receiver contract") 524 c2 = helpers.TestReceiverBalanceOf() 525 sc += c2 526 527 ICO = 1 if ledger_type == "NFT" else 42 # Initial coin offering. 528 last_token_id = 0 if ledger_type == "SingleAsset" else 2 529 530 requests = [ 531 sp.record(owner=alice.address, token_id=0), 532 sp.record(owner=alice.address, token_id=0), 533 sp.record(owner=bob.address, token_id=0), 534 sp.record(owner=alice.address, token_id=last_token_id), 535 ] 536 expected = [ 537 sp.record(balance=ICO, request=sp.record(owner=alice.address, token_id=0)), 538 sp.record(balance=ICO, request=sp.record(owner=alice.address, token_id=0)), 539 sp.record(balance=0, request=sp.record(owner=bob.address, token_id=0)), 540 sp.record( 541 balance=ICO, 542 request=sp.record(owner=alice.address, token_id=last_token_id), 543 ), 544 ] 545 546 # Call to balance_of. 547 fa2.balance_of( 548 callback=sp.contract( 549 sp.list[helpers.t_balance_of_response], 550 c2.address, 551 entrypoint="receive_balances", 552 ).unwrap_some(), 553 requests=requests, 554 _sender=alice, 555 ) 556 557 # Check that balance_of returns the correct balances. 558 # This test non-deduplication, non-reordering, on multiple tokens. 559 sc.verify_equal(c2.data.last_received_balances, expected) 560 561 # Expected errors 562 sc.h2("FA2_TOKEN_UNDEFINED error") 563 fa2.balance_of( 564 callback=sp.contract( 565 sp.list[helpers.t_balance_of_response], 566 c2.address, 567 entrypoint="receive_balances", 568 ).unwrap_some(), 569 requests=[ 570 sp.record(owner=alice.address, token_id=0), 571 sp.record(owner=alice.address, token_id=5), 572 ], 573 _sender=alice, 574 _valid=False, 575 _exception="FA2_TOKEN_UNDEFINED", 576 )
Test that balance_of entrypoint works as expected.
Args: test_name (string): Name of the test fa2 (sp.Contract): The FA2 contract on which the tests occur.
SingleAsset contract: The contract must contains the token 0: tok0_md. Others: The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md.
NFT contract: sp.test_account("Alice").address
must own all the tokens.
Fungible contract: sp.test_account("Alice").address
must own 42 of each token.
Tests:
balance_of
calls back with valid results.balance_of
fails withFA2_TOKEN_UNDEFINED
when token is undefined.
579def test_no_transfer(class_, kwargs, ledger_type, test_name="", modules=[]): 580 """Test that the `no-transfer` policy works as expected. 581 582 Args: 583 test_name (string): Name of the test 584 fa2 (sp.Contract): The FA2 contract on which the tests occur. 585 586 The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md. 587 588 For NFT contracts, `alice` must own the three tokens. 589 590 For Fungible contracts, `alice` must own 42 of each token types. 591 592 Tests: 593 594 - transfer fails with FA2_TX_DENIED. 595 - transfer fails with FA2_TX_DENIED even for admin. 596 - update_operators fails with FA2_OPERATORS_UNSUPPORTED. 597 """ 598 test_name = "test_no-transfer_" + ledger_type + "_" + test_name 599 600 @sp.add_test() 601 def test(): 602 sc = sp.test_scenario(test_name, modules) 603 sc.h1(test_name) 604 605 sc.h2("Accounts") 606 sc.show([admin, alice, bob]) 607 608 sc.h2("FA2 with NoTransfer policy") 609 sc.p("No transfer are allowed.") 610 fa2 = class_(**kwargs) 611 sc += fa2 612 613 # Transfer fails as expected. 614 sc.h2("Alice cannot transfer: FA2_TX_DENIED") 615 fa2.transfer( 616 [ 617 sp.record( 618 from_=alice.address, 619 txs=[sp.record(to_=bob.address, amount=1, token_id=0)], 620 ) 621 ], 622 _sender=alice, 623 _valid=False, 624 _exception="FA2_TX_DENIED", 625 ) 626 627 # Even Admin cannot transfer. 628 sc.h2("Admin cannot transfer alice's token: FA2_TX_DENIED") 629 fa2.transfer( 630 [ 631 sp.record( 632 from_=alice.address, 633 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 634 ) 635 ], 636 _sender=admin, 637 _valid=False, 638 _exception="FA2_TX_DENIED", 639 ) 640 641 # update_operators is unsupported. 642 sc.h2("Alice cannot add operator: FA2_OPERATORS_UNSUPPORTED") 643 fa2.update_operators( 644 [ 645 sp.variant( 646 "add_operator", 647 sp.record(owner=alice.address, operator=bob.address, token_id=0), 648 ) 649 ], 650 _sender=alice, 651 _valid=False, 652 _exception="FA2_OPERATORS_UNSUPPORTED", 653 )
Test that the no-transfer
policy works as expected.
Args: test_name (string): Name of the test fa2 (sp.Contract): The FA2 contract on which the tests occur.
The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md.
For NFT contracts, alice
must own the three tokens.
For Fungible contracts, alice
must own 42 of each token types.
Tests:
- transfer fails with FA2_TX_DENIED.
- transfer fails with FA2_TX_DENIED even for admin.
- update_operators fails with FA2_OPERATORS_UNSUPPORTED.
656def test_owner_transfer(class_, kwargs, ledger_type, test_name="", modules=[]): 657 """Test that the `owner-transfer` policy works as expected. 658 659 Args: 660 test_name (string): Name of the test 661 fa2 (sp.Contract): the FA2 contract on which the tests occur. 662 663 The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md. 664 665 For NFT contracts, `alice` must own the three tokens. 666 667 For Fungible contracts, `alice` must own 42 of each token types. 668 669 Tests: 670 671 - owner can transfer. 672 - transfer fails with FA2_NOT_OWNER for non owner, even admin. 673 - update_operators fails with FA2_OPERATORS_UNSUPPORTED. 674 """ 675 test_name = "test_owner-transfer_" + ledger_type + "_" + test_name 676 677 @sp.add_test() 678 def test(): 679 sc = sp.test_scenario(test_name, modules) 680 sc.h1(test_name) 681 682 sc.h2("Accounts") 683 sc.show([admin, alice, bob]) 684 685 sc.h2("FA2 with OwnerTransfer policy") 686 sc.p("Only owner can transfer, no operator allowed.") 687 fa2 = class_(**kwargs) 688 sc += fa2 689 690 # The owner can transfer its tokens. 691 sc.h2("Alice can transfer") 692 fa2.transfer( 693 [ 694 sp.record( 695 from_=alice.address, 696 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 697 ) 698 ], 699 _sender=alice, 700 ) 701 702 # Admin cannot transfer someone else tokens. 703 sc.h2("Admin cannot transfer alice's token: FA2_NOT_OWNER") 704 fa2.transfer( 705 [ 706 sp.record( 707 from_=alice.address, 708 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 709 ) 710 ], 711 _sender=admin, 712 _valid=False, 713 _exception="FA2_NOT_OWNER", 714 ) 715 716 # Someone cannot transfer someone else tokens. 717 sc.h2("Admin cannot transfer alice's token: FA2_NOT_OWNER") 718 fa2.transfer( 719 [ 720 sp.record( 721 from_=alice.address, 722 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 723 ) 724 ], 725 _sender=bob, 726 _valid=False, 727 _exception="FA2_NOT_OWNER", 728 ) 729 730 # Someone cannot add operator. 731 sc.h2("Alice cannot add operator: FA2_OPERATORS_UNSUPPORTED") 732 fa2.update_operators( 733 [ 734 sp.variant( 735 "add_operator", 736 sp.record(owner=alice.address, operator=bob.address, token_id=0), 737 ) 738 ], 739 _sender=alice, 740 _valid=False, 741 _exception="FA2_OPERATORS_UNSUPPORTED", 742 )
Test that the owner-transfer
policy works as expected.
Args: test_name (string): Name of the test fa2 (sp.Contract): the FA2 contract on which the tests occur.
The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md.
For NFT contracts, alice
must own the three tokens.
For Fungible contracts, alice
must own 42 of each token types.
Tests:
- owner can transfer.
- transfer fails with FA2_NOT_OWNER for non owner, even admin.
- update_operators fails with FA2_OPERATORS_UNSUPPORTED.
745def test_owner_or_operator_transfer( 746 class_, kwargs, ledger_type, test_name="", modules=[] 747): 748 """Test that the `owner-or-operator-transfer` policy works as expected. 749 750 Args: 751 test_name (string): Name of the test 752 fa2 (sp.Contract): The FA2 contract on which the tests occur. 753 754 SingleAsset contract: The contract must contains the token 0: tok0_md. 755 Others: The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md. 756 757 NFT contract: `sp.test_account("Alice").address` must own all the tokens. 758 Fungible contract: `sp.test_account("Alice").address` must own 42 of each token. 759 760 Tests: 761 762 - owner can transfer. 763 - transfer fails with FA2_NOT_OPERATOR for non operator, even admin. 764 - update_operators fails with FA2_OPERATORS_UNSUPPORTED. 765 - owner can add operator. 766 - operator can transfer. 767 - operator cannot transfer for non allowed `token_id`. 768 - owner can remove operator and add operator in a batch. 769 - removed operator cannot transfer anymore. 770 - operator added in a batch can transfer. 771 - add then remove the same operator doesn't change the storage. 772 - remove then add the same operator does change the storage. 773 """ 774 test_name = "test_owner-or-operator-transfer_" + ledger_type + "_" + test_name 775 776 @sp.add_test() 777 def test(): 778 operator_bob = sp.record(owner=alice.address, operator=bob.address, token_id=0) 779 operator_charlie = sp.record( 780 owner=alice.address, operator=charlie.address, token_id=0 781 ) 782 783 sc = sp.test_scenario(test_name, modules) 784 sc.h1(test_name) 785 786 sc.h2("Accounts") 787 sc.show([admin, alice, bob]) 788 789 sc.h2("FA2 with OwnerOrOperatorTransfer policy") 790 sc.p("Owner or operators can transfer.") 791 fa2 = class_(**kwargs) 792 sc += fa2 793 794 # Owner can transfer his tokens. 795 sc.h2("Alice can transfer") 796 fa2.transfer( 797 [ 798 sp.record( 799 from_=alice.address, 800 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 801 ) 802 ], 803 _sender=alice, 804 ) 805 806 # Admin can transfer others tokens. 807 sc.h2("Admin cannot transfer alice's token: FA2_NOT_OPERATOR") 808 fa2.transfer( 809 [ 810 sp.record( 811 from_=alice.address, 812 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 813 ) 814 ], 815 _sender=admin, 816 _valid=False, 817 _exception="FA2_NOT_OPERATOR", 818 ) 819 820 # Update operator works. 821 sc.h2("Alice adds Bob as operator") 822 fa2.update_operators([sp.variant.add_operator(operator_bob)], _sender=alice) 823 824 # The contract is updated as expected. 825 sc.verify(fa2.data.operators.contains(operator_bob)) 826 827 # Operator can transfer allowed tokens on behalf of owner. 828 sc.h2("Bob can transfer Alice's token id 0") 829 fa2.transfer( 830 [ 831 sp.record( 832 from_=alice.address, 833 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 834 ) 835 ], 836 _sender=bob, 837 ) 838 839 if not ledger_type == "SingleAsset": 840 # Operator cannot transfer not allowed tokens on behalf of owner. 841 sc.h2("Bob cannot transfer Alice's token id 1") 842 fa2.transfer( 843 [ 844 sp.record( 845 from_=alice.address, 846 txs=[sp.record(to_=alice.address, amount=1, token_id=1)], 847 ) 848 ], 849 _sender=bob, 850 _valid=False, 851 _exception="FA2_NOT_OPERATOR", 852 ) 853 854 # Batch of update_operators actions. 855 sc.h2("Alice can remove Bob as operator and add Charlie") 856 fa2.update_operators( 857 [ 858 sp.variant.remove_operator(operator_bob), 859 sp.variant.add_operator(operator_charlie), 860 ], 861 _sender=alice, 862 ) 863 864 # The contract is updated as expected. 865 sc.verify(~fa2.data.operators.contains(operator_bob)) 866 sc.verify(fa2.data.operators.contains(operator_charlie)) 867 868 # A removed operator lose its rights. 869 sc.h2("Bob cannot transfer Alice's token 0 anymore") 870 fa2.transfer( 871 [ 872 sp.record( 873 from_=alice.address, 874 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 875 ) 876 ], 877 _sender=bob, 878 _valid=False, 879 _exception="FA2_NOT_OPERATOR", 880 ) 881 882 # The new added operator can now do the transfer. 883 sc.h2("Charlie can transfer Alice's token") 884 fa2.transfer( 885 [ 886 sp.record( 887 from_=alice.address, 888 txs=[sp.record(to_=charlie.address, amount=1, token_id=0)], 889 ) 890 ], 891 _sender=charlie, 892 ) 893 894 # The contract is updated as expected. 895 sc.verify(_get_balance(fa2, sp.record(owner=charlie.address, token_id=0)) == 1) 896 897 # Remove after a Add does nothing. 898 sc.h2("Add then Remove in the same batch is transparent") 899 sc.p( 900 """TZIP-12: If two different commands in the list add and remove an 901 operator for the same token owner and token ID, the last command 902 in the list MUST take effect.""" 903 ) 904 fa2.update_operators( 905 [ 906 sp.variant.add_operator(operator_bob), 907 sp.variant.remove_operator(operator_bob), 908 ], 909 _sender=alice, 910 ) 911 sc.verify(~fa2.data.operators.contains(operator_bob)) 912 913 # Add after remove works 914 sc.h2("Remove then Add do add the operator") 915 fa2.update_operators( 916 [ 917 sp.variant.remove_operator(operator_bob), 918 sp.variant.add_operator(operator_bob), 919 ], 920 _sender=alice, 921 ) 922 sc.verify(fa2.data.operators.contains(operator_bob))
Test that the owner-or-operator-transfer
policy works as expected.
Args: test_name (string): Name of the test fa2 (sp.Contract): The FA2 contract on which the tests occur.
SingleAsset contract: The contract must contains the token 0: tok0_md. Others: The contract must contains the tokens 0: tok0_md, 1: tok1_md, 2: tok2_md.
NFT contract: sp.test_account("Alice").address
must own all the tokens.
Fungible contract: sp.test_account("Alice").address
must own 42 of each token.
Tests:
- owner can transfer.
- transfer fails with FA2_NOT_OPERATOR for non operator, even admin.
- update_operators fails with FA2_OPERATORS_UNSUPPORTED.
- owner can add operator.
- operator can transfer.
- operator cannot transfer for non allowed
token_id
. - owner can remove operator and add operator in a batch.
- removed operator cannot transfer anymore.
- operator added in a batch can transfer.
- add then remove the same operator doesn't change the storage.
- remove then add the same operator does change the storage.
930class NS: 931 """Non standard features of FA2_lib 932 933 Mixin tested: 934 935 - Admin, 936 - WithdrawMutez 937 - ChangeMetadata 938 - OffchainviewTokenMetadata 939 - OnchainviewBalanceOf 940 - Mint* 941 - Burn* 942 """ 943 944 def test_admin(class_, kwargs, ledger_type, test_name="", modules=[]): 945 """Test `Admin` mixin 946 947 - non admin cannot set admin 948 - admin can set admin 949 - new admin can set admin 950 """ 951 test_name = "FA2_optional_interfaces_admin" + ledger_type + test_name 952 953 @sp.add_test() 954 def test(): 955 sc = sp.test_scenario(test_name, modules) 956 fa2 = class_(**kwargs) 957 sc += fa2 958 sc.h1(test_name) 959 sc.h2("Non admin cannot set admin") 960 fa2.set_administrator( 961 alice.address, _sender=alice, _valid=False, _exception="FA2_NOT_ADMIN" 962 ) 963 sc.verify(fa2.data.administrator == admin.address) 964 fa2.set_administrator(admin2.address, _sender=admin) 965 sc.verify(~(fa2.data.administrator == admin.address)) 966 sc.verify(fa2.data.administrator == admin2.address) 967 fa2.set_administrator(admin.address, _sender=admin2) 968 969 def test_mint(class_, kwargs, ledger_type, test_name="", modules=[]): 970 """Test `Mint*` mixin. 971 972 - `mint` fails with `FA2_NOT_ADMIN` for non-admin. 973 - `mint` adds the tokens. 974 - `mint` update the supply. 975 - `mint` works for existing tokens in fungible contracts. 976 """ 977 test_name = "FA2_mint_" + ledger_type + test_name 978 979 def mint_nft(sc, fa2): 980 sc.h2("Mint entrypoint") 981 # Non admin cannot mint a new NFT token. 982 sc.h3("NFT mint failure") 983 fa2.mint( 984 [sp.record(metadata=tok0_md, to_=alice.address)], 985 _sender=alice, 986 _valid=False, 987 _exception="FA2_NOT_ADMIN", 988 ) 989 990 sc.h3("Mint") 991 # Mint of a new NFT token. 992 fa2.mint( 993 [ 994 sp.record(metadata=tok0_md, to_=alice.address), 995 sp.record(metadata=tok1_md, to_=alice.address), 996 sp.record(metadata=tok2_md, to_=bob.address), 997 ], 998 _sender=admin, 999 ) 1000 1001 # Check that the balance is updated. 1002 sc.verify( 1003 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 1 1004 ) 1005 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 1006 sc.verify( 1007 _get_balance(fa2, sp.record(owner=alice.address, token_id=1)) == 1 1008 ) 1009 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=1)) == 0) 1010 sc.verify( 1011 _get_balance(fa2, sp.record(owner=alice.address, token_id=2)) == 0 1012 ) 1013 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=2)) == 1) 1014 1015 # Check that the supply is updated. 1016 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 1) 1017 sc.verify(_total_supply(fa2, sp.record(token_id=1)) == 1) 1018 sc.verify(_total_supply(fa2, sp.record(token_id=2)) == 1) 1019 1020 def mint_fungible(sc, fa2): 1021 sc.h2("Mint entrypoint") 1022 # Non admin cannot mint a new fungible token. 1023 sc.h3("Fungible mint failure") 1024 fa2.mint( 1025 [ 1026 sp.record( 1027 token=sp.variant.new(tok0_md), to_=alice.address, amount=1000 1028 ) 1029 ], 1030 _sender=alice, 1031 _valid=False, 1032 _exception="FA2_NOT_ADMIN", 1033 ) 1034 1035 sc.h3("Mint") 1036 # Mint of a new fungible token. 1037 fa2.mint( 1038 [ 1039 sp.record( 1040 token=sp.variant.new(tok0_md), to_=alice.address, amount=1000 1041 ) 1042 ], 1043 _sender=admin, 1044 ) 1045 1046 # Check ledger update. 1047 sc.verify( 1048 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 1000 1049 ) 1050 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 1051 # Check supply update. 1052 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 1000) 1053 1054 # Mint a new and existing token. 1055 fa2.mint( 1056 [ 1057 sp.record( 1058 token=sp.variant.new(tok1_md), to_=alice.address, amount=1000 1059 ), 1060 sp.record( 1061 token=sp.variant.existing(0), to_=alice.address, amount=1000 1062 ), 1063 sp.record( 1064 token=sp.variant.existing(1), to_=bob.address, amount=1000 1065 ), 1066 ], 1067 _sender=admin, 1068 ) 1069 1070 # Check ledger update. 1071 sc.verify( 1072 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 2000 1073 ) 1074 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 1075 sc.verify( 1076 _get_balance(fa2, sp.record(owner=alice.address, token_id=1)) == 1000 1077 ) 1078 sc.verify( 1079 _get_balance(fa2, sp.record(owner=bob.address, token_id=1)) == 1000 1080 ) 1081 # Check supply update. 1082 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 2000) 1083 sc.verify(_total_supply(fa2, sp.record(token_id=1)) == 2000) 1084 1085 def mint_single_asset(sc, fa2): 1086 sc.h2("Mint entrypoint") 1087 # Non admin cannot mint a new fungible token. 1088 sc.h3("Single asset mint failure") 1089 fa2.mint( 1090 [sp.record(to_=alice.address, amount=1000)], 1091 _sender=alice, 1092 _valid=False, 1093 _exception="FA2_NOT_ADMIN", 1094 ) 1095 1096 sc.h3("Mint") 1097 # Mint some tokens 1098 fa2.mint([sp.record(to_=alice.address, amount=1000)], _sender=admin) 1099 1100 # Check ledger update. 1101 sc.verify( 1102 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 1000 1103 ) 1104 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 1105 # Check supply update. 1106 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 1000) 1107 1108 # multiple mint 1109 fa2.mint( 1110 [ 1111 sp.record(to_=alice.address, amount=1000), 1112 sp.record(to_=bob.address, amount=1000), 1113 sp.record(to_=bob.address, amount=1000), 1114 ], 1115 _sender=admin, 1116 ) 1117 1118 # Check ledger update. 1119 sc.verify( 1120 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 2000 1121 ) 1122 sc.verify( 1123 _get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 2000 1124 ) 1125 # Check supply update. 1126 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 4000) 1127 1128 @sp.add_test() 1129 def test(): 1130 sc = sp.test_scenario(test_name, modules) 1131 fa2 = class_(**kwargs) 1132 sc += fa2 1133 sc.h1(test_name) 1134 if ledger_type == "NFT": 1135 mint_nft(sc, fa2) 1136 elif ledger_type == "Fungible": 1137 mint_fungible(sc, fa2) 1138 elif ledger_type == "SingleAsset": 1139 mint_single_asset(sc, fa2) 1140 else: 1141 raise Exception( 1142 'fa2.ledger type must be "NFT", "Fungible" or "SingleAsset".' 1143 ) 1144 1145 def test_burn( 1146 class_, 1147 kwargs, 1148 ledger_type, 1149 supports_transfer, 1150 supports_operator, 1151 modules=[], 1152 test_name="", 1153 ): 1154 """Test `Burn*` mixin. 1155 1156 - non operator cannot burn, it fails appropriately. 1157 - owner can burn. 1158 - burn fails with `FA2_INSUFFICIENT_BALANCE` when needed. 1159 - operator can burn if the policy allows it. 1160 """ 1161 test_name = "FA2_burn_" + ledger_type + test_name 1162 1163 @sp.add_test() 1164 def test(): 1165 amount = 1 if ledger_type == "NFT" else 20 1166 sc = sp.test_scenario(test_name, modules) 1167 fa2 = class_(**kwargs) 1168 sc += fa2 1169 sc.h1(test_name) 1170 sc.h2("Burn entrypoint") 1171 1172 # Check that non operator cannot burn others tokens. 1173 sc.h3("Cannot burn others tokens") 1174 exception = "FA2_NOT_OPERATOR" if supports_transfer else "FA2_TX_DENIED" 1175 fa2.burn( 1176 [sp.record(token_id=0, from_=alice.address, amount=1)], 1177 _sender=bob, 1178 _valid=False, 1179 _exception=exception, 1180 ) 1181 1182 # Not allowed transfers 1183 if not supports_transfer: 1184 fa2.burn( 1185 [sp.record(token_id=0, from_=alice.address, amount=amount)], 1186 _sender=alice, 1187 _valid=False, 1188 _exception="FA2_TX_DENIED", 1189 ) 1190 return 1191 1192 # Owner can burn. 1193 sc.h3("Owner burns his nft tokens") 1194 fa2.burn( 1195 [sp.record(token_id=0, from_=alice.address, amount=amount)], 1196 _sender=alice, 1197 ) 1198 1199 if ledger_type == "NFT": 1200 # Check that the contract storage is updated. 1201 sc.verify(~fa2.data.ledger.contains(0)) 1202 # Check that burning an nft removes token_metadata. 1203 sc.verify(~fa2.data.token_metadata.contains(0)) 1204 else: 1205 # Check ledger update. 1206 sc.verify( 1207 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 22 1208 ) 1209 # Check that burning doesn't remove token_metadata. 1210 sc.verify(fa2.data.token_metadata.contains(0)) 1211 # Check supply update. 1212 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 22) 1213 1214 # Check burn of FA2_INSUFFICIENT_BALANCE. 1215 sc.h3("Burn with insufficient balance") 1216 token_id = 0 if ledger_type == "SingleAsset" else 1 1217 fa2.burn( 1218 [sp.record(token_id=token_id, from_=alice.address, amount=43)], 1219 _sender=alice, 1220 _valid=False, 1221 _exception="FA2_INSUFFICIENT_BALANCE", 1222 ) 1223 1224 if supports_operator: 1225 # Add operator to test if he can burn on behalf of the owner. 1226 sc.h3("Operator can burn on behalf of the owner") 1227 operator_bob = sp.record( 1228 owner=alice.address, operator=bob.address, token_id=token_id 1229 ) 1230 fa2.update_operators( 1231 [sp.variant.add_operator(operator_bob)], _sender=alice 1232 ) 1233 # Operator can burn nft on behalf of the owner. 1234 fa2.burn( 1235 [sp.record(token_id=token_id, from_=alice.address, amount=0)], 1236 _sender=bob, 1237 ) 1238 1239 def test_withdraw_mutez(class_, kwargs, ledger_type, test_name="", modules=[]): 1240 """Test of WithdrawMutez. 1241 1242 - non admin cannot withdraw mutez: FA2_NOT_ADMIN. 1243 - admin can withdraw mutez. 1244 """ 1245 test_name = "FA2_withdraw_mutez" + ledger_type + test_name 1246 1247 @sp.add_test() 1248 def test(): 1249 sc = sp.test_scenario(test_name, modules) 1250 sc.add_module(helpers) 1251 fa2 = class_(**kwargs) 1252 sc += fa2 1253 sc.h1(test_name) 1254 sc.h2("Mutez receiver contract") 1255 1256 wallet = helpers.Wallet() 1257 sc += wallet 1258 1259 # Non admin cannot withdraw mutez. 1260 sc.h2("Non admin cannot withdraw_mutez") 1261 fa2.withdraw_mutez( 1262 destination=wallet.address, 1263 amount=sp.tez(10), 1264 _sender=alice, 1265 _amount=sp.tez(42), 1266 _valid=False, 1267 _exception="FA2_NOT_ADMIN", 1268 ) 1269 1270 # Admin can withdraw mutez. 1271 sc.h3("Admin withdraw_mutez") 1272 fa2.withdraw_mutez( 1273 destination=wallet.address, 1274 amount=sp.tez(10), 1275 _sender=admin, 1276 _amount=sp.tez(42), 1277 ) 1278 1279 # Check that the mutez has been transferred. 1280 sc.verify(fa2.balance == sp.tez(32)) 1281 sc.verify(wallet.balance == sp.tez(10)) 1282 1283 def test_change_metadata(class_, kwargs, ledger_type, test_name="", modules=[]): 1284 """Test of ChangeMetadata. 1285 1286 - non admin cannot set metadata 1287 - `set_metadata` works as expected 1288 """ 1289 test_name = "FA2_change_metadata" + ledger_type + test_name 1290 1291 @sp.add_test() 1292 def test(): 1293 sc = sp.test_scenario(test_name, modules) 1294 fa2 = class_(**kwargs) 1295 sc += fa2 1296 sc.h1(test_name) 1297 sc.h2("Change metadata") 1298 sc.h3("Non admin cannot set metadata") 1299 fa2.set_metadata( 1300 sp.scenario_utils.metadata_of_url("http://example.com"), 1301 _sender=alice, 1302 _valid=False, 1303 _exception="FA2_NOT_ADMIN", 1304 ) 1305 1306 sc.h3("Admin set metadata") 1307 fa2.set_metadata( 1308 sp.scenario_utils.metadata_of_url("http://example.com"), _sender=admin 1309 ) 1310 1311 # Check that the metadata has been updated. 1312 sc.verify_equal( 1313 fa2.data.metadata[""], 1314 sp.scenario_utils.metadata_of_url("http://example.com")[""], 1315 ) 1316 1317 def test_get_balance_of(class_, kwargs, ledger_type, test_name="", modules=[]): 1318 """Test of `OnchainviewBalanceOf` 1319 1320 - `get_balance_of` doesn't deduplicate nor reorder on nft. 1321 - `get_balance_of` doesn't deduplicate nor reorder on fungible. 1322 - `get_balance_of` fails with `FA2_TOKEN_UNDEFINED` when needed on nft. 1323 - `get_balance_of` fails with `FA2_TOKEN_UNDEFINED` when needed on fungible. 1324 """ 1325 test_name = "FA2_get_balance_of" + ledger_type + test_name 1326 1327 @sp.add_test() 1328 def test(): 1329 sc = sp.test_scenario(test_name, modules) 1330 fa2 = class_(**kwargs) 1331 sc += fa2 1332 sc.h1(test_name) 1333 1334 # get_balance_of on fungible 1335 # We deliberately give multiple identical params to check for 1336 # non-deduplication and non-reordering. 1337 1338 ICO = 1 if ledger_type == "NFT" else 42 # Initial coin offering. 1339 last_token_id = 0 if ledger_type == "SingleAsset" else 2 1340 1341 requests = [ 1342 sp.record(owner=alice.address, token_id=0), 1343 sp.record(owner=alice.address, token_id=0), 1344 sp.record(owner=bob.address, token_id=0), 1345 sp.record(owner=alice.address, token_id=last_token_id), 1346 ] 1347 expected = [ 1348 sp.record( 1349 balance=ICO, request=sp.record(owner=alice.address, token_id=0) 1350 ), 1351 sp.record( 1352 balance=ICO, request=sp.record(owner=alice.address, token_id=0) 1353 ), 1354 sp.record(balance=0, request=sp.record(owner=bob.address, token_id=0)), 1355 sp.record( 1356 balance=ICO, 1357 request=sp.record(owner=alice.address, token_id=last_token_id), 1358 ), 1359 ] 1360 1361 sc.verify_equal(_get_balance_of(fa2, requests), expected) 1362 1363 # Check that on-chain view fails on undefined tokens. 1364 sc.verify( 1365 sp.catch_exception( 1366 _get_balance_of(fa2, [sp.record(owner=alice.address, token_id=5)]) 1367 ) 1368 == sp.Some("FA2_TOKEN_UNDEFINED") 1369 ) 1370 1371 def test_offchain_token_metadata( 1372 class_, kwargs, ledger_type, test_name="", modules=[] 1373 ): 1374 """Test `OffchainviewTokenMetadata`. 1375 1376 Tests: 1377 1378 - `token_metadata` works as expected on nft and fungible. 1379 """ 1380 test_name = "FA2_offchain_token_metadata" + ledger_type + test_name 1381 1382 @sp.add_test() 1383 def test(): 1384 sc = sp.test_scenario(test_name, modules) 1385 fa2 = class_(**kwargs) 1386 sc += fa2 1387 sc.h1(test_name) 1388 sc.verify_equal( 1389 fa2.token_metadata(0), sp.record(token_id=0, token_info=tok0_md) 1390 ) 1391 1392 def test_pause(class_, kwargs, ledger_type, test_name="", modules=[]): 1393 """Test the `Pause` policy decorator. 1394 1395 - transfer works without pause 1396 - transfer update_operators without pause 1397 - non admin cannot set_pause 1398 - admin can set pause 1399 - transfer fails with ('FA2_TX_DENIED', 'FA2_PAUSED') when paused. 1400 - update_operators fails with 1401 ('FA2_OPERATORS_UNSUPPORTED', 'FA2_PAUSED') when paused. 1402 """ 1403 test_name = "FA2_pause_" + ledger_type + test_name 1404 1405 @sp.add_test() 1406 def test(): 1407 sc = sp.test_scenario(test_name, modules) 1408 fa2 = class_(**kwargs) 1409 sc.h1(test_name) 1410 1411 sc.h2("Accounts") 1412 sc.show([admin, alice, bob]) 1413 sc.h2("FA2 Contract") 1414 sc += fa2 1415 1416 sc.h2("Transfer without pause") 1417 fa2.transfer( 1418 [ 1419 sp.record( 1420 from_=alice.address, 1421 txs=[sp.record(to_=alice.address, amount=0, token_id=0)], 1422 ), 1423 ], 1424 _sender=alice, 1425 ) 1426 1427 sc.h2("Update_operator without pause") 1428 fa2.update_operators( 1429 [ 1430 sp.variant( 1431 "add_operator", 1432 sp.record( 1433 owner=alice.address, operator=alice.address, token_id=0 1434 ), 1435 ), 1436 sp.variant( 1437 "remove_operator", 1438 sp.record( 1439 owner=alice.address, operator=alice.address, token_id=0 1440 ), 1441 ), 1442 ], 1443 _sender=alice, 1444 ) 1445 1446 sc.h2("Pause entrypoint") 1447 sc.h3("Non admin cannot set pause") 1448 fa2.set_pause(True, _sender=alice, _valid=False, _exception="FA2_NOT_ADMIN") 1449 1450 sc.h3("Admin set pause") 1451 fa2.set_pause(True, _sender=admin) 1452 1453 sc.h2("Transfer fails with pause") 1454 fa2.transfer( 1455 [ 1456 sp.record( 1457 from_=alice.address, 1458 txs=[sp.record(to_=alice.address, amount=0, token_id=0)], 1459 ), 1460 ], 1461 _sender=alice, 1462 _valid=False, 1463 _exception=("FA2_TX_DENIED", "FA2_PAUSED"), 1464 ) 1465 1466 sc.h2("Update_operator fails with pause") 1467 fa2.update_operators( 1468 [ 1469 sp.variant( 1470 "add_operator", 1471 sp.record( 1472 owner=alice.address, operator=alice.address, token_id=0 1473 ), 1474 ), 1475 sp.variant( 1476 "remove_operator", 1477 sp.record( 1478 owner=alice.address, operator=alice.address, token_id=0 1479 ), 1480 ), 1481 ], 1482 _sender=alice, 1483 _valid=False, 1484 _exception=("FA2_OPERATORS_UNSUPPORTED", "FA2_PAUSED"), 1485 )
Non standard features of FA2_lib
Mixin tested:
- Admin,
- WithdrawMutez
- ChangeMetadata
- OffchainviewTokenMetadata
- OnchainviewBalanceOf
- Mint*
- Burn*
944 def test_admin(class_, kwargs, ledger_type, test_name="", modules=[]): 945 """Test `Admin` mixin 946 947 - non admin cannot set admin 948 - admin can set admin 949 - new admin can set admin 950 """ 951 test_name = "FA2_optional_interfaces_admin" + ledger_type + test_name 952 953 @sp.add_test() 954 def test(): 955 sc = sp.test_scenario(test_name, modules) 956 fa2 = class_(**kwargs) 957 sc += fa2 958 sc.h1(test_name) 959 sc.h2("Non admin cannot set admin") 960 fa2.set_administrator( 961 alice.address, _sender=alice, _valid=False, _exception="FA2_NOT_ADMIN" 962 ) 963 sc.verify(fa2.data.administrator == admin.address) 964 fa2.set_administrator(admin2.address, _sender=admin) 965 sc.verify(~(fa2.data.administrator == admin.address)) 966 sc.verify(fa2.data.administrator == admin2.address) 967 fa2.set_administrator(admin.address, _sender=admin2)
Test Admin
mixin
- non admin cannot set admin
- admin can set admin
- new admin can set admin
969 def test_mint(class_, kwargs, ledger_type, test_name="", modules=[]): 970 """Test `Mint*` mixin. 971 972 - `mint` fails with `FA2_NOT_ADMIN` for non-admin. 973 - `mint` adds the tokens. 974 - `mint` update the supply. 975 - `mint` works for existing tokens in fungible contracts. 976 """ 977 test_name = "FA2_mint_" + ledger_type + test_name 978 979 def mint_nft(sc, fa2): 980 sc.h2("Mint entrypoint") 981 # Non admin cannot mint a new NFT token. 982 sc.h3("NFT mint failure") 983 fa2.mint( 984 [sp.record(metadata=tok0_md, to_=alice.address)], 985 _sender=alice, 986 _valid=False, 987 _exception="FA2_NOT_ADMIN", 988 ) 989 990 sc.h3("Mint") 991 # Mint of a new NFT token. 992 fa2.mint( 993 [ 994 sp.record(metadata=tok0_md, to_=alice.address), 995 sp.record(metadata=tok1_md, to_=alice.address), 996 sp.record(metadata=tok2_md, to_=bob.address), 997 ], 998 _sender=admin, 999 ) 1000 1001 # Check that the balance is updated. 1002 sc.verify( 1003 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 1 1004 ) 1005 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 1006 sc.verify( 1007 _get_balance(fa2, sp.record(owner=alice.address, token_id=1)) == 1 1008 ) 1009 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=1)) == 0) 1010 sc.verify( 1011 _get_balance(fa2, sp.record(owner=alice.address, token_id=2)) == 0 1012 ) 1013 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=2)) == 1) 1014 1015 # Check that the supply is updated. 1016 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 1) 1017 sc.verify(_total_supply(fa2, sp.record(token_id=1)) == 1) 1018 sc.verify(_total_supply(fa2, sp.record(token_id=2)) == 1) 1019 1020 def mint_fungible(sc, fa2): 1021 sc.h2("Mint entrypoint") 1022 # Non admin cannot mint a new fungible token. 1023 sc.h3("Fungible mint failure") 1024 fa2.mint( 1025 [ 1026 sp.record( 1027 token=sp.variant.new(tok0_md), to_=alice.address, amount=1000 1028 ) 1029 ], 1030 _sender=alice, 1031 _valid=False, 1032 _exception="FA2_NOT_ADMIN", 1033 ) 1034 1035 sc.h3("Mint") 1036 # Mint of a new fungible token. 1037 fa2.mint( 1038 [ 1039 sp.record( 1040 token=sp.variant.new(tok0_md), to_=alice.address, amount=1000 1041 ) 1042 ], 1043 _sender=admin, 1044 ) 1045 1046 # Check ledger update. 1047 sc.verify( 1048 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 1000 1049 ) 1050 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 1051 # Check supply update. 1052 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 1000) 1053 1054 # Mint a new and existing token. 1055 fa2.mint( 1056 [ 1057 sp.record( 1058 token=sp.variant.new(tok1_md), to_=alice.address, amount=1000 1059 ), 1060 sp.record( 1061 token=sp.variant.existing(0), to_=alice.address, amount=1000 1062 ), 1063 sp.record( 1064 token=sp.variant.existing(1), to_=bob.address, amount=1000 1065 ), 1066 ], 1067 _sender=admin, 1068 ) 1069 1070 # Check ledger update. 1071 sc.verify( 1072 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 2000 1073 ) 1074 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 1075 sc.verify( 1076 _get_balance(fa2, sp.record(owner=alice.address, token_id=1)) == 1000 1077 ) 1078 sc.verify( 1079 _get_balance(fa2, sp.record(owner=bob.address, token_id=1)) == 1000 1080 ) 1081 # Check supply update. 1082 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 2000) 1083 sc.verify(_total_supply(fa2, sp.record(token_id=1)) == 2000) 1084 1085 def mint_single_asset(sc, fa2): 1086 sc.h2("Mint entrypoint") 1087 # Non admin cannot mint a new fungible token. 1088 sc.h3("Single asset mint failure") 1089 fa2.mint( 1090 [sp.record(to_=alice.address, amount=1000)], 1091 _sender=alice, 1092 _valid=False, 1093 _exception="FA2_NOT_ADMIN", 1094 ) 1095 1096 sc.h3("Mint") 1097 # Mint some tokens 1098 fa2.mint([sp.record(to_=alice.address, amount=1000)], _sender=admin) 1099 1100 # Check ledger update. 1101 sc.verify( 1102 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 1000 1103 ) 1104 sc.verify(_get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 0) 1105 # Check supply update. 1106 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 1000) 1107 1108 # multiple mint 1109 fa2.mint( 1110 [ 1111 sp.record(to_=alice.address, amount=1000), 1112 sp.record(to_=bob.address, amount=1000), 1113 sp.record(to_=bob.address, amount=1000), 1114 ], 1115 _sender=admin, 1116 ) 1117 1118 # Check ledger update. 1119 sc.verify( 1120 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 2000 1121 ) 1122 sc.verify( 1123 _get_balance(fa2, sp.record(owner=bob.address, token_id=0)) == 2000 1124 ) 1125 # Check supply update. 1126 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 4000) 1127 1128 @sp.add_test() 1129 def test(): 1130 sc = sp.test_scenario(test_name, modules) 1131 fa2 = class_(**kwargs) 1132 sc += fa2 1133 sc.h1(test_name) 1134 if ledger_type == "NFT": 1135 mint_nft(sc, fa2) 1136 elif ledger_type == "Fungible": 1137 mint_fungible(sc, fa2) 1138 elif ledger_type == "SingleAsset": 1139 mint_single_asset(sc, fa2) 1140 else: 1141 raise Exception( 1142 'fa2.ledger type must be "NFT", "Fungible" or "SingleAsset".' 1143 )
Test Mint*
mixin.
mint
fails withFA2_NOT_ADMIN
for non-admin.mint
adds the tokens.mint
update the supply.mint
works for existing tokens in fungible contracts.
1145 def test_burn( 1146 class_, 1147 kwargs, 1148 ledger_type, 1149 supports_transfer, 1150 supports_operator, 1151 modules=[], 1152 test_name="", 1153 ): 1154 """Test `Burn*` mixin. 1155 1156 - non operator cannot burn, it fails appropriately. 1157 - owner can burn. 1158 - burn fails with `FA2_INSUFFICIENT_BALANCE` when needed. 1159 - operator can burn if the policy allows it. 1160 """ 1161 test_name = "FA2_burn_" + ledger_type + test_name 1162 1163 @sp.add_test() 1164 def test(): 1165 amount = 1 if ledger_type == "NFT" else 20 1166 sc = sp.test_scenario(test_name, modules) 1167 fa2 = class_(**kwargs) 1168 sc += fa2 1169 sc.h1(test_name) 1170 sc.h2("Burn entrypoint") 1171 1172 # Check that non operator cannot burn others tokens. 1173 sc.h3("Cannot burn others tokens") 1174 exception = "FA2_NOT_OPERATOR" if supports_transfer else "FA2_TX_DENIED" 1175 fa2.burn( 1176 [sp.record(token_id=0, from_=alice.address, amount=1)], 1177 _sender=bob, 1178 _valid=False, 1179 _exception=exception, 1180 ) 1181 1182 # Not allowed transfers 1183 if not supports_transfer: 1184 fa2.burn( 1185 [sp.record(token_id=0, from_=alice.address, amount=amount)], 1186 _sender=alice, 1187 _valid=False, 1188 _exception="FA2_TX_DENIED", 1189 ) 1190 return 1191 1192 # Owner can burn. 1193 sc.h3("Owner burns his nft tokens") 1194 fa2.burn( 1195 [sp.record(token_id=0, from_=alice.address, amount=amount)], 1196 _sender=alice, 1197 ) 1198 1199 if ledger_type == "NFT": 1200 # Check that the contract storage is updated. 1201 sc.verify(~fa2.data.ledger.contains(0)) 1202 # Check that burning an nft removes token_metadata. 1203 sc.verify(~fa2.data.token_metadata.contains(0)) 1204 else: 1205 # Check ledger update. 1206 sc.verify( 1207 _get_balance(fa2, sp.record(owner=alice.address, token_id=0)) == 22 1208 ) 1209 # Check that burning doesn't remove token_metadata. 1210 sc.verify(fa2.data.token_metadata.contains(0)) 1211 # Check supply update. 1212 sc.verify(_total_supply(fa2, sp.record(token_id=0)) == 22) 1213 1214 # Check burn of FA2_INSUFFICIENT_BALANCE. 1215 sc.h3("Burn with insufficient balance") 1216 token_id = 0 if ledger_type == "SingleAsset" else 1 1217 fa2.burn( 1218 [sp.record(token_id=token_id, from_=alice.address, amount=43)], 1219 _sender=alice, 1220 _valid=False, 1221 _exception="FA2_INSUFFICIENT_BALANCE", 1222 ) 1223 1224 if supports_operator: 1225 # Add operator to test if he can burn on behalf of the owner. 1226 sc.h3("Operator can burn on behalf of the owner") 1227 operator_bob = sp.record( 1228 owner=alice.address, operator=bob.address, token_id=token_id 1229 ) 1230 fa2.update_operators( 1231 [sp.variant.add_operator(operator_bob)], _sender=alice 1232 ) 1233 # Operator can burn nft on behalf of the owner. 1234 fa2.burn( 1235 [sp.record(token_id=token_id, from_=alice.address, amount=0)], 1236 _sender=bob, 1237 )
Test Burn*
mixin.
- non operator cannot burn, it fails appropriately.
- owner can burn.
- burn fails with
FA2_INSUFFICIENT_BALANCE
when needed. - operator can burn if the policy allows it.
1239 def test_withdraw_mutez(class_, kwargs, ledger_type, test_name="", modules=[]): 1240 """Test of WithdrawMutez. 1241 1242 - non admin cannot withdraw mutez: FA2_NOT_ADMIN. 1243 - admin can withdraw mutez. 1244 """ 1245 test_name = "FA2_withdraw_mutez" + ledger_type + test_name 1246 1247 @sp.add_test() 1248 def test(): 1249 sc = sp.test_scenario(test_name, modules) 1250 sc.add_module(helpers) 1251 fa2 = class_(**kwargs) 1252 sc += fa2 1253 sc.h1(test_name) 1254 sc.h2("Mutez receiver contract") 1255 1256 wallet = helpers.Wallet() 1257 sc += wallet 1258 1259 # Non admin cannot withdraw mutez. 1260 sc.h2("Non admin cannot withdraw_mutez") 1261 fa2.withdraw_mutez( 1262 destination=wallet.address, 1263 amount=sp.tez(10), 1264 _sender=alice, 1265 _amount=sp.tez(42), 1266 _valid=False, 1267 _exception="FA2_NOT_ADMIN", 1268 ) 1269 1270 # Admin can withdraw mutez. 1271 sc.h3("Admin withdraw_mutez") 1272 fa2.withdraw_mutez( 1273 destination=wallet.address, 1274 amount=sp.tez(10), 1275 _sender=admin, 1276 _amount=sp.tez(42), 1277 ) 1278 1279 # Check that the mutez has been transferred. 1280 sc.verify(fa2.balance == sp.tez(32)) 1281 sc.verify(wallet.balance == sp.tez(10))
Test of WithdrawMutez.
- non admin cannot withdraw mutez: FA2_NOT_ADMIN.
- admin can withdraw mutez.
1283 def test_change_metadata(class_, kwargs, ledger_type, test_name="", modules=[]): 1284 """Test of ChangeMetadata. 1285 1286 - non admin cannot set metadata 1287 - `set_metadata` works as expected 1288 """ 1289 test_name = "FA2_change_metadata" + ledger_type + test_name 1290 1291 @sp.add_test() 1292 def test(): 1293 sc = sp.test_scenario(test_name, modules) 1294 fa2 = class_(**kwargs) 1295 sc += fa2 1296 sc.h1(test_name) 1297 sc.h2("Change metadata") 1298 sc.h3("Non admin cannot set metadata") 1299 fa2.set_metadata( 1300 sp.scenario_utils.metadata_of_url("http://example.com"), 1301 _sender=alice, 1302 _valid=False, 1303 _exception="FA2_NOT_ADMIN", 1304 ) 1305 1306 sc.h3("Admin set metadata") 1307 fa2.set_metadata( 1308 sp.scenario_utils.metadata_of_url("http://example.com"), _sender=admin 1309 ) 1310 1311 # Check that the metadata has been updated. 1312 sc.verify_equal( 1313 fa2.data.metadata[""], 1314 sp.scenario_utils.metadata_of_url("http://example.com")[""], 1315 )
Test of ChangeMetadata.
- non admin cannot set metadata
set_metadata
works as expected
1317 def test_get_balance_of(class_, kwargs, ledger_type, test_name="", modules=[]): 1318 """Test of `OnchainviewBalanceOf` 1319 1320 - `get_balance_of` doesn't deduplicate nor reorder on nft. 1321 - `get_balance_of` doesn't deduplicate nor reorder on fungible. 1322 - `get_balance_of` fails with `FA2_TOKEN_UNDEFINED` when needed on nft. 1323 - `get_balance_of` fails with `FA2_TOKEN_UNDEFINED` when needed on fungible. 1324 """ 1325 test_name = "FA2_get_balance_of" + ledger_type + test_name 1326 1327 @sp.add_test() 1328 def test(): 1329 sc = sp.test_scenario(test_name, modules) 1330 fa2 = class_(**kwargs) 1331 sc += fa2 1332 sc.h1(test_name) 1333 1334 # get_balance_of on fungible 1335 # We deliberately give multiple identical params to check for 1336 # non-deduplication and non-reordering. 1337 1338 ICO = 1 if ledger_type == "NFT" else 42 # Initial coin offering. 1339 last_token_id = 0 if ledger_type == "SingleAsset" else 2 1340 1341 requests = [ 1342 sp.record(owner=alice.address, token_id=0), 1343 sp.record(owner=alice.address, token_id=0), 1344 sp.record(owner=bob.address, token_id=0), 1345 sp.record(owner=alice.address, token_id=last_token_id), 1346 ] 1347 expected = [ 1348 sp.record( 1349 balance=ICO, request=sp.record(owner=alice.address, token_id=0) 1350 ), 1351 sp.record( 1352 balance=ICO, request=sp.record(owner=alice.address, token_id=0) 1353 ), 1354 sp.record(balance=0, request=sp.record(owner=bob.address, token_id=0)), 1355 sp.record( 1356 balance=ICO, 1357 request=sp.record(owner=alice.address, token_id=last_token_id), 1358 ), 1359 ] 1360 1361 sc.verify_equal(_get_balance_of(fa2, requests), expected) 1362 1363 # Check that on-chain view fails on undefined tokens. 1364 sc.verify( 1365 sp.catch_exception( 1366 _get_balance_of(fa2, [sp.record(owner=alice.address, token_id=5)]) 1367 ) 1368 == sp.Some("FA2_TOKEN_UNDEFINED") 1369 )
Test of OnchainviewBalanceOf
get_balance_of
doesn't deduplicate nor reorder on nft.get_balance_of
doesn't deduplicate nor reorder on fungible.get_balance_of
fails withFA2_TOKEN_UNDEFINED
when needed on nft.get_balance_of
fails withFA2_TOKEN_UNDEFINED
when needed on fungible.
1371 def test_offchain_token_metadata( 1372 class_, kwargs, ledger_type, test_name="", modules=[] 1373 ): 1374 """Test `OffchainviewTokenMetadata`. 1375 1376 Tests: 1377 1378 - `token_metadata` works as expected on nft and fungible. 1379 """ 1380 test_name = "FA2_offchain_token_metadata" + ledger_type + test_name 1381 1382 @sp.add_test() 1383 def test(): 1384 sc = sp.test_scenario(test_name, modules) 1385 fa2 = class_(**kwargs) 1386 sc += fa2 1387 sc.h1(test_name) 1388 sc.verify_equal( 1389 fa2.token_metadata(0), sp.record(token_id=0, token_info=tok0_md) 1390 )
Test OffchainviewTokenMetadata
.
Tests:
token_metadata
works as expected on nft and fungible.
1392 def test_pause(class_, kwargs, ledger_type, test_name="", modules=[]): 1393 """Test the `Pause` policy decorator. 1394 1395 - transfer works without pause 1396 - transfer update_operators without pause 1397 - non admin cannot set_pause 1398 - admin can set pause 1399 - transfer fails with ('FA2_TX_DENIED', 'FA2_PAUSED') when paused. 1400 - update_operators fails with 1401 ('FA2_OPERATORS_UNSUPPORTED', 'FA2_PAUSED') when paused. 1402 """ 1403 test_name = "FA2_pause_" + ledger_type + test_name 1404 1405 @sp.add_test() 1406 def test(): 1407 sc = sp.test_scenario(test_name, modules) 1408 fa2 = class_(**kwargs) 1409 sc.h1(test_name) 1410 1411 sc.h2("Accounts") 1412 sc.show([admin, alice, bob]) 1413 sc.h2("FA2 Contract") 1414 sc += fa2 1415 1416 sc.h2("Transfer without pause") 1417 fa2.transfer( 1418 [ 1419 sp.record( 1420 from_=alice.address, 1421 txs=[sp.record(to_=alice.address, amount=0, token_id=0)], 1422 ), 1423 ], 1424 _sender=alice, 1425 ) 1426 1427 sc.h2("Update_operator without pause") 1428 fa2.update_operators( 1429 [ 1430 sp.variant( 1431 "add_operator", 1432 sp.record( 1433 owner=alice.address, operator=alice.address, token_id=0 1434 ), 1435 ), 1436 sp.variant( 1437 "remove_operator", 1438 sp.record( 1439 owner=alice.address, operator=alice.address, token_id=0 1440 ), 1441 ), 1442 ], 1443 _sender=alice, 1444 ) 1445 1446 sc.h2("Pause entrypoint") 1447 sc.h3("Non admin cannot set pause") 1448 fa2.set_pause(True, _sender=alice, _valid=False, _exception="FA2_NOT_ADMIN") 1449 1450 sc.h3("Admin set pause") 1451 fa2.set_pause(True, _sender=admin) 1452 1453 sc.h2("Transfer fails with pause") 1454 fa2.transfer( 1455 [ 1456 sp.record( 1457 from_=alice.address, 1458 txs=[sp.record(to_=alice.address, amount=0, token_id=0)], 1459 ), 1460 ], 1461 _sender=alice, 1462 _valid=False, 1463 _exception=("FA2_TX_DENIED", "FA2_PAUSED"), 1464 ) 1465 1466 sc.h2("Update_operator fails with pause") 1467 fa2.update_operators( 1468 [ 1469 sp.variant( 1470 "add_operator", 1471 sp.record( 1472 owner=alice.address, operator=alice.address, token_id=0 1473 ), 1474 ), 1475 sp.variant( 1476 "remove_operator", 1477 sp.record( 1478 owner=alice.address, operator=alice.address, token_id=0 1479 ), 1480 ), 1481 ], 1482 _sender=alice, 1483 _valid=False, 1484 _exception=("FA2_OPERATORS_UNSUPPORTED", "FA2_PAUSED"), 1485 )
Test the Pause
policy decorator.
- transfer works without pause
- transfer update_operators without pause
- non admin cannot set_pause
- admin can set pause
- transfer fails with ('FA2_TX_DENIED', 'FA2_PAUSED') when paused.
- update_operators fails with ('FA2_OPERATORS_UNSUPPORTED', 'FA2_PAUSED') when paused.