Monorepo for Aesthetic.Computer aesthetic.computer
at main 216 lines 7.6 kB view raw
1import smartpy as sp 2from smartpy.templates import fa2_lib as fa2 3 4# Main template for FA2 contracts 5main = fa2.main 6 7 8@sp.module 9def my_module(): 10 import main 11 12 # Order of inheritance: [Admin], [<policy>], <base class>, [<other mixins>]. 13 class MyNFTContract( 14 main.Admin, 15 main.Nft, 16 main.MintNft, 17 main.BurnNft, 18 main.OnchainviewBalanceOf, 19 ): 20 def __init__(self, admin_address, contract_metadata, ledger, token_metadata): 21 """Initializes the contract with administrative permissions and NFT functionalities. 22 The base class is required; all mixins are optional. 23 The initialization must follow this order: 24 25 - Other mixins such as OnchainviewBalanceOf, MintNFT, and BurnNFT 26 - Base class: NFT 27 - Transfer policy 28 - Admin 29 """ 30 31 # Initialize on-chain balance view 32 main.OnchainviewBalanceOf.__init__(self) 33 34 # Initialize the NFT-specific entrypoints 35 main.BurnNft.__init__(self) 36 main.MintNft.__init__(self) 37 38 # Initialize the NFT base class 39 main.Nft.__init__(self, contract_metadata, ledger, token_metadata) 40 41 # Initialize administrative permissions 42 main.Admin.__init__(self, admin_address) 43 44 45def _get_balance(fa2_contract, args): 46 """Utility function to call the contract's get_balance view to get an account's token balance.""" 47 return sp.View(fa2_contract, "get_balance")(args) 48 49 50def _total_supply(fa2_contract, args): 51 """Utility function to call the contract's total_supply view to get the total amount of tokens.""" 52 return sp.View(fa2_contract, "total_supply")(args) 53 54 55@sp.add_test() 56def test(): 57 # Create and configure the test scenario 58 # Import the types from the FA2 library, the library itself, and the contract module, in that order. 59 scenario = sp.test_scenario("fa2_lib_nft") 60 scenario.h1("FA2 NFT contract test") 61 62 # Define test accounts 63 admin = sp.test_account("Admin") 64 alice = sp.test_account("Alice") 65 bob = sp.test_account("Bob") 66 67 # Define initial token metadata and ownership 68 tok0_md = fa2.make_metadata(name="Token Zero", decimals=1, symbol="Tok0") 69 tok1_md = fa2.make_metadata(name="Token One", decimals=1, symbol="Tok1") 70 tok2_md = fa2.make_metadata(name="Token Two", decimals=1, symbol="Tok2") 71 token_metadata = [tok0_md, tok1_md, tok2_md] 72 ledger = {0: alice.address, 1: alice.address, 2: bob.address} 73 74 # Instantiate the FA2 NFT contract 75 contract = my_module.MyNFTContract( 76 admin.address, sp.big_map(), ledger, token_metadata 77 ) 78 79 # Build contract metadata content 80 contract_metadata = sp.create_tzip16_metadata( 81 name="My FA2 NFT contract", 82 description="This is an FA2 NFT contract using SmartPy.", 83 version="1.0.0", 84 license_name="CC-BY-SA", 85 license_details="Creative Commons Attribution Share Alike license 4.0 https://creativecommons.org/licenses/by/4.0/", 86 interfaces=["TZIP-012", "TZIP-016"], 87 authors=["SmartPy <https://smartpy.io/#contact>"], 88 homepage="https://smartpy.io/ide?template=fa2_lib_nft.py", 89 # Optionally, upload the source code to IPFS and add the URI here 90 source_uri=None, 91 offchain_views=contract.get_offchain_views(), 92 ) 93 94 # Add the info specific to FA2 permissions 95 contract_metadata["permissions"] = { 96 # The operator policy chosen: 97 # owner-or-operator-transfer is the default. 98 "operator": "owner-or-operator-transfer", 99 # Those two options should always have these values. 100 # It means that the contract doesn't use the hook mechanism. 101 "receiver": "owner-no-hook", 102 "sender": "owner-no-hook", 103 } 104 105 # You must upload the contract metadata to IPFS and get its URI. 106 # You can write the contract_metadata object to a JSON file with json.dumps() and upload it manually. 107 # You can also use sp.pin_on_ipfs() to upload the object via pinata.cloud and get the IPFS URI: 108 # metadata_uri = sp.pin_on_ipfs(contract_metadata, api_key=None, secret_key=None, name = "Metadata for my FA2 contract") 109 110 # This is a placeholder value. In production, replace it with your metadata URI. 111 metadata_uri = "ipfs://example" 112 113 # Create the metadata big map based on the IPFS URI 114 contract_metadata = sp.scenario_utils.metadata_of_url(metadata_uri) 115 116 # Update the scenario instance with the new metadata 117 contract.data.metadata = contract_metadata 118 119 # Originate the contract in the test scenario 120 scenario += contract 121 122 if scenario.simulation_mode() is sp.SimulationMode.MOCKUP: 123 scenario.p("mockups - fix transfer based testing") 124 return 125 126 # Run tests 127 128 scenario.h2("Verify the initial owners of the tokens") 129 scenario.verify( 130 _get_balance(contract, sp.record(owner=alice.address, token_id=0)) == 1 131 ) 132 scenario.verify( 133 _get_balance(contract, sp.record(owner=bob.address, token_id=0)) == 0 134 ) 135 scenario.verify( 136 _get_balance(contract, sp.record(owner=alice.address, token_id=1)) == 1 137 ) 138 scenario.verify( 139 _get_balance(contract, sp.record(owner=bob.address, token_id=1)) == 0 140 ) 141 scenario.verify( 142 _get_balance(contract, sp.record(owner=alice.address, token_id=2)) == 0 143 ) 144 scenario.verify( 145 _get_balance(contract, sp.record(owner=bob.address, token_id=2)) == 1 146 ) 147 148 # Verify the token supply 149 scenario.verify(_total_supply(contract, sp.record(token_id=0)) == 1) 150 scenario.verify(_total_supply(contract, sp.record(token_id=1)) == 1) 151 scenario.verify(_total_supply(contract, sp.record(token_id=2)) == 1) 152 153 scenario.h2("Transfer a token") 154 contract.transfer( 155 [ 156 sp.record( 157 from_=alice.address, 158 txs=[sp.record(to_=bob.address, amount=1, token_id=0)], 159 ), 160 ], 161 _sender=alice, 162 ) 163 # Verify the result 164 scenario.verify( 165 _get_balance(contract, sp.record(owner=alice.address, token_id=0)) == 0 166 ) 167 scenario.verify( 168 _get_balance(contract, sp.record(owner=bob.address, token_id=0)) == 1 169 ) 170 # Transfer it back 171 contract.transfer( 172 [ 173 sp.record( 174 from_=bob.address, 175 txs=[sp.record(to_=alice.address, amount=1, token_id=0)], 176 ), 177 ], 178 _sender=bob, 179 ) 180 181 scenario.h2("Mint a token") 182 nft3_md = fa2.make_metadata(name="Token Three", decimals=1, symbol="Tok3") 183 # Verify that only the admin can mint a token 184 contract.mint( 185 [ 186 sp.record(metadata=nft3_md, to_=bob.address), 187 ], 188 _sender=bob, 189 _valid=False, 190 ) 191 # Mint a token 192 contract.mint( 193 [ 194 sp.record(metadata=nft3_md, to_=bob.address), 195 ], 196 _sender=admin, 197 ) 198 # Verify the result 199 scenario.verify(_total_supply(contract, sp.record(token_id=3)) == 1) 200 scenario.verify( 201 _get_balance(contract, sp.record(owner=alice.address, token_id=3)) == 0 202 ) 203 scenario.verify( 204 _get_balance(contract, sp.record(owner=bob.address, token_id=3)) == 1 205 ) 206 207 scenario.h2("Burn a token") 208 # Verify that you can't burn someone else's token 209 contract.burn( 210 [sp.record(token_id=3, from_=bob.address, amount=1)], 211 _sender=alice, 212 _valid=False, 213 ) 214 215 # Verify that you can burn your own token 216 contract.burn([sp.record(token_id=3, from_=bob.address, amount=1)], _sender=bob)