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            )
admin = <smartpy.TestAccount object>
admin2 = <smartpy.TestAccount object>
alice = <smartpy.TestAccount object>
bob = <smartpy.TestAccount object>
charlie = <smartpy.TestAccount object>
def make_metadata(symbol, name, decimals):
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.

tok0_md = (("templates/fa2_lib_testing.py" 12) map ((("test-env/lib/python3.10/site-packages/pdoc/doc.py" 1110) literal (string "decimals")) (("templates/fa2_lib_testing.py" 14) literal (bytes "0x31"))) ((("test-env/lib/python3.10/site-packages/pdoc/doc.py" 1110) literal (string "name")) (("templates/fa2_lib_testing.py" 15) literal (bytes "0x546f6b656e205a65726f"))) ((("test-env/lib/python3.10/site-packages/pdoc/doc.py" 1110) literal (string "symbol")) (("templates/fa2_lib_testing.py" 16) literal (bytes "0x546f6b30"))))
tok1_md = (("templates/fa2_lib_testing.py" 12) map ((("test-env/lib/python3.10/site-packages/pdoc/doc.py" 1110) literal (string "decimals")) (("templates/fa2_lib_testing.py" 14) literal (bytes "0x31"))) ((("test-env/lib/python3.10/site-packages/pdoc/doc.py" 1110) literal (string "name")) (("templates/fa2_lib_testing.py" 15) literal (bytes "0x546f6b656e204f6e65"))) ((("test-env/lib/python3.10/site-packages/pdoc/doc.py" 1110) literal (string "symbol")) (("templates/fa2_lib_testing.py" 16) literal (bytes "0x546f6b31"))))
tok2_md = (("templates/fa2_lib_testing.py" 12) map ((("test-env/lib/python3.10/site-packages/pdoc/doc.py" 1110) literal (string "decimals")) (("templates/fa2_lib_testing.py" 14) literal (bytes "0x31"))) ((("test-env/lib/python3.10/site-packages/pdoc/doc.py" 1110) literal (string "name")) (("templates/fa2_lib_testing.py" 15) literal (bytes "0x546f6b656e2054776f"))) ((("test-env/lib/python3.10/site-packages/pdoc/doc.py" 1110) literal (string "symbol")) (("templates/fa2_lib_testing.py" 16) literal (bytes "0x546f6b32"))))
def test_core_interfaces(class_, kwargs, ledger_type, test_name='', modules=[]):
 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
def test_transfer(class_, kwargs, ledger_type, test_name='', modules=[]):
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.

def test_balance_of(class_, kwargs, ledger_type, test_name='', modules=[]):
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 with FA2_TOKEN_UNDEFINED when token is undefined.
def test_no_transfer(class_, kwargs, ledger_type, test_name='', modules=[]):
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.
def test_owner_transfer(class_, kwargs, ledger_type, test_name='', modules=[]):
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.
def test_owner_or_operator_transfer(class_, kwargs, ledger_type, test_name='', modules=[]):
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.
class NS:
 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*
def test_admin(class_, kwargs, ledger_type, test_name='', modules=[]):
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
def test_mint(class_, kwargs, ledger_type, test_name='', modules=[]):
 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 with FA2_NOT_ADMIN for non-admin.
  • mint adds the tokens.
  • mint update the supply.
  • mint works for existing tokens in fungible contracts.
def test_burn( class_, kwargs, ledger_type, supports_transfer, supports_operator, modules=[], test_name=''):
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.
def test_withdraw_mutez(class_, kwargs, ledger_type, test_name='', modules=[]):
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.
def test_change_metadata(class_, kwargs, ledger_type, test_name='', modules=[]):
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
def test_get_balance_of(class_, kwargs, ledger_type, test_name='', modules=[]):
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 with FA2_TOKEN_UNDEFINED when needed on nft.
  • get_balance_of fails with FA2_TOKEN_UNDEFINED when needed on fungible.
def test_offchain_token_metadata(class_, kwargs, ledger_type, test_name='', modules=[]):
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.
def test_pause(class_, kwargs, ledger_type, test_name='', modules=[]):
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.