├── .gitignore ├── .project ├── .pydevproject ├── README.md ├── output └── .gitignore └── python ├── contracts ├── barterContract.py ├── collaborationContract.py ├── collaborativeTextContract.py ├── doctorContract.py ├── extendedFa2Contract.py ├── fa2Contract.py ├── henReunionContract.py ├── lambdaFunctionUtilContract.py ├── managerContract.py ├── marketplaceContract.py ├── minterContract.py ├── multisig_metadata.json ├── multisignWalletContract.py ├── nonCustodialBarterContract.py ├── patientContract.py ├── pingPongContract.py ├── seedsContract.py └── simpleBarterContract.py ├── templates └── fa2Contract.py └── tests ├── barterContract_test.py ├── collaborationContract_test.py ├── doctorContract_test.py ├── extendedFa2Contract_test.py ├── fa2Contract_test.py ├── lambdaFunctionUtilContract_test.py ├── managerContract_test.py ├── marketplaceContract_test.py ├── minterContract_test.py ├── multisignWalletContract_test.py ├── nonCustodialBarterContract_test.py ├── patientContract_test.py ├── pingPongContract_test.py └── simpleBarterContract_test.py /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jagracar/tezos-smart-contracts/187ee06fbe97f1c651aed29c7417821280113ac0/.gitignore -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | tezos-smart-contracts 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Default 5 | 6 | python interpreter 7 | 8 | 9 | /home/jgracia/smartpy-cli 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UPDATE 2 | 3 | The development of the smart contracts in this repository is now taking place at the Teia Community smart contracts repository: 4 | 5 | https://github.com/teia-community/teia-smart-contracts 6 | 7 | Please, go there for more updated source code and unit tests. 8 | 9 | 10 | # tezos-smart-contracts 11 | 12 | This repository contains some Tezos smart contracts that I write at the same 13 | time that I learn [SmartPy](https://smartpy.io). For that reason, be careful if 14 | you decide to use them for your own projects: they could be buggy and highly 15 | inefficient! 16 | 17 | ## SmartPy installation 18 | 19 | ```bash 20 | wget https://smartpy.io/cli/install.sh 21 | bash ./install.sh 22 | rm install.sh 23 | ``` 24 | 25 | ## Execute the tests 26 | 27 | ```bash 28 | cd ~/github/tezos-smart-contracts 29 | ~/smartpy-cli/SmartPy.sh test python/tests/managerContract_test.py output/tests/managerContract --html --purge 30 | ~/smartpy-cli/SmartPy.sh test python/tests/patientContract_test.py output/tests/patientContract --html --purge 31 | ~/smartpy-cli/SmartPy.sh test python/tests/doctorContract_test.py output/tests/doctorContract --html --purge 32 | ~/smartpy-cli/SmartPy.sh test python/tests/pingPongContract_test.py output/tests/pingPongContract --html --purge 33 | ~/smartpy-cli/SmartPy.sh test python/tests/multisignWalletContract_test.py output/tests/multisignWalletContract --html --purge 34 | ~/smartpy-cli/SmartPy.sh test python/tests/barterContract_test.py output/tests/barterContract --html --purge 35 | ~/smartpy-cli/SmartPy.sh test python/tests/simpleBarterContract_test.py output/tests/simpleBarterContract --html --purge 36 | ~/smartpy-cli/SmartPy.sh test python/tests/nonCustodialBarterContract_test.py output/tests/nonCustodialBarterContract --html --purge 37 | ~/smartpy-cli/SmartPy.sh test python/tests/lambdaFunctionUtilContract_test.py output/tests/lambdaFunctionUtilsContract --html --purge 38 | ~/smartpy-cli/SmartPy.sh test python/tests/fa2Contract_test.py output/tests/fa2Contract --html --purge 39 | ~/smartpy-cli/SmartPy.sh test python/tests/extendedFa2Contract_test.py output/tests/extendedFa2Contract --html --purge 40 | ~/smartpy-cli/SmartPy.sh test python/tests/minterContract_test.py output/tests/minterContract --html --purge 41 | ~/smartpy-cli/SmartPy.sh test python/tests/marketplaceContract_test.py output/tests/marketplaceContract --html --purge 42 | ~/smartpy-cli/SmartPy.sh test python/tests/collaborationContract_test.py output/tests/collaborationContract --html --purge 43 | ``` 44 | 45 | ## Compile the contracts 46 | 47 | ```bash 48 | cd ~/github/tezos-smart-contracts 49 | ~/smartpy-cli/SmartPy.sh compile python/contracts/managerContract.py output/contracts/managerContract --html --purge 50 | ~/smartpy-cli/SmartPy.sh compile python/contracts/patientContract.py output/contracts/patientContract --html --purge 51 | ~/smartpy-cli/SmartPy.sh compile python/contracts/doctorContract.py output/contracts/doctorContract --html --purge 52 | ~/smartpy-cli/SmartPy.sh compile python/contracts/pingPongContract.py output/contracts/pingPongContract --html --purge 53 | ~/smartpy-cli/SmartPy.sh compile python/contracts/multisignWalletContract.py output/contracts/multisignWalletContract --html --purge 54 | ~/smartpy-cli/SmartPy.sh compile python/contracts/barterContract.py output/contracts/barterContract --html --purge 55 | ~/smartpy-cli/SmartPy.sh compile python/contracts/simpleBarterContract.py output/contracts/simpleBarterContract --html --purge 56 | ~/smartpy-cli/SmartPy.sh compile python/contracts/nonCustodialBarterContract.py output/contracts/nonCustodialBarterContract --html --purge 57 | ~/smartpy-cli/SmartPy.sh compile python/contracts/lambdaFunctionUtilContract.py output/contracts/lambdaFunctionUtilContract --html --purge 58 | ~/smartpy-cli/SmartPy.sh compile python/contracts/fa2Contract.py output/contracts/fa2Contract --html --purge 59 | ~/smartpy-cli/SmartPy.sh compile python/contracts/extendedFa2Contract.py output/contracts/extendedFa2Contract --html --purge 60 | ~/smartpy-cli/SmartPy.sh compile python/contracts/minterContract.py output/contracts/minterContract --html --purge 61 | ~/smartpy-cli/SmartPy.sh compile python/contracts/marketplaceContract.py output/contracts/marketplaceContract --html --purge 62 | ~/smartpy-cli/SmartPy.sh compile python/contracts/collaborationContract.py output/contracts/collaborationContract --html --purge 63 | ``` 64 | -------------------------------------------------------------------------------- /output/.gitignore: -------------------------------------------------------------------------------- 1 | tests 2 | contracts 3 | -------------------------------------------------------------------------------- /python/contracts/barterContract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | 4 | class BarterContract(sp.Contract): 5 | """This contract implements a simple barter contract where users can trade 6 | FA2 tokens and tez for other FA2 tokens. 7 | 8 | """ 9 | 10 | TOKEN_TYPE = sp.TRecord( 11 | # The FA2 token contract address 12 | fa2=sp.TAddress, 13 | # The FA2 token id 14 | id=sp.TNat, 15 | # The number of editions to trade 16 | amount=sp.TNat).layout(("fa2", ("id", "amount"))) 17 | 18 | TRADE_PROPOSAL_TYPE = sp.TRecord( 19 | # The first user involved in the trade 20 | user1=sp.TAddress, 21 | # The second user involved in the trade 22 | user2=sp.TAddress, 23 | # The first user mutez to trade 24 | mutez_amount=sp.TMutez, 25 | # The first user tokens to trade 26 | tokens1=sp.TList(TOKEN_TYPE), 27 | # The second user tokens to trade 28 | tokens2=sp.TList(TOKEN_TYPE)).layout( 29 | ("user1", ("user2", ("mutez_amount", ("tokens1", "tokens2"))))) 30 | 31 | def __init__(self, manager, allowed_fa2s): 32 | """Initializes the contract. 33 | 34 | """ 35 | # Define the contract storage data types for clarity 36 | self.init_type(sp.TRecord( 37 | manager=sp.TAddress, 38 | allowed_fa2s=sp.TBigMap(sp.TAddress, sp.TBool), 39 | trades=sp.TBigMap(sp.TNat, sp.TRecord( 40 | user1_accepted=sp.TBool, 41 | user2_accepted=sp.TBool, 42 | executed=sp.TBool, 43 | proposal=BarterContract.TRADE_PROPOSAL_TYPE)), 44 | counter=sp.TNat)) 45 | 46 | # Initialize the contract storage 47 | self.init( 48 | manager=manager, 49 | allowed_fa2s=allowed_fa2s, 50 | trades=sp.big_map(), 51 | counter=0) 52 | 53 | def check_is_manager(self): 54 | """Checks that the address that called the entry point is the contract 55 | manager. 56 | 57 | """ 58 | sp.verify(sp.sender == self.data.manager, 59 | message="This can only be executed by the contract manager") 60 | 61 | def check_is_user(self, trade_proposal): 62 | """Checks that the address that called the entry point is one of the 63 | users involved in the trade proposal. 64 | 65 | """ 66 | sp.verify((sp.sender == trade_proposal.user1) | (sp.sender == trade_proposal.user2), 67 | message="This can only be executed by one of the trade users") 68 | 69 | def check_no_tez_transfer(self): 70 | """Checks that no tez were transferred in the operation. 71 | 72 | """ 73 | sp.verify(sp.amount == sp.tez(0), 74 | message="The operation does not need tez transfers") 75 | 76 | def check_trade_not_executed(self, trade_id): 77 | """Checks that the trade id corresponds to an existing trade that has 78 | not been executed. 79 | 80 | """ 81 | # Check that the trade id is present in the trades big map 82 | sp.verify(self.data.trades.contains(trade_id), 83 | message="The provided trade id doesn't exist") 84 | 85 | # Check that the trade was not executed before 86 | sp.verify(~self.data.trades[trade_id].executed, 87 | message="The trade was executed before") 88 | 89 | @sp.entry_point 90 | def propose_trade(self, trade_proposal): 91 | """Proposes a trade between two users. 92 | 93 | """ 94 | # Define the input parameter data type 95 | sp.set_type(trade_proposal, BarterContract.TRADE_PROPOSAL_TYPE) 96 | 97 | # Check that the trade proposal comes from one of the users 98 | self.check_is_user(trade_proposal) 99 | 100 | # Check that the two involved users are not the same wallet 101 | sp.verify(trade_proposal.user1 != trade_proposal.user2, 102 | message="The users involved in the trade need to be different") 103 | 104 | # Check that no tez have been transferred 105 | self.check_no_tez_transfer() 106 | 107 | # Loop over the first user token list 108 | sp.for token in trade_proposal.tokens1: 109 | # Check that the token is one of the allowed tokens to trade 110 | sp.verify(self.data.allowed_fa2s.get(token.fa2, default_value=False), 111 | message="This token type cannot be traded") 112 | 113 | # Check that at least one edition will be traded 114 | sp.verify(token.amount >= 0, 115 | message="At least one token edition needs to be traded") 116 | 117 | # Loop over the second user token list 118 | sp.for token in trade_proposal.tokens2: 119 | # Check that the token is one of the allowed tokens to trade 120 | sp.verify(self.data.allowed_fa2s.get(token.fa2, default_value=False), 121 | message="This token type cannot be traded") 122 | 123 | # Check that at least one edition will be traded 124 | sp.verify(token.amount >= 0, 125 | message="At least one token edition needs to be traded") 126 | 127 | # Update the trades bigmap with the new trade information 128 | self.data.trades[self.data.counter] = sp.record( 129 | user1_accepted=False, 130 | user2_accepted=False, 131 | executed=False, 132 | proposal=trade_proposal) 133 | 134 | # Increase the trades counter 135 | self.data.counter += 1 136 | 137 | @sp.entry_point 138 | def accept_trade(self, trade_id): 139 | """Accepts a trade. 140 | 141 | """ 142 | # Define the input parameter data type 143 | sp.set_type(trade_id, sp.TNat) 144 | 145 | # Check that the trade was not executed before 146 | self.check_trade_not_executed(trade_id) 147 | 148 | # Check that the sender is one of the trade users 149 | trade = self.data.trades[trade_id] 150 | self.check_is_user(trade.proposal) 151 | 152 | # Transfer the tez and tokens to the barter account 153 | sp.if sp.sender == trade.proposal.user1: 154 | # Check that the user didn't accept the trade before 155 | sp.verify(~trade.user1_accepted, 156 | message="The trade is already accepted") 157 | 158 | # Accept the trade 159 | trade.user1_accepted = True 160 | 161 | # Check that the sent tez coincide with what was specified in the 162 | # trade proposal 163 | sp.verify(sp.amount == trade.proposal.mutez_amount, 164 | message="The sent tez amount does not coincide trade proposal amount") 165 | 166 | # Transfer all the editions to the barter account 167 | sp.for token in trade.proposal.tokens1: 168 | self.fa2_transfer( 169 | fa2=token.fa2, 170 | from_=sp.sender, 171 | to_=sp.self_address, 172 | token_id=token.id, 173 | token_amount=token.amount) 174 | sp.else: 175 | # Check that the user didn't accept the trade before 176 | sp.verify(~trade.user2_accepted, 177 | message="The trade is already accepted") 178 | 179 | # Accept the trade 180 | trade.user2_accepted = True 181 | 182 | # Check that the user didn't transfer any tez 183 | self.check_no_tez_transfer() 184 | 185 | # Transfer all the editions to the barter account 186 | sp.for token in trade.proposal.tokens2: 187 | self.fa2_transfer( 188 | fa2=token.fa2, 189 | from_=sp.sender, 190 | to_=sp.self_address, 191 | token_id=token.id, 192 | token_amount=token.amount) 193 | 194 | @sp.entry_point 195 | def cancel_trade(self, trade_id): 196 | """Cancels an already accepted trade. 197 | 198 | """ 199 | # Define the input parameter data type 200 | sp.set_type(trade_id, sp.TNat) 201 | 202 | # Check that no tez have been transferred 203 | self.check_no_tez_transfer() 204 | 205 | # Check that the trade was not executed before 206 | self.check_trade_not_executed(trade_id) 207 | 208 | # Check that the sender is one of the trade users 209 | trade = self.data.trades[trade_id] 210 | self.check_is_user(trade.proposal) 211 | 212 | # Transfer the tez and tokens to the user adddress 213 | sp.if sp.sender == trade.proposal.user1: 214 | # Check that the user accepted the trade before 215 | sp.verify(trade.user1_accepted, 216 | message="The trade was not accepted before") 217 | 218 | # Change the status to not accepted 219 | trade.user1_accepted = False 220 | 221 | # Transfer the tez to the user 222 | sp.if trade.proposal.mutez_amount != sp.mutez(0): 223 | sp.send(sp.sender, trade.proposal.mutez_amount) 224 | 225 | # Return all the editions to the user account 226 | sp.for token in trade.proposal.tokens1: 227 | self.fa2_transfer( 228 | fa2=token.fa2, 229 | from_=sp.self_address, 230 | to_=sp.sender, 231 | token_id=token.id, 232 | token_amount=token.amount) 233 | sp.else: 234 | # Check that the user accepted the trade before 235 | sp.verify(trade.user2_accepted, 236 | message="The trade was not accepted before") 237 | 238 | # Change the status to not accepted 239 | trade.user2_accepted = False 240 | 241 | # Return all the editions to the user account 242 | sp.for token in trade.proposal.tokens2: 243 | self.fa2_transfer( 244 | fa2=token.fa2, 245 | from_=sp.self_address, 246 | to_=sp.sender, 247 | token_id=token.id, 248 | token_amount=token.amount) 249 | 250 | @sp.entry_point 251 | def execute_trade(self, trade_id): 252 | """Executes a trade. 253 | 254 | """ 255 | # Define the input parameter data type 256 | sp.set_type(trade_id, sp.TNat) 257 | 258 | # Check that no tez have been transferred 259 | self.check_no_tez_transfer() 260 | 261 | # Check that the trade was not executed before 262 | self.check_trade_not_executed(trade_id) 263 | 264 | # Check that the sender is one of the trade users 265 | trade = self.data.trades[trade_id] 266 | self.check_is_user(trade.proposal) 267 | 268 | # Check that the two users accepted the trade 269 | sp.verify(trade.user1_accepted & trade.user2_accepted, 270 | message="One of the users didn't accept the trade") 271 | 272 | # Set the trade as executed 273 | trade.executed = True 274 | 275 | # Transfer the tez to the second user 276 | sp.if trade.proposal.mutez_amount != sp.mutez(0): 277 | sp.send(trade.proposal.user2, trade.proposal.mutez_amount) 278 | 279 | # Transfer the first user tokens to the second user 280 | sp.for token in trade.proposal.tokens1: 281 | self.fa2_transfer( 282 | fa2=token.fa2, 283 | from_=sp.self_address, 284 | to_=trade.proposal.user2, 285 | token_id=token.id, 286 | token_amount=token.amount) 287 | 288 | # Transfer the second user tokens to the first user 289 | sp.for token in trade.proposal.tokens2: 290 | self.fa2_transfer( 291 | fa2=token.fa2, 292 | from_=sp.self_address, 293 | to_=trade.proposal.user1, 294 | token_id=token.id, 295 | token_amount=token.amount) 296 | 297 | @sp.entry_point 298 | def update_manager(self, manager): 299 | """Updates the barter manager address. 300 | 301 | """ 302 | # Define the input parameter data type 303 | sp.set_type(manager, sp.TAddress) 304 | 305 | # Check that the manager executed the entry point 306 | self.check_is_manager() 307 | 308 | # Check that no tez have been transferred 309 | self.check_no_tez_transfer() 310 | 311 | # Set the new manager address 312 | self.data.manager = manager 313 | 314 | @sp.entry_point 315 | def add_fa2(self, fa2): 316 | """Adds a new FA2 token address to the list of tradable tokens. 317 | 318 | """ 319 | # Define the input parameter data type 320 | sp.set_type(fa2, sp.TAddress) 321 | 322 | # Check that the manager executed the entry point 323 | self.check_is_manager() 324 | 325 | # Check that no tez have been transferred 326 | self.check_no_tez_transfer() 327 | 328 | # Add the new FA2 token 329 | self.data.allowed_fa2s[fa2] = True 330 | 331 | @sp.entry_point 332 | def remove_fa2(self, fa2): 333 | """Removes one of the tradable FA2 token address. 334 | 335 | """ 336 | # Define the input parameter data type 337 | sp.set_type(fa2, sp.TAddress) 338 | 339 | # Check that the manager executed the entry point 340 | self.check_is_manager() 341 | 342 | # Check that no tez have been transferred 343 | self.check_no_tez_transfer() 344 | 345 | # Dissable the FA2 token address 346 | self.data.allowed_fa2s[fa2] = False 347 | 348 | def fa2_transfer(self, fa2, from_, to_, token_id, token_amount): 349 | """Transfers a number of editions of a FA2 token between two addresses. 350 | 351 | """ 352 | # Get a handle to the FA2 token transfer entry point 353 | c = sp.contract( 354 | t=sp.TList(sp.TRecord( 355 | from_=sp.TAddress, 356 | txs=sp.TList(sp.TRecord( 357 | to_=sp.TAddress, 358 | token_id=sp.TNat, 359 | amount=sp.TNat).layout(("to_", ("token_id", "amount")))))), 360 | address=fa2, 361 | entry_point="transfer").open_some() 362 | 363 | # Transfer the FA2 token editions to the new address 364 | sp.transfer( 365 | arg=sp.list([sp.record( 366 | from_=from_, 367 | txs=sp.list([sp.record( 368 | to_=to_, 369 | token_id=token_id, 370 | amount=token_amount)]))]), 371 | amount=sp.mutez(0), 372 | destination=c) 373 | 374 | 375 | # Add a compilation target initialized to a test account and the OBJKT FA2 contract 376 | sp.add_compilation_target("barter", BarterContract( 377 | manager=sp.address("tz1gnL9CeM5h5kRzWZztFYLypCNnVQZjndBN"), 378 | allowed_fa2s=sp.big_map({sp.address("KT1RJ6PbjHpwc3M5rw5s2Nbmefwbuwbdxton"): True}))) 379 | -------------------------------------------------------------------------------- /python/contracts/collaborationContract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | 4 | class CollaborationContract(sp.Contract): 5 | """A basic artists collaboration contract. 6 | 7 | """ 8 | 9 | PROPOSAL_TYPE = sp.TRecord( 10 | # Flag to indicate if the proposal has been already executed 11 | executed=sp.TBool, 12 | # The number of collaborator approvals 13 | approvals=sp.TNat, 14 | # The proposal lambda function id in the lambda provider contract 15 | lambda_id=sp.TNat, 16 | # The proposal lambda function parameters 17 | parameters=sp.TBytes).layout( 18 | ("executed", ("approvals", ("lambda_id", "parameters")))) 19 | 20 | def __init__(self): 21 | """Initializes the contract. 22 | 23 | """ 24 | # Define the contract storage data types for clarity 25 | self.init_type(sp.TRecord( 26 | # The contract metadata 27 | metadata=sp.TBigMap(sp.TString, sp.TBytes), 28 | # The collaborators and their share in the collaboration 29 | collaborators=sp.TMap(sp.TAddress, sp.TNat), 30 | # The lambda provider contract address 31 | lambda_provider=sp.TAddress, 32 | # The collaboration proposals 33 | proposals=sp.TBigMap(sp.TNat, CollaborationContract.PROPOSAL_TYPE), 34 | # The collaborators proposal approvals 35 | approvals=sp.TBigMap(sp.TPair(sp.TNat, sp.TAddress), sp.TBool), 36 | # The proposals bigmap counter 37 | counter=sp.TNat)) 38 | 39 | def check_is_collaborator(self): 40 | """Checks that the address that called the entry point is one of the 41 | collaboration members. 42 | 43 | """ 44 | sp.verify(self.data.collaborators.contains(sp.sender), 45 | message="COLLAB_NOT_COLLABORATOR") 46 | 47 | def check_proposal_is_valid(self, proposal_id): 48 | """Checks that the proposal_id is from a valid proposal. 49 | 50 | """ 51 | # Check that the proposal id is present in the proposals big map 52 | sp.verify(self.data.proposals.contains(proposal_id), 53 | message="COLLAB_INEXISTENT_PROPOSAL") 54 | 55 | # Check that the proposal has not been executed 56 | sp.verify(~self.data.proposals[proposal_id].executed, 57 | message="COLLAB_EXECUTED_PROPOSAL") 58 | 59 | @sp.entry_point 60 | def default(self, unit): 61 | """Default entrypoint that allows receiving tez transfers in the same 62 | way as one would do with a normal tz wallet. 63 | 64 | """ 65 | # Define the input parameter data type 66 | sp.set_type(unit, sp.TUnit) 67 | 68 | # Do nothing, just receive tez 69 | pass 70 | 71 | @sp.entry_point 72 | def transfer_funds(self, unit): 73 | """Transfers all the existing funds to the collaborators. 74 | 75 | """ 76 | # Define the input parameter data type 77 | sp.set_type(unit, sp.TUnit) 78 | 79 | # Check that one of the collaborators executed the entry point 80 | self.check_is_collaborator() 81 | 82 | # Distribute the funds 83 | transfer_amount = sp.local("transfer_amount", sp.mutez(0)) 84 | transferred_amount = sp.local("transferred_amount", sp.mutez(0)) 85 | counter = sp.local("counter", sp.len(self.data.collaborators)) 86 | 87 | with sp.for_("collaborator", self.data.collaborators.items()) as collaborator: 88 | # Calculate the amount to transfer to the collaborator 89 | with sp.if_(counter.value > 1): 90 | transfer_amount.value = sp.split_tokens( 91 | sp.balance, collaborator.value, 1000) 92 | with sp.else_(): 93 | transfer_amount.value = sp.balance - transferred_amount.value 94 | 95 | # Transfer the mutez to the collaborator 96 | sp.send(collaborator.key, transfer_amount.value) 97 | 98 | # Update the counters 99 | transferred_amount.value += transfer_amount.value 100 | counter.value = sp.as_nat(counter.value - 1) 101 | 102 | @sp.entry_point 103 | def add_proposal(self, params): 104 | """Adds a new proposal to the proposals big map. 105 | 106 | """ 107 | # Define the input parameter data type 108 | sp.set_type(params, sp.TRecord( 109 | lambda_id=sp.TNat, 110 | parameters=sp.TBytes).layout(("lambda_id", "parameters"))) 111 | 112 | # Check that one of the collaborators executed the entry point 113 | self.check_is_collaborator() 114 | 115 | # Check that the lambda function exists in the lambda provider contract 116 | sp.verify( 117 | sp.view( 118 | name="has_lambda", 119 | address=self.data.lambda_provider, 120 | param=params.lambda_id, 121 | t=sp.TBool).open_some(), 122 | message="COLLAB_INEXISTENT_LAMBDA") 123 | 124 | # Update the proposals bigmap with the new proposal information 125 | self.data.proposals[self.data.counter] = sp.record( 126 | executed=False, 127 | approvals=1, 128 | lambda_id=params.lambda_id, 129 | parameters=params.parameters) 130 | 131 | # Assume that the collaborator approves their own proposal 132 | self.data.approvals[(self.data.counter, sp.sender)] = True 133 | 134 | # Increase the proposals counter 135 | self.data.counter += 1 136 | 137 | @sp.entry_point 138 | def approve(self, params): 139 | """Approves or not a collaboration proposal. 140 | 141 | """ 142 | # Define the input parameter data type 143 | sp.set_type(params, sp.TRecord( 144 | proposal_id=sp.TNat, 145 | approval=sp.TBool).layout(("proposal_id", "approval"))) 146 | 147 | # Check that one of the collaborators executed the entry point 148 | self.check_is_collaborator() 149 | 150 | # Check that is a valid proposal 151 | self.check_proposal_is_valid(params.proposal_id) 152 | 153 | # Check if the collaborator approved the proposal before and remove 154 | # their approval from the proposal approvals counter 155 | approval_key = sp.pair(params.proposal_id, sp.sender) 156 | proposal = self.data.proposals[params.proposal_id] 157 | 158 | with sp.if_(self.data.approvals.get(approval_key, default_value=False)): 159 | proposal.approvals = sp.as_nat(proposal.approvals - 1) 160 | 161 | # Add the approval to the proposal approvals counter if it's positive 162 | with sp.if_(params.approval): 163 | proposal.approvals += 1 164 | 165 | # Add or update the collaborator approval 166 | self.data.approvals[approval_key] = params.approval 167 | 168 | @sp.entry_point 169 | def execute_proposal(self, proposal_id): 170 | """Executes a given proposal. 171 | 172 | """ 173 | # Define the input parameter data type 174 | sp.set_type(proposal_id, sp.TNat) 175 | 176 | # Check that one of the collaborators executed the entry point 177 | self.check_is_collaborator() 178 | 179 | # Check that is a valid proposal 180 | self.check_proposal_is_valid(proposal_id) 181 | 182 | # Check that the proposal received all the collaborator approvals, 183 | # except for special lambdas (lambda_id < 10) 184 | proposal = sp.local("proposal", self.data.proposals[proposal_id]) 185 | 186 | with sp.if_(proposal.value.lambda_id >= 10): 187 | sp.verify( 188 | proposal.value.approvals == sp.len(self.data.collaborators), 189 | message="COLLAB_NOT_APPROVED") 190 | with sp.else_(): 191 | sp.verify( 192 | proposal.value.approvals >= 1, 193 | message="COLLAB_NOT_APPROVED") 194 | 195 | # Set the proposal as executed 196 | self.data.proposals[proposal_id].executed = True 197 | 198 | # Get the lambda function to execute from the lambda provider contract 199 | lambda_function = sp.view( 200 | name="get_lambda", 201 | address=self.data.lambda_provider, 202 | param=proposal.value.lambda_id, 203 | t=LambdaProviderContract.LAMBDA_FUNCTION_TYPE).open_some() 204 | 205 | # Execute the proposal 206 | operations = lambda_function(proposal.value.parameters) 207 | sp.add_operations(operations) 208 | 209 | 210 | class CollabOriginatorContract(sp.Contract): 211 | """A contract used to originate the artists collaboration contracts. 212 | 213 | """ 214 | 215 | def __init__(self, metadata): 216 | """Initializes the contract. 217 | 218 | """ 219 | # Initialize the collaboration contract 220 | self.contract = CollaborationContract() 221 | 222 | # Define the contract storage data types for clarity 223 | self.init_type(sp.TRecord( 224 | # The contract metadata 225 | metadata=sp.TBigMap(sp.TString, sp.TBytes), 226 | # The big map with all the originated collaborations 227 | collaborations=sp.TBigMap(sp.TNat, sp.TAddress), 228 | # The collaborations bigmap counter 229 | counter=sp.TNat)) 230 | 231 | # Initialize the contract storage 232 | self.init( 233 | metadata=metadata, 234 | collaborations=sp.big_map(), 235 | counter=0) 236 | 237 | @sp.entry_point 238 | def create_collaboration(self, params): 239 | """Creates a new collaboration contract. 240 | 241 | """ 242 | # Define the input parameter data type 243 | sp.set_type(params, sp.TRecord( 244 | metadata=sp.TBigMap(sp.TString, sp.TBytes), 245 | collaborators=sp.TMap(sp.TAddress, sp.TNat), 246 | lambda_provider=sp.TAddress).layout( 247 | ("metadata", ("collaborators", "lambda_provider")))) 248 | 249 | # Check that there is at least two collaborators 250 | sp.verify(sp.len(params.collaborators) > 1, 251 | message="ORIGINATOR_FEW_COLLABORATORS") 252 | 253 | # Check that the collaboration is initiated by one of the collaborators 254 | sp.verify(params.collaborators.contains(sp.sender), 255 | message="ORIGINATOR_NO_COLLABORATOR") 256 | 257 | # Check that the collaborators shares add to a total of 1000 258 | total_shares = sp.local("total_shares", sp.nat(0)) 259 | 260 | with sp.for_("share", params.collaborators.values()) as share: 261 | total_shares.value += share 262 | 263 | sp.verify(total_shares.value == 1000, message="ORIGINATOR_WRONG_SHARES") 264 | 265 | # Create the new contract and add it to the collaborations big map 266 | self.data.collaborations[self.data.counter] = sp.create_contract( 267 | contract=self.contract, 268 | storage=sp.record( 269 | metadata=params.metadata, 270 | collaborators=params.collaborators, 271 | lambda_provider=params.lambda_provider, 272 | proposals=sp.big_map( 273 | tkey=sp.TNat, tvalue=CollaborationContract.PROPOSAL_TYPE), 274 | approvals=sp.big_map( 275 | tkey=sp.TPair(sp.TNat, sp.TAddress), tvalue=sp.TBool), 276 | counter=sp.nat(0)), 277 | amount=sp.mutez(0)) 278 | 279 | # Increase the collaborations counter 280 | self.data.counter += 1 281 | 282 | 283 | class LambdaProviderContract(sp.Contract): 284 | """A proxy contract that is used by the artists collaboration contract to 285 | call other contracts. 286 | 287 | """ 288 | 289 | LAMBDA_FUNCTION_TYPE = sp.TLambda(sp.TBytes, sp.TList(sp.TOperation)) 290 | 291 | LAMBDA_RECORD_TYPE = sp.TRecord( 292 | # Flag to indicate if the lambda function is enabled or disabled 293 | enabled=sp.TBool, 294 | # The lambda function alias (e.g mint_objkt, swap_teia) 295 | alias=sp.TString, 296 | # The lambda function 297 | lambda_function=LAMBDA_FUNCTION_TYPE).layout( 298 | ("enabled", ("alias", "lambda_function"))) 299 | 300 | def __init__(self, administrator, metadata): 301 | """Initializes the contract. 302 | 303 | """ 304 | # Define the contract storage data types for clarity 305 | self.init_type(sp.TRecord( 306 | # The contract administrador 307 | administrator=sp.TAddress, 308 | # The contract metadata 309 | metadata=sp.TBigMap(sp.TString, sp.TBytes), 310 | # The big map with all the lambda functions 311 | lambdas=sp.TBigMap( 312 | sp.TNat, LambdaProviderContract.LAMBDA_RECORD_TYPE), 313 | # The proposed new administrator address 314 | proposed_administrator=sp.TOption(sp.TAddress))) 315 | 316 | # Initialize the contract storage 317 | self.init( 318 | administrator=administrator, 319 | metadata=metadata, 320 | lambdas=sp.big_map(), 321 | proposed_administrator=sp.none) 322 | 323 | def check_is_administrator(self): 324 | """Checks that the address that called the entry point is the contract 325 | administrator. 326 | 327 | """ 328 | sp.verify(sp.sender == self.data.administrator, 329 | message="PROXY_NOT_ADMIN") 330 | 331 | def check_lambda_exists(self, lambda_id): 332 | """Checks that the lambda id is from an existing lambda function. 333 | 334 | """ 335 | sp.verify(self.data.lambdas.contains(lambda_id), 336 | message="PROXY_INEXISTENT_LAMBDA") 337 | 338 | @sp.entry_point 339 | def add_lambda(self, params): 340 | """Adds a new lambda function. 341 | 342 | """ 343 | # Define the input parameter data type 344 | sp.set_type(params, sp.TRecord( 345 | lambda_id=sp.TNat, 346 | alias=sp.TString, 347 | lambda_function=LambdaProviderContract.LAMBDA_FUNCTION_TYPE).layout( 348 | ("lambda_id", ("alias", "lambda_function")))) 349 | 350 | # Check that the administrator executed the entry point 351 | self.check_is_administrator() 352 | 353 | # Check that the lambda id doesn't exist already 354 | sp.verify(~self.data.lambdas.contains(params.lambda_id), 355 | message="PROXY_EXISTENT_LAMBDA") 356 | 357 | # Add the new lambda function 358 | self.data.lambdas[params.lambda_id] = sp.record( 359 | enabled=True, 360 | alias=params.alias, 361 | lambda_function=params.lambda_function) 362 | 363 | @sp.entry_point 364 | def enable_lambda(self, params): 365 | """Enables or disables an existing lambda function. 366 | 367 | """ 368 | # Define the input parameter data type 369 | sp.set_type(params, sp.TRecord( 370 | lambda_id=sp.TNat, 371 | enabled=sp.TBool).layout(("lambda_id", "enabled"))) 372 | 373 | # Check that the administrator executed the entry point 374 | self.check_is_administrator() 375 | 376 | # Check that the lambda function is present in the lambdas big map 377 | self.check_lambda_exists(params.lambda_id) 378 | 379 | # Enable or disable the lambda function 380 | self.data.lambdas[params.lambda_id].enabled = params.enabled 381 | 382 | @sp.onchain_view() 383 | def has_lambda(self, lambda_id): 384 | """Returns true if the lambda function exists. 385 | 386 | """ 387 | # Define the input parameter data type 388 | sp.set_type(lambda_id, sp.TNat) 389 | 390 | # Return true if the lambda function exists 391 | sp.result(self.data.lambdas.contains(lambda_id)) 392 | 393 | @sp.onchain_view() 394 | def get_lambda(self, lambda_id): 395 | """Returns an existing lambda function. 396 | 397 | """ 398 | # Define the input parameter data type 399 | sp.set_type(lambda_id, sp.TNat) 400 | 401 | # Check that the lambda function is present in the lambdas big map 402 | self.check_lambda_exists(lambda_id) 403 | 404 | # Check that the lambda function is enabled 405 | sp.verify(self.data.lambdas[lambda_id].enabled, 406 | message="PROXY_DISABLED_LAMBDA") 407 | 408 | # Return the lambda function 409 | sp.result(self.data.lambdas[lambda_id].lambda_function) 410 | 411 | @sp.entry_point 412 | def transfer_administrator(self, proposed_administrator): 413 | """Proposes to transfer the contract administrator to another address. 414 | 415 | """ 416 | # Define the input parameter data type 417 | sp.set_type(proposed_administrator, sp.TAddress) 418 | 419 | # Check that the administrator executed the entry point 420 | self.check_is_administrator() 421 | 422 | # Set the new proposed administrator address 423 | self.data.proposed_administrator = sp.some(proposed_administrator) 424 | 425 | @sp.entry_point 426 | def accept_administrator(self): 427 | """The proposed administrator accepts the contract administrator 428 | responsabilities. 429 | 430 | """ 431 | # Check that there is a proposed administrator 432 | sp.verify(self.data.proposed_administrator.is_some(), 433 | message="PROXY_NO_NEW_ADMIN") 434 | 435 | # Check that the proposed administrator executed the entry point 436 | sp.verify(sp.sender == self.data.proposed_administrator.open_some(), 437 | message="PROXY_NOT_PROPOSED_ADMIN") 438 | 439 | # Set the new administrator address 440 | self.data.administrator = sp.sender 441 | 442 | # Reset the proposed administrator value 443 | self.data.proposed_administrator = sp.none 444 | 445 | 446 | sp.add_compilation_target("Collaboration", CollaborationContract()) 447 | 448 | sp.add_compilation_target("CollabOriginator", CollabOriginatorContract( 449 | metadata=sp.utils.metadata_of_url("ipfs://aaa"))) 450 | 451 | sp.add_compilation_target("LambdaProvider", LambdaProviderContract( 452 | administrator=sp.address("tz1M9CMEtsXm3QxA7FmMU2Qh7xzsuGXVbcDr"), 453 | metadata=sp.utils.metadata_of_url("ipfs://bbb"))) 454 | -------------------------------------------------------------------------------- /python/contracts/collaborativeTextContract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | 4 | class CollaborativeTextContract(sp.Contract): 5 | """This contract implements a simple contract where users can write some 6 | collaborative text. 7 | 8 | """ 9 | 10 | def __init__(self): 11 | """Initializes the contract. 12 | 13 | """ 14 | # Define the contract storage data types for clarity 15 | self.init_type(sp.TRecord( 16 | text=sp.TBigMap(sp.TNat, sp.TString), 17 | counter=sp.TNat)) 18 | 19 | # Initialize the contract storage 20 | self.init( 21 | text=sp.big_map(), 22 | counter=0) 23 | 24 | @sp.entry_point 25 | def add_line(self, line): 26 | """Adds a line to the text. 27 | 28 | """ 29 | # Define the input parameter data type 30 | sp.set_type(line, sp.TString) 31 | 32 | # Check that no tez have been transferred 33 | sp.verify(sp.amount == sp.tez(0), 34 | message="The operation does not need tez transfers") 35 | 36 | # Add the line and increase the line counter 37 | self.data.text[self.data.counter] = line 38 | self.data.counter += 1 39 | 40 | 41 | # Add a compilation target 42 | sp.add_compilation_target("collaborativeText", CollaborativeTextContract()) 43 | -------------------------------------------------------------------------------- /python/contracts/doctorContract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | 4 | class DoctorContract(sp.Contract): 5 | """This contract implements a basic doctor account. 6 | 7 | The doctor treats their patient's illnesses prescribing them the 8 | appropriate medicaments. 9 | 10 | """ 11 | 12 | def __init__(self): 13 | """Initializes the contract. 14 | 15 | """ 16 | # Define the contract storage data types for clarity 17 | self.init_type(sp.TRecord( 18 | patients=sp.TSet(sp.TAddress), 19 | medicaments=sp.TMap(sp.TString, sp.TString))) 20 | 21 | # Initialize the contract storage 22 | self.init( 23 | patients=sp.set([]), 24 | medicaments={ 25 | "cold": "med1", 26 | "flu": "med2", 27 | "headache": "med3"}) 28 | 29 | @sp.entry_point 30 | def clean_patients(self): 31 | """Cleans the list of patients. 32 | 33 | """ 34 | sp.verify(sp.sender == sp.self_address) 35 | self.data.patients = sp.set([]) 36 | 37 | @sp.entry_point 38 | def treat_illness(self, params): 39 | """Treats the patient illness. 40 | 41 | """ 42 | # Define the input parameter data type 43 | sp.set_type(params, sp.TString) 44 | 45 | # Check that it knows a medicament for the patient illness 46 | sp.verify(self.data.medicaments.contains(params)) 47 | 48 | # Add the patient to the patients list 49 | self.data.patients.add(sp.sender) 50 | 51 | # Prescribe the medicaments to the patient 52 | patient = sp.contract( 53 | sp.TString, sp.sender, 54 | entry_point="get_medicament").open_some() 55 | sp.transfer(self.data.medicaments[params], sp.mutez(0), patient) 56 | 57 | 58 | # Add a compilation target 59 | sp.add_compilation_target("doctor", DoctorContract()) 60 | -------------------------------------------------------------------------------- /python/contracts/extendedFa2Contract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | 4 | class FA2(sp.Contract): 5 | """This contract tries to simplify and exented the FA2 contract template 6 | example in smartpy.io v0.9.1. 7 | 8 | The FA2 template was originally developed by Seb Mondet: 9 | https://gitlab.com/smondet/fa2-smartpy 10 | 11 | The contract follows the FA2 standard specification: 12 | https://gitlab.com/tezos/tzip/-/blob/master/proposals/tzip-12/tzip-12.md 13 | 14 | """ 15 | 16 | LEDGER_KEY_TYPE = sp.TPair( 17 | # The owner of the token editions 18 | sp.TAddress, 19 | # The token id 20 | sp.TNat) 21 | 22 | TOKEN_METADATA_VALUE_TYPE = sp.TRecord( 23 | # The token id 24 | token_id=sp.TNat, 25 | # The map with the token metadata information 26 | token_info=sp.TMap(sp.TString, sp.TBytes)).layout( 27 | ("token_id", "token_info")) 28 | 29 | USER_ROYALTIES_TYPE = sp.TRecord( 30 | # The user address 31 | address=sp.TAddress, 32 | # The user royalties in per mille (100 is 10%) 33 | royalties=sp.TNat).layout( 34 | ("address", "royalties")) 35 | 36 | TOKEN_ROYALTIES_VALUE_TYPE = sp.TRecord( 37 | # The token original minter 38 | minter=USER_ROYALTIES_TYPE, 39 | # The token creator (it could be a single creator or a collaboration) 40 | creator=USER_ROYALTIES_TYPE).layout( 41 | ("minter", "creator")) 42 | 43 | OPERATOR_KEY_TYPE = sp.TRecord( 44 | # The owner of the token editions 45 | owner=sp.TAddress, 46 | # The operator allowed by the owner to transfer their token editions 47 | operator=sp.TAddress, 48 | # The token id 49 | token_id=sp.TNat).layout( 50 | ("owner", ("operator", "token_id"))) 51 | 52 | def __init__(self, administrator, metadata): 53 | """Initializes the contract. 54 | 55 | """ 56 | # Define the contract storage data types for clarity 57 | self.init_type(sp.TRecord( 58 | # The contract administrador 59 | administrator=sp.TAddress, 60 | # The contract metadata 61 | metadata=sp.TBigMap(sp.TString, sp.TBytes), 62 | # The ledger big map where the tokens owners are listed 63 | ledger=sp.TBigMap(FA2.LEDGER_KEY_TYPE, sp.TNat), 64 | # The tokens total supply 65 | supply=sp.TBigMap(sp.TNat, sp.TNat), 66 | # The big map with the tokens metadata 67 | token_metadata=sp.TBigMap(sp.TNat, FA2.TOKEN_METADATA_VALUE_TYPE), 68 | # The big map with the tokens data (source code, description, etc) 69 | token_data=sp.TBigMap(sp.TNat, sp.TMap(sp.TString, sp.TBytes)), 70 | # The big map with the tokens royalties for the minter and creators 71 | token_royalties=sp.TBigMap(sp.TNat, FA2.TOKEN_ROYALTIES_VALUE_TYPE), 72 | # The big map with the tokens operators 73 | operators=sp.TBigMap(FA2.OPERATOR_KEY_TYPE, sp.TUnit), 74 | # The proposed new administrator address 75 | proposed_administrator=sp.TOption(sp.TAddress), 76 | # A counter that tracks the total number of tokens minted so far 77 | counter=sp.TNat)) 78 | 79 | # Initialize the contract storage 80 | self.init( 81 | administrator=administrator, 82 | metadata=metadata, 83 | ledger=sp.big_map(), 84 | supply=sp.big_map(), 85 | token_metadata=sp.big_map(), 86 | token_data=sp.big_map(), 87 | token_royalties=sp.big_map(), 88 | operators=sp.big_map(), 89 | proposed_administrator=sp.none, 90 | counter=0) 91 | 92 | # Build the TZIP-016 contract metadata 93 | # This is helpful to get the off-chain views code in json format 94 | contract_metadata = { 95 | "name": "Extended FA2 template contract", 96 | "description" : "This contract tries to simplify and extend the " 97 | "FA2 contract template example in smartpy.io v0.9.1", 98 | "version": "v1.0.0", 99 | "authors": ["Javier Gracia Carpio "], 100 | "homepage": "https://github.com/jagracar/tezos-smart-contracts", 101 | "source": { 102 | "tools": ["SmartPy 0.9.1"], 103 | "location": "https://github.com/jagracar/tezos-smart-contracts/blob/main/python/contracts/extendedFa2Contract.py" 104 | }, 105 | "interfaces": ["TZIP-012", "TZIP-016"], 106 | "views": [ 107 | self.get_balance, 108 | self.total_supply, 109 | self.all_tokens, 110 | self.is_operator, 111 | self.token_metadata, 112 | self.token_data, 113 | self.token_royalties], 114 | "permissions": { 115 | "operator": "owner-or-operator-transfer", 116 | "receiver": "owner-no-hook", 117 | "sender": "owner-no-hook" 118 | } 119 | } 120 | 121 | self.init_metadata("contract_metadata", contract_metadata) 122 | 123 | def check_is_administrator(self): 124 | """Checks that the address that called the entry point is the contract 125 | administrator. 126 | 127 | """ 128 | sp.verify(sp.sender == self.data.administrator, message="FA2_NOT_ADMIN") 129 | 130 | def check_token_exists(self, token_id): 131 | """Checks that the given token exists. 132 | 133 | """ 134 | sp.verify(token_id < self.data.counter, message="FA2_TOKEN_UNDEFINED") 135 | 136 | @sp.entry_point 137 | def mint(self, params): 138 | """Mints a new token. 139 | 140 | """ 141 | # Define the input parameter data type 142 | sp.set_type(params, sp.TRecord( 143 | amount=sp.TNat, 144 | metadata=sp.TMap(sp.TString, sp.TBytes), 145 | data=sp.TMap(sp.TString, sp.TBytes), 146 | royalties=FA2.TOKEN_ROYALTIES_VALUE_TYPE).layout( 147 | ("amount", ("metadata", ("data", "royalties"))))) 148 | 149 | # Check that the administrator executed the entry point 150 | self.check_is_administrator() 151 | 152 | # Check that the total royalties do not exceed 100% 153 | sp.verify(params.royalties.minter.royalties + 154 | params.royalties.creator.royalties <= 1000, 155 | message="FA2_INVALID_ROYALTIES") 156 | 157 | # Update the big maps 158 | token_id = sp.compute(self.data.counter) 159 | self.data.ledger[ 160 | (params.royalties.minter.address, token_id)] = params.amount 161 | self.data.supply[token_id] = params.amount 162 | self.data.token_metadata[token_id] = sp.record( 163 | token_id=token_id, 164 | token_info=params.metadata) 165 | self.data.token_data[token_id] = params.data 166 | self.data.token_royalties[token_id] = params.royalties 167 | 168 | # Increase the tokens counter 169 | self.data.counter += 1 170 | 171 | @sp.entry_point 172 | def transfer(self, params): 173 | """Executes a list of token transfers. 174 | 175 | """ 176 | # Define the input parameter data type 177 | sp.set_type(params, sp.TList(sp.TRecord( 178 | from_=sp.TAddress, 179 | txs=sp.TList(sp.TRecord( 180 | to_=sp.TAddress, 181 | token_id=sp.TNat, 182 | amount=sp.TNat).layout( 183 | ("to_", ("token_id", "amount"))))).layout( 184 | ("from_", "txs")))) 185 | 186 | # Loop over the list of transfers 187 | with sp.for_("transfer", params) as transfer: 188 | with sp.for_("tx", transfer.txs) as tx: 189 | # Check that the token exists 190 | token_id = sp.compute(tx.token_id) 191 | self.check_token_exists(token_id) 192 | 193 | # Check that the sender is one of the token operators 194 | owner = sp.compute(transfer.from_) 195 | sp.verify( 196 | (sp.sender == owner) | 197 | self.data.operators.contains(sp.record( 198 | owner=owner, 199 | operator=sp.sender, 200 | token_id=token_id)), 201 | message="FA2_NOT_OPERATOR") 202 | 203 | # Check that the transfer amount is not zero 204 | with sp.if_(tx.amount > 0): 205 | # Remove the token amount from the owner 206 | owner_key = sp.pair(owner, token_id) 207 | self.data.ledger[owner_key] = sp.as_nat( 208 | self.data.ledger.get(owner_key, 0) - tx.amount, 209 | "FA2_INSUFFICIENT_BALANCE") 210 | 211 | # Add the token amount to the new owner 212 | new_owner_key = sp.pair(tx.to_, token_id) 213 | self.data.ledger[new_owner_key] = self.data.ledger.get( 214 | new_owner_key, 0) + tx.amount 215 | 216 | @sp.entry_point 217 | def balance_of(self, params): 218 | """Requests information about a list of token balances. 219 | 220 | """ 221 | # Define the input parameter data type 222 | request_type = sp.TRecord( 223 | owner=sp.TAddress, 224 | token_id=sp.TNat).layout(("owner", "token_id")) 225 | sp.set_type(params, sp.TRecord( 226 | requests=sp.TList(request_type), 227 | callback=sp.TContract(sp.TList(sp.TRecord( 228 | request=request_type, 229 | balance=sp.TNat).layout(("request", "balance"))))).layout( 230 | ("requests", "callback"))) 231 | 232 | def process_request(request): 233 | # Check that the token exists 234 | self.check_token_exists(request.token_id) 235 | 236 | # Return the owner token balance 237 | sp.result(sp.record( 238 | request=request, 239 | balance=self.data.ledger.get( 240 | (request.owner, request.token_id), 0))) 241 | 242 | sp.transfer( 243 | params.requests.map(process_request), sp.mutez(0), params.callback) 244 | 245 | @sp.entry_point 246 | def update_operators(self, params): 247 | """Updates a list of operators. 248 | 249 | """ 250 | # Define the input parameter data type 251 | sp.set_type(params, sp.TList(sp.TVariant( 252 | add_operator=FA2.OPERATOR_KEY_TYPE, 253 | remove_operator=FA2.OPERATOR_KEY_TYPE))) 254 | 255 | # Loop over the list of update operators 256 | with sp.for_("update_operator", params) as update_operator: 257 | with update_operator.match_cases() as arg: 258 | with arg.match("add_operator") as operator_key: 259 | # Check that the token exists 260 | self.check_token_exists(operator_key.token_id) 261 | 262 | # Check that the sender is the token owner 263 | sp.verify(sp.sender == operator_key.owner, 264 | message="FA2_SENDER_IS_NOT_OWNER") 265 | 266 | # Add the new operator to the operators big map 267 | self.data.operators[operator_key] = sp.unit 268 | with arg.match("remove_operator") as operator_key: 269 | # Check that the token exists 270 | self.check_token_exists(operator_key.token_id) 271 | 272 | # Check that the sender is the token owner 273 | sp.verify(sp.sender == operator_key.owner, 274 | message="FA2_SENDER_IS_NOT_OWNER") 275 | 276 | # Remove the operator from the operators big map 277 | del self.data.operators[operator_key] 278 | 279 | @sp.entry_point 280 | def transfer_administrator(self, proposed_administrator): 281 | """Proposes to transfer the contract administrator to another address. 282 | 283 | """ 284 | # Define the input parameter data type 285 | sp.set_type(proposed_administrator, sp.TAddress) 286 | 287 | # Check that the administrator executed the entry point 288 | self.check_is_administrator() 289 | 290 | # Set the new proposed administrator address 291 | self.data.proposed_administrator = sp.some(proposed_administrator) 292 | 293 | @sp.entry_point 294 | def accept_administrator(self): 295 | """The proposed administrator accepts the contract administrator 296 | responsabilities. 297 | 298 | """ 299 | # Check that there is a proposed administrator 300 | sp.verify(self.data.proposed_administrator.is_some(), 301 | message="FA_NO_NEW_ADMIN") 302 | 303 | # Check that the proposed administrator executed the entry point 304 | sp.verify(sp.sender == self.data.proposed_administrator.open_some(), 305 | message="FA_NOT_PROPOSED_ADMIN") 306 | 307 | # Set the new administrator address 308 | self.data.administrator = sp.sender 309 | 310 | # Reset the proposed administrator value 311 | self.data.proposed_administrator = sp.none 312 | 313 | @sp.entry_point 314 | def set_metadata(self, params): 315 | """Updates the contract metadata. 316 | 317 | """ 318 | # Define the input parameter data type 319 | sp.set_type(params, sp.TRecord( 320 | k=sp.TString, 321 | v=sp.TBytes).layout(("k", "v"))) 322 | 323 | # Check that the administrator executed the entry point 324 | self.check_is_administrator() 325 | 326 | # Update the contract metadata 327 | self.data.metadata[params.k] = params.v 328 | 329 | @sp.onchain_view(pure=True) 330 | def token_exists(self, token_id): 331 | """Checks if the token exists. 332 | 333 | """ 334 | # Define the input parameter data type 335 | sp.set_type(token_id, sp.TNat) 336 | 337 | # Return true if the token exists 338 | sp.result(token_id < self.data.counter) 339 | 340 | @sp.onchain_view(pure=True) 341 | def count_tokens(self): 342 | """Returns how many tokens are in this FA2 contract. 343 | 344 | """ 345 | sp.result(self.data.counter) 346 | 347 | @sp.onchain_view(pure=True) 348 | def get_balance(self, params): 349 | """Returns the owner token balance. 350 | 351 | """ 352 | # Define the input parameter data type 353 | sp.set_type(params, sp.TRecord( 354 | owner=sp.TAddress, 355 | token_id=sp.TNat).layout(("owner", "token_id"))) 356 | 357 | # Check that the token exists 358 | self.check_token_exists(params.token_id) 359 | 360 | # Return the owner token balance 361 | sp.result(self.data.ledger.get((params.owner, params.token_id), 0)) 362 | 363 | @sp.onchain_view(pure=True) 364 | def total_supply(self, token_id): 365 | """Returns the total supply for a given token id. 366 | 367 | """ 368 | # Define the input parameter data type 369 | sp.set_type(token_id, sp.TNat) 370 | 371 | # Check that the token exists 372 | self.check_token_exists(token_id) 373 | 374 | # Return the token total supply 375 | sp.result(self.data.supply.get(token_id, 0)) 376 | 377 | @sp.onchain_view(pure=True) 378 | def all_tokens(self): 379 | """Returns a list with all the token ids. 380 | 381 | """ 382 | sp.result(sp.range(0, self.data.counter)) 383 | 384 | @sp.onchain_view(pure=True) 385 | def is_operator(self, params): 386 | """Checks if a given token operator exists. 387 | 388 | """ 389 | # Define the input parameter data type 390 | sp.set_type(params, FA2.OPERATOR_KEY_TYPE) 391 | 392 | # Check that the token exists 393 | self.check_token_exists(params.token_id) 394 | 395 | # Return true if the token operator exists 396 | sp.result(self.data.operators.contains(params)) 397 | 398 | @sp.onchain_view(pure=True) 399 | def token_metadata(self, token_id): 400 | """Returns the token metadata. 401 | 402 | """ 403 | # Define the input parameter data type 404 | sp.set_type(token_id, sp.TNat) 405 | 406 | # Check that the token exists 407 | self.check_token_exists(token_id) 408 | 409 | # Return the token metadata 410 | sp.result(self.data.token_metadata[token_id]) 411 | 412 | @sp.onchain_view(pure=True) 413 | def token_data(self, token_id): 414 | """Returns the token on-chain data. 415 | 416 | """ 417 | # Define the input parameter data type 418 | sp.set_type(token_id, sp.TNat) 419 | 420 | # Return the token on-chain data 421 | sp.result(self.data.token_data[token_id]) 422 | 423 | @sp.onchain_view(pure=True) 424 | def token_royalties(self, token_id): 425 | """Returns the token royalties information. 426 | 427 | """ 428 | # Define the input parameter data type 429 | sp.set_type(token_id, sp.TNat) 430 | 431 | # Return the token royalties information 432 | sp.result(self.data.token_royalties[token_id]) 433 | 434 | 435 | sp.add_compilation_target("ExtendedFA2", FA2( 436 | administrator=sp.address("tz1M9CMEtsXm3QxA7FmMU2Qh7xzsuGXVbcDr"), 437 | metadata=sp.utils.metadata_of_url("ipfs://aaa"))) 438 | -------------------------------------------------------------------------------- /python/contracts/fa2Contract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | 4 | class FA2(sp.Contract): 5 | """This contract tries to simplify the FA2 contract template example in 6 | smartpy.io v0.9.0. 7 | 8 | The FA2 template was originally developed by Seb Mondet: 9 | https://gitlab.com/smondet/fa2-smartpy 10 | 11 | The contract follows the FA2 standard specification: 12 | https://gitlab.com/tezos/tzip/-/blob/master/proposals/tzip-12/tzip-12.md 13 | 14 | """ 15 | 16 | LEDGER_KEY_TYPE = sp.TPair( 17 | # The token owner 18 | sp.TAddress, 19 | # The token id 20 | sp.TNat) 21 | 22 | LEDGER_VALUE_TYPE = sp.TRecord( 23 | # The number of token editions that the owner has 24 | balance=sp.TNat) 25 | 26 | TOKEN_METADATA_VALUE_TYPE = sp.TRecord( 27 | # The token id 28 | token_id=sp.TNat, 29 | # The map with the token metadata information 30 | token_info=sp.TMap(sp.TString, sp.TBytes)).layout( 31 | ("token_id", "token_info")) 32 | 33 | OPERATOR_KEY_TYPE = sp.TRecord( 34 | # The token owner 35 | owner=sp.TAddress, 36 | # The operator allowed by the owner to transfer the token 37 | operator=sp.TAddress, 38 | # The token id 39 | token_id=sp.TNat).layout( 40 | ("owner", ("operator", "token_id"))) 41 | 42 | def __init__(self, administrator, metadata): 43 | """Initializes the contract. 44 | 45 | """ 46 | # Define the contract storage data types for clarity 47 | self.init_type(sp.TRecord( 48 | # The contract administrador 49 | administrator=sp.TAddress, 50 | # The contract metadata 51 | metadata=sp.TBigMap(sp.TString, sp.TBytes), 52 | # The ledger bigmap where the token owners are listed 53 | ledger=sp.TBigMap(FA2.LEDGER_KEY_TYPE, FA2.LEDGER_VALUE_TYPE), 54 | # The tokens total supply 55 | total_supply=sp.TBigMap(sp.TNat, sp.TNat), 56 | # The tokens metadata big map 57 | token_metadata=sp.TBigMap(sp.TNat, FA2.TOKEN_METADATA_VALUE_TYPE), 58 | # The token operators big map 59 | operators=sp.TBigMap(FA2.OPERATOR_KEY_TYPE, sp.TUnit), 60 | # The total number of tokens minted so far 61 | all_tokens=sp.TNat, 62 | # Flag to indicate if the contract is paused or not 63 | paused=sp.TBool)) 64 | 65 | # Initialize the contract storage 66 | self.init( 67 | administrator=administrator, 68 | metadata=metadata, 69 | ledger=sp.big_map(), 70 | total_supply=sp.big_map(), 71 | token_metadata=sp.big_map(), 72 | operators=sp.big_map(), 73 | all_tokens=0, 74 | paused=False) 75 | 76 | # Adds some flags and optimization levels 77 | self.add_flag("initial-cast") 78 | self.exception_optimization_level = "default-line" 79 | 80 | # Build the TZIP-016 contract metadata 81 | # This is helpful to get the off-chain views code in json format 82 | contract_metadata = { 83 | "name": "Simplified FA2 template contract", 84 | "description" : "This contract tries to simplify the FA2 " + 85 | "contract template example in smartpy.io v0.9.0", 86 | "version": "v1.0.0", 87 | "authors": [ 88 | "Seb Mondet ", 89 | "Javier Gracia Carpio "], 90 | "homepage": "https://github.com/jagracar/tezos-smart-contracts", 91 | "source": { 92 | "tools": ["SmartPy 0.9.0"], 93 | "location": "https://github.com/jagracar/tezos-smart-contracts/blob/main/python/contracts/fa2Contract.py" 94 | }, 95 | "interfaces": ["TZIP-012", "TZIP-016"], 96 | "views": [ 97 | self.get_balance, 98 | self.does_token_exist, 99 | self.count_tokens, 100 | self.all_tokens, 101 | self.total_supply, 102 | self.is_operator], 103 | "permissions": { 104 | "operator": "owner-or-operator-transfer", 105 | "receiver": "owner-no-hook", 106 | "sender": "owner-no-hook" 107 | } 108 | } 109 | 110 | self.init_metadata("contract_metadata", contract_metadata) 111 | 112 | def check_is_administrator(self): 113 | """Checks that the address that called the entry point is the contract 114 | administrator. 115 | 116 | """ 117 | sp.verify(sp.sender == self.data.administrator, message="FA2_NOT_ADMIN") 118 | 119 | def check_is_administrator_or_owner(self, owner): 120 | """Checks that the address that called the entry point is either the 121 | contract administrator or the token owner. 122 | 123 | """ 124 | sp.verify((sp.sender == self.data.administrator) | (sp.sender == owner), 125 | message="FA2_NOT_ADMIN_OR_OPERATOR") 126 | 127 | def check_is_operator(self, owner, token_id): 128 | """Checks that the address that called the entry point is allowed to 129 | transfer the token. 130 | 131 | """ 132 | sp.verify((sp.sender == self.data.administrator) | 133 | (sp.sender == owner) | 134 | (self.data.operators.contains(sp.record( 135 | owner=owner, operator=sp.sender, token_id=token_id))), 136 | message="FA2_NOT_OPERATOR") 137 | 138 | def check_token_exists(self, token_id): 139 | """Checks that the given token exists. 140 | 141 | """ 142 | sp.verify(self.data.token_metadata.contains(token_id), 143 | message="FA2_TOKEN_UNDEFINED") 144 | 145 | def check_sufficient_balance(self, owner, token_id, amount): 146 | """Checks that the owner has enough editions of the given token. 147 | 148 | """ 149 | sp.verify(self.data.ledger[(owner, token_id)].balance >= amount, 150 | message="FA2_INSUFFICIENT_BALANCE") 151 | 152 | def check_is_not_paused(self): 153 | """Checks that the contract is not paused. 154 | 155 | """ 156 | sp.verify(~self.data.paused, message="FA2_PAUSED") 157 | 158 | @sp.entry_point 159 | def mint(self, params): 160 | """Mints a new token. 161 | 162 | """ 163 | # Define the input parameter data type 164 | sp.set_type(params, sp.TRecord( 165 | address=sp.TAddress, 166 | amount=sp.TNat, 167 | metadata=sp.TMap(sp.TString, sp.TBytes), 168 | token_id=sp.TNat).layout( 169 | ("address", ("amount", ("metadata", "token_id"))))) 170 | 171 | # Check that the administrator executed the entry point 172 | self.check_is_administrator() 173 | 174 | # Update the ledger big map 175 | ledger_key = sp.pair(params.address, params.token_id) 176 | 177 | with sp.if_(self.data.ledger.contains(ledger_key)): 178 | self.data.ledger[ledger_key].balance += params.amount 179 | with sp.else_(): 180 | self.data.ledger[ledger_key] = sp.record(balance=params.amount) 181 | 182 | # Update the total supply and token metadata big maps 183 | with sp.if_(params.token_id < self.data.all_tokens): 184 | # Increase the token total supply 185 | self.data.total_supply[params.token_id] += params.amount 186 | with sp.else_(): 187 | # Check that the token ids are consecutive 188 | sp.verify(self.data.all_tokens == params.token_id, 189 | message="Token-IDs should be consecutive") 190 | 191 | # Add the new big map rows 192 | self.data.total_supply[params.token_id] = params.amount 193 | self.data.token_metadata[params.token_id] = sp.record( 194 | token_id=params.token_id, 195 | token_info=params.metadata) 196 | 197 | # Increase the all tokens counter 198 | self.data.all_tokens += 1 199 | 200 | @sp.entry_point 201 | def transfer(self, params): 202 | """Executes a list of token transfers. 203 | 204 | """ 205 | # Define the input parameter data type 206 | sp.set_type(params, sp.TList(sp.TRecord( 207 | from_=sp.TAddress, 208 | txs=sp.TList(sp.TRecord( 209 | to_=sp.TAddress, 210 | token_id=sp.TNat, 211 | amount=sp.TNat).layout(("to_", ("token_id", "amount"))))).layout( 212 | ("from_", "txs")))) 213 | 214 | # Checks that the contract is not paused 215 | self.check_is_not_paused() 216 | 217 | # Loop over the list of transfers 218 | with sp.for_("transfer", params) as transfer: 219 | with sp.for_("tx", transfer.txs) as tx: 220 | # Check that the sender is one of the token operators 221 | self.check_is_operator(transfer.from_, tx.token_id) 222 | 223 | # Check that the token exists 224 | self.check_token_exists(tx.token_id) 225 | 226 | # Only do something if the token amount is larger than zero 227 | with sp.if_(tx.amount > 0): 228 | # Check that the owner has enough editions of the token 229 | self.check_sufficient_balance(transfer.from_, tx.token_id, tx.amount) 230 | 231 | # Remove the token amount from the owner 232 | owner_key = sp.pair(transfer.from_, tx.token_id) 233 | self.data.ledger[owner_key].balance = sp.as_nat( 234 | self.data.ledger[owner_key].balance - tx.amount) 235 | 236 | # Add the token amount to the new owner 237 | new_owner_key = sp.pair(tx.to_, tx.token_id) 238 | 239 | with sp.if_(self.data.ledger.contains(new_owner_key)): 240 | self.data.ledger[new_owner_key].balance += tx.amount 241 | with sp.else_(): 242 | self.data.ledger[new_owner_key] = sp.record(balance=tx.amount) 243 | 244 | @sp.entry_point 245 | def balance_of(self, params): 246 | """Requests information about a list of token balances. 247 | 248 | """ 249 | # Define the input parameter data type 250 | request_type = sp.TRecord( 251 | owner=sp.TAddress, 252 | token_id=sp.TNat).layout(("owner", "token_id")) 253 | sp.set_type(params, sp.TRecord( 254 | requests=sp.TList(request_type), 255 | callback=sp.TContract(sp.TList(sp.TRecord( 256 | request=request_type, 257 | balance=sp.TNat).layout(("request", "balance"))))).layout( 258 | ("requests", "callback"))) 259 | 260 | # Checks that the contract is not paused 261 | self.check_is_not_paused() 262 | 263 | def process_request(request): 264 | # Check that the token exists 265 | self.check_token_exists(request.token_id) 266 | 267 | # Check if the owner has the token or had it in the past 268 | ledger_key = sp.pair(request.owner, request.token_id) 269 | 270 | with sp.if_(self.data.ledger.contains(ledger_key)): 271 | sp.result(sp.record( 272 | request=sp.record( 273 | owner=request.owner, 274 | token_id=request.token_id), 275 | balance=self.data.ledger[ledger_key].balance)) 276 | with sp.else_(): 277 | sp.result(sp.record( 278 | request=sp.record( 279 | owner=request.owner, 280 | token_id=request.token_id), 281 | balance=0)) 282 | 283 | responses = sp.local("responses", params.requests.map(process_request)) 284 | sp.transfer(responses.value, sp.mutez(0), params.callback) 285 | 286 | @sp.entry_point 287 | def update_operators(self, params): 288 | """Updates a list of operators. 289 | 290 | """ 291 | # Define the input parameter data type 292 | sp.set_type(params, sp.TList(sp.TVariant( 293 | add_operator=FA2.OPERATOR_KEY_TYPE, 294 | remove_operator=FA2.OPERATOR_KEY_TYPE))) 295 | 296 | # Loop over the list of update operators 297 | with sp.for_("update_operator", params) as update_operator: 298 | with update_operator.match_cases() as arg: 299 | with arg.match("add_operator") as operator_key: 300 | # Check that the sender is the administrator or the token owner 301 | self.check_is_administrator_or_owner(operator_key.owner) 302 | 303 | # Add the new operator to the operators big map 304 | self.data.operators[operator_key] = sp.unit 305 | with arg.match("remove_operator") as operator_key: 306 | # Check that the sender is the administrator or the token owner 307 | self.check_is_administrator_or_owner(operator_key.owner) 308 | 309 | # Remove the operator from the operators big map 310 | del self.data.operators[operator_key] 311 | 312 | @sp.entry_point 313 | def set_administrator(self, administrator): 314 | """Sets a new contract administrator. 315 | 316 | """ 317 | # Define the input parameter data type 318 | sp.set_type(administrator, sp.TAddress) 319 | 320 | # Check that the administrator executed the entry point 321 | self.check_is_administrator() 322 | 323 | # Set the new administrator 324 | self.data.administrator = administrator 325 | 326 | @sp.entry_point 327 | def set_metadata(self, params): 328 | """Updates the contract metadata. 329 | 330 | """ 331 | # Define the input parameter data type 332 | sp.set_type(params, sp.TRecord( 333 | k=sp.TString, 334 | v=sp.TBytes).layout(("k", "v"))) 335 | 336 | # Check that the administrator executed the entry point 337 | self.check_is_administrator() 338 | 339 | # Update the contract metadata 340 | self.data.metadata[params.k] = params.v 341 | 342 | @sp.entry_point 343 | def set_pause(self, pause): 344 | """Pauses or unpauses the contract. 345 | 346 | """ 347 | # Define the input parameter data type 348 | sp.set_type(pause, sp.TBool) 349 | 350 | # Check that the administrator executed the entry point 351 | self.check_is_administrator() 352 | 353 | # Se the new paused state 354 | self.data.paused = pause 355 | 356 | @sp.offchain_view(pure=True) 357 | def get_balance(self, params): 358 | """Returns the owner token balance. 359 | 360 | """ 361 | # Define the input parameter data type 362 | sp.set_type(params, sp.TRecord( 363 | owner=sp.TAddress, 364 | token_id=sp.TNat).layout(("owner", "token_id"))) 365 | 366 | # Check that the token exists 367 | self.check_token_exists(params.token_id) 368 | 369 | # Return the owner token balance 370 | sp.result(self.data.ledger[(params.owner, params.token_id)].balance) 371 | 372 | @sp.offchain_view(pure=True) 373 | def does_token_exist(self, token_id): 374 | """Checks if the token exists. 375 | 376 | """ 377 | # Define the input parameter data type 378 | sp.set_type(token_id, sp.TNat) 379 | 380 | # Return true if the token exists 381 | sp.result(self.data.token_metadata.contains(token_id)) 382 | 383 | @sp.offchain_view(pure=True) 384 | def count_tokens(self): 385 | """Returns how many tokens are in this FA2 contract. 386 | 387 | """ 388 | sp.result(self.data.all_tokens) 389 | 390 | @sp.offchain_view(pure=True) 391 | def all_tokens(self): 392 | """Returns a list with all the token ids. 393 | 394 | """ 395 | sp.result(sp.range(0, self.data.all_tokens)) 396 | 397 | @sp.offchain_view(pure=True) 398 | def total_supply(self, token_id): 399 | """Returns the total supply for a given token id. 400 | 401 | """ 402 | # Define the input parameter data type 403 | sp.set_type(token_id, sp.TNat) 404 | 405 | # Return the token total supply 406 | sp.result(self.data.total_supply[token_id]) 407 | 408 | @sp.offchain_view(pure=True) 409 | def is_operator(self, params): 410 | """Checks if a given token operator exists. 411 | 412 | """ 413 | # Define the input parameter data type 414 | sp.set_type(params, FA2.OPERATOR_KEY_TYPE) 415 | 416 | # Return true if the token operator exists 417 | sp.result(self.data.operators.contains(params)) 418 | 419 | 420 | sp.add_compilation_target("FA2", FA2( 421 | administrator=sp.address("tz1M9CMEtsXm3QxA7FmMU2Qh7xzsuGXVbcDr"), 422 | metadata=sp.utils.metadata_of_url("ipfs://aaa"))) 423 | -------------------------------------------------------------------------------- /python/contracts/henReunionContract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | 4 | class HenReunionContract(sp.Contract): 5 | """This contract implements a simple contract where users can sign for the 6 | #henreunion event. 7 | 8 | """ 9 | 10 | def __init__(self, metadata, end_party): 11 | """Initializes the contract. 12 | 13 | """ 14 | # Define the contract storage data types for clarity 15 | self.init_type(sp.TRecord( 16 | # The contract metadata 17 | metadata=sp.TBigMap(sp.TString, sp.TBytes), 18 | # The timestamp when the party ends 19 | end_party=sp.TTimestamp, 20 | # The participants bigmap 21 | participants=sp.TBigMap(sp.TAddress, sp.TUnit))) 22 | 23 | # Initialize the contract storage 24 | self.init( 25 | metadata=metadata, 26 | end_party=end_party, 27 | participants=sp.big_map()) 28 | 29 | @sp.entry_point 30 | def default(self, unit): 31 | """Don't allow any tez transactions. 32 | 33 | """ 34 | # Define the input parameter data type 35 | sp.set_type(unit, sp.TUnit) 36 | 37 | # Check that the user didn't send anytez 38 | sp.verify(sp.amount == sp.mutez(0), message="The party is free!") 39 | 40 | @sp.entry_point 41 | def go_to_the_kitchen(self, unit): 42 | """You didn't join a party if you didn't visit the kitchen. 43 | 44 | """ 45 | # Define the input parameter data type 46 | sp.set_type(unit, sp.TUnit) 47 | 48 | # Check that the user didn't send anytez 49 | sp.verify(sp.amount == sp.mutez(0), message="The party is free!") 50 | 51 | # Check that the party didn't finish 52 | sp.verify(sp.now < self.data.end_party, 53 | message="Someone called the police and the party is finished :(") 54 | 55 | # Check that the user didn't sign up yet 56 | sp.verify(~self.data.participants.contains(sp.sender), 57 | message="You already joined the party! Are you drunk?") 58 | 59 | # Add the new participant 60 | self.data.participants[sp.sender] = sp.unit 61 | 62 | @sp.onchain_view(pure=True) 63 | def attended(self, user_address): 64 | """Returns True if the user address attended the party. 65 | 66 | """ 67 | # Define the input parameter data type 68 | sp.set_type(user_address, sp.TAddress) 69 | 70 | # Return True if the user attended the party 71 | sp.result(self.data.participants.contains(user_address)) 72 | 73 | 74 | # Add a compilation target 75 | sp.add_compilation_target("henreunionContract", HenReunionContract( 76 | metadata=sp.utils.metadata_of_url("ipfs://QmadaqKUJyV9fJS9fhEbEC3uQUK5Tz995quGrnwMFqLmzf"), 77 | end_party=sp.timestamp_from_utc(2022, 10, 3, 12, 0, 0))) 78 | -------------------------------------------------------------------------------- /python/contracts/lambdaFunctionUtilContract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | 4 | class LambdaFunctionUtilContract(sp.Contract): 5 | """This contract is an untility contract used to extract the michelson code 6 | from a given lambda function. 7 | 8 | The contract can be used to get the code needed to build a lambda proposal 9 | for the multisign wallet contract. 10 | 11 | """ 12 | 13 | def __init__(self): 14 | """Initializes the contract. 15 | 16 | """ 17 | # Define the contract storage data types for clarity 18 | self.init_type(sp.TRecord( 19 | lambda_function=sp.TOption( 20 | sp.TLambda(sp.TUnit, sp.TList(sp.TOperation))))) 21 | 22 | # Initialize the contract storage 23 | self.init(lambda_function=sp.none) 24 | 25 | @sp.entry_point 26 | def update_and_execute_lambda(self, lambda_function): 27 | """Updates and executes the contract lambda function. 28 | 29 | """ 30 | # Define the input parameter data type 31 | sp.set_type( 32 | lambda_function, sp.TLambda(sp.TUnit, sp.TList(sp.TOperation))) 33 | 34 | # Save the lambda function in the contract storage 35 | self.data.lambda_function = sp.some(lambda_function) 36 | 37 | # Execute the lambda function 38 | operations = self.data.lambda_function.open_some()(sp.unit) 39 | 40 | # Add the lambda function operations 41 | sp.add_operations(operations) 42 | 43 | 44 | # Add a compilation target 45 | sp.add_compilation_target("lambdaFunctionUtil", LambdaFunctionUtilContract()) 46 | -------------------------------------------------------------------------------- /python/contracts/managerContract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | DEFAULT_RESCUE_TIME = 100 * 24 * 3600 4 | 5 | 6 | class ManagerContract(sp.Contract): 7 | """This contract implements a basic manager account that can be used to 8 | manage other kind of contracts. 9 | 10 | The contract includes a rescue mode to solve the bus accident scenario: 11 | a situation where the manager dissapears and someone else needs to take 12 | control of the manager tasks. 13 | 14 | """ 15 | 16 | def __init__(self, manager, rescue_time=DEFAULT_RESCUE_TIME): 17 | """Initializes the contract. 18 | 19 | """ 20 | # Define the contract storage data types for clarity 21 | self.init_type(sp.TRecord( 22 | manager=sp.TAddress, 23 | rescue_time=sp.TNat, 24 | rescue_accounts=sp.TSet(sp.TAddress), 25 | last_ping=sp.TTimestamp)) 26 | 27 | # Initialize the contract storage 28 | self.init( 29 | manager=manager, 30 | rescue_time=rescue_time, 31 | rescue_accounts=sp.set([]), 32 | last_ping=sp.timestamp_from_utc_now()) 33 | 34 | def check_is_manager(self): 35 | """Checks that the address that called the entry point is the contract 36 | manager. 37 | 38 | """ 39 | sp.verify(sp.sender == self.data.manager, 40 | message="This can only be executed by the contract manager.") 41 | 42 | @sp.entry_point 43 | def ping(self): 44 | """Pings the contract to indicate that the manager is still active. 45 | 46 | """ 47 | # Update the last ping time stamp 48 | self.check_is_manager() 49 | self.data.last_ping = sp.now 50 | 51 | @sp.entry_point 52 | def update_manager(self, params): 53 | """Updates the manager account. 54 | 55 | """ 56 | # Define the input parameter data type 57 | sp.set_type(params, sp.TAddress) 58 | 59 | # Update the manager account 60 | self.check_is_manager() 61 | self.data.manager = params 62 | 63 | @sp.entry_point 64 | def add_rescue_account(self, params): 65 | """Adds a new account to the rescue accounts. 66 | 67 | """ 68 | # Define the input parameter data type 69 | sp.set_type(params, sp.TAddress) 70 | 71 | # Add the new rescue account 72 | self.check_is_manager() 73 | self.data.rescue_accounts.add(params) 74 | 75 | @sp.entry_point 76 | def remove_rescue_account(self, params): 77 | """Removes an account from the rescue accounts. 78 | 79 | """ 80 | # Define the input parameter data type 81 | sp.set_type(params, sp.TAddress) 82 | 83 | # Remove the rescue account 84 | self.check_is_manager() 85 | self.data.rescue_accounts.remove(params) 86 | 87 | @sp.entry_point 88 | def rescue(self): 89 | """Rescues the manager account when the elapsed time between now and 90 | the last ping from the manager account is larger than the rescue time. 91 | 92 | """ 93 | # Check that the ellapsed time is larger than the rescue time 94 | sp.verify( 95 | sp.as_nat(sp.now - self.data.last_ping) > self.data.rescue_time) 96 | 97 | # Check that the sender is in the rescue accounts 98 | sp.verify(self.data.rescue_accounts.contains(sp.sender), 99 | message="The sender is not in the rescue accounts.") 100 | 101 | # Set the sender as the new manager and update the last ping time stamp 102 | self.data.manager = sp.sender 103 | self.data.last_ping = sp.now 104 | 105 | 106 | # Add a compilation target initialized to my tezos wallet account 107 | sp.add_compilation_target("manager", ManagerContract( 108 | manager=sp.address("tz1g6JRCpsEnD2BLiAzPNK3GBD1fKicV9rCx"))) 109 | -------------------------------------------------------------------------------- /python/contracts/marketplaceContract.py: -------------------------------------------------------------------------------- 1 | 2 | import smartpy as sp 3 | 4 | 5 | class MarketplaceContract(sp.Contract): 6 | """A basic marketplace contract for the extended FA2 token contract. 7 | 8 | """ 9 | 10 | USER_ROYALTIES_TYPE = sp.TRecord( 11 | # The user address 12 | address=sp.TAddress, 13 | # The user royalties in per mille (100 is 10%) 14 | royalties=sp.TNat).layout( 15 | ("address", "royalties")) 16 | 17 | ORG_DONATION_TYPE = sp.TRecord( 18 | # The organization address to donate to 19 | address=sp.TAddress, 20 | # The donation in per mille (100 is 10%) 21 | donation=sp.TNat).layout( 22 | ("address", "donation")) 23 | 24 | SWAP_TYPE = sp.TRecord( 25 | # The user that created the swap 26 | issuer=sp.TAddress, 27 | # The token id 28 | token_id=sp.TNat, 29 | # The number of swapped editions 30 | editions=sp.TNat, 31 | # The edition price in mutez 32 | price=sp.TMutez, 33 | # The list of donations to different organizations 34 | donations=sp.TList(ORG_DONATION_TYPE)).layout( 35 | ("issuer", ("token_id", ("editions", ("price", "donations"))))) 36 | 37 | def __init__(self, administrator, metadata, fa2, fee): 38 | """Initializes the contract. 39 | 40 | """ 41 | # Define the contract storage data types for clarity 42 | self.init_type(sp.TRecord( 43 | # The contract administrador 44 | administrator=sp.TAddress, 45 | # The contract metadata 46 | metadata=sp.TBigMap(sp.TString, sp.TBytes), 47 | # The FA2 token contract address 48 | fa2=sp.TAddress, 49 | # The marketplace fee taken for each collect operation in per mille 50 | fee=sp.TNat, 51 | # The address that will receive the marketplace fees 52 | fee_recipient=sp.TAddress, 53 | # The big map with the swaps information 54 | swaps=sp.TBigMap(sp.TNat, MarketplaceContract.SWAP_TYPE), 55 | # The swaps counter 56 | counter=sp.TNat, 57 | # The proposed new administrator address 58 | proposed_administrator=sp.TOption(sp.TAddress), 59 | # A flag that indicates if the marketplace swaps are paused 60 | swaps_paused=sp.TBool, 61 | # A flag that indicates if the marketplace collects are paused 62 | collects_paused=sp.TBool)) 63 | 64 | # Initialize the contract storage 65 | self.init( 66 | administrator=administrator, 67 | metadata=metadata, 68 | fa2=fa2, 69 | fee=fee, 70 | fee_recipient=administrator, 71 | swaps=sp.big_map(), 72 | counter=0, 73 | proposed_administrator=sp.none, 74 | swaps_paused=False, 75 | collects_paused=False) 76 | 77 | def check_is_administrator(self): 78 | """Checks that the address that called the entry point is the contract 79 | administrator. 80 | 81 | """ 82 | sp.verify(sp.sender == self.data.administrator, message="MP_NOT_ADMIN") 83 | 84 | def check_no_tez_transfer(self): 85 | """Checks that no tez were transferred in the operation. 86 | 87 | """ 88 | sp.verify(sp.amount == sp.tez(0), message="MP_TEZ_TRANSFER") 89 | 90 | @sp.entry_point 91 | def swap(self, params): 92 | """Swaps several editions of a token for a fixed price. 93 | 94 | Note that for this operation to work, the marketplace contract should 95 | be added before as an operator of the token by the swap issuer. 96 | It's recommended to remove the marketplace operator rights after 97 | calling this entry point. 98 | 99 | """ 100 | # Define the input parameter data type 101 | sp.set_type(params, sp.TRecord( 102 | token_id=sp.TNat, 103 | editions=sp.TNat, 104 | price=sp.TMutez, 105 | donations=sp.TList(MarketplaceContract.ORG_DONATION_TYPE)).layout( 106 | ("token_id", ("editions", ("price", "donations"))))) 107 | 108 | # Check that swaps are not paused 109 | sp.verify(~self.data.swaps_paused, message="MP_SWAPS_PAUSED") 110 | 111 | # Check that no tez have been transferred 112 | self.check_no_tez_transfer() 113 | 114 | # Check that at least one edition will be swapped 115 | sp.verify(params.editions > 0, message="MP_NO_SWAPPED_EDITIONS") 116 | 117 | # Check that the number of donations is not too large 118 | donations = sp.local("donations", params.donations) 119 | sp.verify(sp.len(donations.value) <= 5, message="MP_TOO_MANY_DONATIONS") 120 | 121 | # Check that royalties + donations + fee does not exceed 100% 122 | royalties = sp.local("royalties", 123 | self.get_token_royalties(params.token_id)) 124 | total = sp.local("total", 125 | self.data.fee + 126 | royalties.value.minter.royalties + 127 | royalties.value.creator.royalties) 128 | 129 | with sp.for_("org_donation", donations.value) as org_donation: 130 | total.value += org_donation.donation 131 | 132 | sp.verify(total.value <= 1000, message="MP_TOO_HIGH_DONATIONS") 133 | 134 | # Transfer all the editions to the marketplace account 135 | self.fa2_transfer( 136 | fa2=self.data.fa2, 137 | from_=sp.sender, 138 | to_=sp.self_address, 139 | token_id=params.token_id, 140 | token_amount=params.editions) 141 | 142 | # Update the swaps bigmap with the new swap information 143 | self.data.swaps[self.data.counter] = sp.record( 144 | issuer=sp.sender, 145 | token_id=params.token_id, 146 | editions=params.editions, 147 | price=params.price, 148 | donations=donations.value) 149 | 150 | # Increase the swaps counter 151 | self.data.counter += 1 152 | 153 | @sp.entry_point 154 | def collect(self, swap_id): 155 | """Collects one edition of a token that has already been swapped. 156 | 157 | """ 158 | # Define the input parameter data type 159 | sp.set_type(swap_id, sp.TNat) 160 | 161 | # Check that collects are not paused 162 | sp.verify(~self.data.collects_paused, message="MP_COLLECTS_PAUSED") 163 | 164 | # Check that the swap id is present in the swaps big map 165 | sp.verify(self.data.swaps.contains(swap_id), message="MP_WRONG_SWAP_ID") 166 | 167 | # Check that the collector is not the creator of the swap 168 | swap = sp.local("swap", self.data.swaps[swap_id]) 169 | sp.verify(sp.sender != swap.value.issuer, message="MP_IS_SWAP_ISSUER") 170 | 171 | # Check that there is at least one edition available to collect 172 | sp.verify(swap.value.editions > 0, message="MP_SWAP_COLLECTED") 173 | 174 | # Check that the provided mutez amount is exactly the edition price 175 | sp.verify(sp.amount == swap.value.price, message="MP_WRONG_TEZ_AMOUNT") 176 | 177 | # Handle tez tranfers if the edition price is not zero 178 | with sp.if_(sp.amount != sp.mutez(0)): 179 | # Get the royalties information from the FA2 token contract 180 | royalties = sp.local( 181 | "royalties", self.get_token_royalties(swap.value.token_id)) 182 | 183 | # Send the royalties to the token minter 184 | minter_royalties_amount = sp.local( 185 | "minter_royalties_amount", sp.split_tokens( 186 | sp.amount, royalties.value.minter.royalties, 1000)) 187 | 188 | with sp.if_(minter_royalties_amount.value > sp.mutez(0)): 189 | sp.send(royalties.value.minter.address, 190 | minter_royalties_amount.value) 191 | 192 | # Send the royalties to the token creator 193 | creator_royalties_amount = sp.local( 194 | "creator_royalties_amount", sp.split_tokens( 195 | sp.amount, royalties.value.creator.royalties, 1000)) 196 | 197 | with sp.if_(creator_royalties_amount.value > sp.mutez(0)): 198 | sp.send(royalties.value.creator.address, 199 | creator_royalties_amount.value) 200 | 201 | # Send the management fees 202 | fee_amount = sp.local( 203 | "fee_amount", sp.split_tokens(sp.amount, self.data.fee, 1000)) 204 | 205 | with sp.if_(fee_amount.value > sp.mutez(0)): 206 | sp.send(self.data.fee_recipient, fee_amount.value) 207 | 208 | # Send the donations 209 | donation_amount = sp.local("donation_amount", sp.mutez(0)) 210 | total_donations_amount = sp.local( 211 | "total_donations_amount", sp.mutez(0)) 212 | 213 | with sp.for_("org_donation", swap.value.donations) as org_donation: 214 | donation_amount.value = sp.split_tokens( 215 | sp.amount, org_donation.donation, 1000) 216 | 217 | with sp.if_(donation_amount.value > sp.mutez(0)): 218 | sp.send(org_donation.address, donation_amount.value) 219 | total_donations_amount.value += donation_amount.value 220 | 221 | # Send what is left to the swap issuer 222 | sp.send(swap.value.issuer, 223 | sp.amount - 224 | minter_royalties_amount.value - 225 | creator_royalties_amount.value - 226 | fee_amount.value - 227 | total_donations_amount.value) 228 | 229 | # Transfer the token edition to the collector 230 | self.fa2_transfer( 231 | fa2=self.data.fa2, 232 | from_=sp.self_address, 233 | to_=sp.sender, 234 | token_id=swap.value.token_id, 235 | token_amount=1) 236 | 237 | # Update the number of editions available in the swaps big map 238 | self.data.swaps[swap_id].editions = sp.as_nat(swap.value.editions - 1) 239 | 240 | @sp.entry_point 241 | def cancel_swap(self, swap_id): 242 | """Cancels an existing swap. 243 | 244 | """ 245 | # Define the input parameter data type 246 | sp.set_type(swap_id, sp.TNat) 247 | 248 | # Check that no tez have been transferred 249 | self.check_no_tez_transfer() 250 | 251 | # Check that the swap id is present in the swaps big map 252 | sp.verify(self.data.swaps.contains(swap_id), message="MP_WRONG_SWAP_ID") 253 | 254 | # Check that the swap issuer is cancelling the swap 255 | swap = sp.local("swap", self.data.swaps[swap_id]) 256 | sp.verify(sp.sender == swap.value.issuer, message="MP_NOT_SWAP_ISSUER") 257 | 258 | # Check that there is at least one swapped edition 259 | sp.verify(swap.value.editions > 0, message="MP_SWAP_COLLECTED") 260 | 261 | # Transfer the remaining token editions back to the owner 262 | self.fa2_transfer( 263 | fa2=self.data.fa2, 264 | from_=sp.self_address, 265 | to_=sp.sender, 266 | token_id=swap.value.token_id, 267 | token_amount=swap.value.editions) 268 | 269 | # Delete the swap entry in the the swaps big map 270 | del self.data.swaps[swap_id] 271 | 272 | @sp.entry_point 273 | def update_fee(self, new_fee): 274 | """Updates the marketplace management fees. 275 | 276 | """ 277 | # Define the input parameter data type 278 | sp.set_type(new_fee, sp.TNat) 279 | 280 | # Check that the administrator executed the entry point 281 | self.check_is_administrator() 282 | 283 | # Check that no tez have been transferred 284 | self.check_no_tez_transfer() 285 | 286 | # Check that the new fee is not larger than 25% 287 | sp.verify(new_fee <= 250, message="MP_WRONG_FEES") 288 | 289 | # Set the new management fee 290 | self.data.fee = new_fee 291 | 292 | @sp.entry_point 293 | def update_fee_recipient(self, new_fee_recipient): 294 | """Updates the marketplace management fee recipient address. 295 | 296 | """ 297 | # Define the input parameter data type 298 | sp.set_type(new_fee_recipient, sp.TAddress) 299 | 300 | # Check that the administrator executed the entry point 301 | self.check_is_administrator() 302 | 303 | # Check that no tez have been transferred 304 | self.check_no_tez_transfer() 305 | 306 | # Set the new management fee recipient address 307 | self.data.fee_recipient = new_fee_recipient 308 | 309 | @sp.entry_point 310 | def transfer_administrator(self, proposed_administrator): 311 | """Proposes to transfer the contract administrator to another address. 312 | 313 | """ 314 | # Define the input parameter data type 315 | sp.set_type(proposed_administrator, sp.TAddress) 316 | 317 | # Check that the administrator executed the entry point 318 | self.check_is_administrator() 319 | 320 | # Check that no tez have been transferred 321 | self.check_no_tez_transfer() 322 | 323 | # Set the new proposed administrator address 324 | self.data.proposed_administrator = sp.some(proposed_administrator) 325 | 326 | @sp.entry_point 327 | def accept_administrator(self): 328 | """The proposed administrator accepts the contract administrator 329 | responsabilities. 330 | 331 | """ 332 | # Check that there is a proposed administrator 333 | sp.verify(self.data.proposed_administrator.is_some(), 334 | message="MP_NO_NEW_ADMIN") 335 | 336 | # Check that the proposed administrator executed the entry point 337 | sp.verify(sp.sender == self.data.proposed_administrator.open_some(), 338 | message="MP_NOT_PROPOSED_ADMIN") 339 | 340 | # Check that no tez have been transferred 341 | self.check_no_tez_transfer() 342 | 343 | # Set the new administrator address 344 | self.data.administrator = sp.sender 345 | 346 | # Reset the proposed administrator value 347 | self.data.proposed_administrator = sp.none 348 | 349 | @sp.entry_point 350 | def set_pause_swaps(self, pause): 351 | """Pause or not the swaps. 352 | 353 | """ 354 | # Define the input parameter data type 355 | sp.set_type(pause, sp.TBool) 356 | 357 | # Check that the administrator executed the entry point 358 | self.check_is_administrator() 359 | 360 | # Check that no tez have been transferred 361 | self.check_no_tez_transfer() 362 | 363 | # Pause or unpause the swaps 364 | self.data.swaps_paused = pause 365 | 366 | @sp.entry_point 367 | def set_pause_collects(self, pause): 368 | """Pause or not the collects. 369 | 370 | """ 371 | # Define the input parameter data type 372 | sp.set_type(pause, sp.TBool) 373 | 374 | # Check that the administrator executed the entry point 375 | self.check_is_administrator() 376 | 377 | # Check that no tez have been transferred 378 | self.check_no_tez_transfer() 379 | 380 | # Pause or unpause the collects 381 | self.data.collects_paused = pause 382 | 383 | @sp.onchain_view() 384 | def get_administrator(self): 385 | """Returns the marketplace administrator address. 386 | 387 | """ 388 | sp.result(self.data.administrator) 389 | 390 | @sp.onchain_view() 391 | def has_swap(self, swap_id): 392 | """Check if a given swap id is present in the swaps big map. 393 | 394 | """ 395 | # Define the input parameter data type 396 | sp.set_type(swap_id, sp.TNat) 397 | 398 | # Return True if the swap id is present in the swaps big map 399 | sp.result(self.data.swaps.contains(swap_id)) 400 | 401 | @sp.onchain_view() 402 | def get_swap(self, swap_id): 403 | """Returns the complete information from a given swap id. 404 | 405 | """ 406 | # Define the input parameter data type 407 | sp.set_type(swap_id, sp.TNat) 408 | 409 | # Check that the swap id is present in the swaps big map 410 | sp.verify(self.data.swaps.contains(swap_id), message="MP_WRONG_SWAP_ID") 411 | 412 | # Return the swap information 413 | sp.result(self.data.swaps[swap_id]) 414 | 415 | @sp.onchain_view() 416 | def get_swaps_counter(self): 417 | """Returns the swaps counter. 418 | 419 | """ 420 | sp.result(self.data.counter) 421 | 422 | @sp.onchain_view() 423 | def get_fee(self): 424 | """Returns the marketplace fee. 425 | 426 | """ 427 | sp.result(self.data.fee) 428 | 429 | @sp.onchain_view() 430 | def get_fee_recipient(self): 431 | """Returns the marketplace fee recipient address. 432 | 433 | """ 434 | sp.result(self.data.fee_recipient) 435 | 436 | def fa2_transfer(self, fa2, from_, to_, token_id, token_amount): 437 | """Transfers a number of editions of a FA2 token between two addresses. 438 | 439 | """ 440 | # Get a handle to the FA2 token transfer entry point 441 | c = sp.contract( 442 | t=sp.TList(sp.TRecord( 443 | from_=sp.TAddress, 444 | txs=sp.TList(sp.TRecord( 445 | to_=sp.TAddress, 446 | token_id=sp.TNat, 447 | amount=sp.TNat).layout(("to_", ("token_id", "amount")))))), 448 | address=fa2, 449 | entry_point="transfer").open_some() 450 | 451 | # Transfer the FA2 token editions to the new address 452 | sp.transfer( 453 | arg=sp.list([sp.record( 454 | from_=from_, 455 | txs=sp.list([sp.record( 456 | to_=to_, 457 | token_id=token_id, 458 | amount=token_amount)]))]), 459 | amount=sp.mutez(0), 460 | destination=c) 461 | 462 | def get_token_royalties(self, token_id): 463 | """Gets the token royalties information calling the FA2 contract 464 | on-chain view. 465 | 466 | """ 467 | return sp.view( 468 | name="token_royalties", 469 | address=self.data.fa2, 470 | param=token_id, 471 | t=sp.TRecord( 472 | minter=MarketplaceContract.USER_ROYALTIES_TYPE, 473 | creator=MarketplaceContract.USER_ROYALTIES_TYPE).layout( 474 | ("minter", "creator")) 475 | ).open_some() 476 | 477 | 478 | sp.add_compilation_target("marketplace", MarketplaceContract( 479 | administrator=sp.address("tz1M9CMEtsXm3QxA7FmMU2Qh7xzsuGXVbcDr"), 480 | metadata=sp.utils.metadata_of_url("ipfs://aaa"), 481 | fa2=sp.address("KT1M9CMEtsXm3QxA7FmMU2Qh7xzsuGXVbcDr"), 482 | fee=sp.nat(25))) 483 | -------------------------------------------------------------------------------- /python/contracts/minterContract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | 4 | class MinterContract(sp.Contract): 5 | """A basic minter contract for the extended FA2 token contract. 6 | 7 | """ 8 | 9 | USER_ROYALTIES_TYPE = sp.TRecord( 10 | # The user address 11 | address=sp.TAddress, 12 | # The user royalties in per mille (100 is 10%) 13 | royalties=sp.TNat).layout( 14 | ("address", "royalties")) 15 | 16 | def __init__(self, administrator, metadata, fa2): 17 | """Initializes the contract. 18 | 19 | """ 20 | # Define the contract storage data types for clarity 21 | self.init_type(sp.TRecord( 22 | # The contract administrador 23 | administrator=sp.TAddress, 24 | # The contract metadata 25 | metadata=sp.TBigMap(sp.TString, sp.TBytes), 26 | # The FA2 token contract address 27 | fa2=sp.TAddress, 28 | # The proposed new administrator address 29 | proposed_administrator=sp.TOption(sp.TAddress), 30 | # Flag to indicate if the contract is paused or not 31 | paused=sp.TBool)) 32 | 33 | # Initialize the contract storage 34 | self.init( 35 | administrator=administrator, 36 | metadata=metadata, 37 | fa2=fa2, 38 | proposed_administrator=sp.none, 39 | paused=False) 40 | 41 | def check_is_administrator(self): 42 | """Checks that the address that called the entry point is the contract 43 | administrator. 44 | 45 | """ 46 | sp.verify(sp.sender == self.data.administrator, 47 | message="MINTER_NOT_ADMIN") 48 | 49 | @sp.entry_point 50 | def mint(self, params): 51 | """Mints a new FA2 token. The minter and the creator are assumed to be 52 | the same person. 53 | 54 | """ 55 | # Define the input parameter data type 56 | sp.set_type(params, sp.TRecord( 57 | editions=sp.TNat, 58 | metadata=sp.TMap(sp.TString, sp.TBytes), 59 | data=sp.TMap(sp.TString, sp.TBytes), 60 | royalties=sp.TNat).layout( 61 | ("editions", ("metadata", ("data", "royalties"))))) 62 | 63 | # Check that the contract is not paused 64 | sp.verify(~self.data.paused, message="MINT_PAUSED") 65 | 66 | # Check that the number of editions is not zero 67 | sp.verify(params.editions != 0, message="MINT_ZERO_EDITIONS") 68 | 69 | # Check that the creator royalties are less than 25% 70 | sp.verify(params.royalties <= 250, message="MINT_INVALID_ROYALTIES") 71 | 72 | # Get a handle on the FA2 contract mint entry point 73 | fa2_mint_handle = sp.contract( 74 | t=sp.TRecord( 75 | amount=sp.TNat, 76 | metadata=sp.TMap(sp.TString, sp.TBytes), 77 | data=sp.TMap(sp.TString, sp.TBytes), 78 | royalties=sp.TRecord( 79 | minter=MinterContract.USER_ROYALTIES_TYPE, 80 | creator=MinterContract.USER_ROYALTIES_TYPE).layout( 81 | ("minter", "creator"))).layout( 82 | ("amount", ("metadata", ("data", "royalties")))), 83 | address=self.data.fa2, 84 | entry_point="mint").open_some() 85 | 86 | # Mint the token 87 | sp.transfer( 88 | arg=sp.record( 89 | amount=params.editions, 90 | metadata=params.metadata, 91 | data=params.data, 92 | royalties=sp.record( 93 | minter=sp.record(address=sp.sender, royalties=0), 94 | creator=sp.record(address=sp.sender, royalties=params.royalties))), 95 | amount=sp.mutez(0), 96 | destination=fa2_mint_handle) 97 | 98 | @sp.entry_point 99 | def transfer_administrator(self, proposed_administrator): 100 | """Proposes to transfer the contract administrator to another address. 101 | 102 | """ 103 | # Define the input parameter data type 104 | sp.set_type(proposed_administrator, sp.TAddress) 105 | 106 | # Check that the administrator executed the entry point 107 | self.check_is_administrator() 108 | 109 | # Set the new proposed administrator address 110 | self.data.proposed_administrator = sp.some(proposed_administrator) 111 | 112 | @sp.entry_point 113 | def accept_administrator(self): 114 | """The proposed administrator accepts the contract administrator 115 | responsabilities. 116 | 117 | """ 118 | # Check that there is a proposed administrator 119 | sp.verify(self.data.proposed_administrator.is_some(), 120 | message="MINTER_NO_NEW_ADMIN") 121 | 122 | # Check that the proposed administrator executed the entry point 123 | sp.verify(sp.sender == self.data.proposed_administrator.open_some(), 124 | message="MINTER_NOT_PROPOSED_ADMIN") 125 | 126 | # Set the new administrator address 127 | self.data.administrator = sp.sender 128 | 129 | # Reset the proposed administrator value 130 | self.data.proposed_administrator = sp.none 131 | 132 | @sp.entry_point 133 | def transfer_fa2_administrator(self, proposed_fa2_administrator): 134 | """Proposes to transfer the FA2 token contract administator to another 135 | minter contract. 136 | 137 | """ 138 | # Define the input parameter data type 139 | sp.set_type(proposed_fa2_administrator, sp.TAddress) 140 | 141 | # Check that the administrator executed the entry point 142 | self.check_is_administrator() 143 | 144 | # Get a handle on the FA2 contract transfer_administator entry point 145 | fa2_transfer_administrator_handle = sp.contract( 146 | t=sp.TAddress, 147 | address=self.data.fa2, 148 | entry_point="transfer_administrator").open_some() 149 | 150 | # Propose to transfer the FA2 token contract administrator 151 | sp.transfer( 152 | arg=proposed_fa2_administrator, 153 | amount=sp.mutez(0), 154 | destination=fa2_transfer_administrator_handle) 155 | 156 | @sp.entry_point 157 | def accept_fa2_administrator(self): 158 | """Accepts the FA2 contract administrator responsabilities. 159 | 160 | """ 161 | # Check that the administrator executed the entry point 162 | self.check_is_administrator() 163 | 164 | # Get a handle on the FA2 contract accept_administator entry point 165 | fa2_accept_administrator_handle = sp.contract( 166 | t=sp.TUnit, 167 | address=self.data.fa2, 168 | entry_point="accept_administrator").open_some() 169 | 170 | # Accept the FA2 token contract administrator responsabilities 171 | sp.transfer( 172 | arg=sp.unit, 173 | amount=sp.mutez(0), 174 | destination=fa2_accept_administrator_handle) 175 | 176 | @sp.entry_point 177 | def set_pause(self, pause): 178 | """Pause or not minting with the contract. 179 | 180 | """ 181 | # Define the input parameter data type 182 | sp.set_type(pause, sp.TBool) 183 | 184 | # Check that the administrator executed the entry point 185 | self.check_is_administrator() 186 | 187 | # Pause or unpause the mints 188 | self.data.paused = pause 189 | 190 | @sp.onchain_view(pure=True) 191 | def is_paused(self): 192 | """Checks if the contract is paused. 193 | 194 | """ 195 | # Return true if the contract is paused 196 | sp.result(self.data.paused) 197 | 198 | 199 | sp.add_compilation_target("Minter", MinterContract( 200 | administrator=sp.address("tz1M9CMEtsXm3QxA7FmMU2Qh7xzsuGXVbcDr"), 201 | metadata=sp.utils.metadata_of_url("ipfs://aaa"), 202 | fa2=sp.address("KT1M9CMEtsXm3QxA7FmMU2Qh7xzsuGXVbcDr"))) 203 | -------------------------------------------------------------------------------- /python/contracts/multisig_metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Teia Community multisig wallet / mini-DAO", 3 | "description": "This contract allows a group of users to administer funds. It can also be used to create, vote and execute proposals.", 4 | "version": "1.0.0", 5 | "license": { 6 | "name": "MIT", 7 | "details": "The MIT License" 8 | }, 9 | "authors": ["Teia Community "], 10 | "homepage": "https://teia.art", 11 | "source": { 12 | "tools": ["SmartPy 0.8.10"], 13 | "location": "https://github.com/jagracar/tezos-smart-contracts/blob/1.0.0/python/contracts/multisignWalletContract.py" 14 | }, 15 | "interfaces": ["TZIP-016"], 16 | "errors": [ 17 | { 18 | "error": { "string": "MS_NOT_USER"}, 19 | "expansion": { "string": "The operation can only be executed by one of the multisig wallet users"}, 20 | "languages": ["en"] 21 | }, 22 | { 23 | "error": { "string": "MS_INEXISTENT_PROPOSAL"}, 24 | "expansion": { "string": "The given proposal id doesn't exist"}, 25 | "languages": ["en"] 26 | }, 27 | { 28 | "error": { "string": "MS_EXECUTED_PROPOSAL"}, 29 | "expansion": { "string": "The proposal has been executed and cannot be voted or executed anymore"}, 30 | "languages": ["en"] 31 | }, 32 | { 33 | "error": { "string": "MS_EXPIRED_PROPOSAL"}, 34 | "expansion": { "string": "The proposal has expired and cannot be voted or executed anymore"}, 35 | "languages": ["en"] 36 | }, 37 | { 38 | "error": { "string": "MS_WRONG_MINIMUM_VOTES"}, 39 | "expansion": { "string": "The minimum_votes parameter cannot be smaller than 1 or higher than the number of users"}, 40 | "languages": ["en"] 41 | }, 42 | { 43 | "error": { "string": "MS_WRONG_EXPIRATION_TIME"}, 44 | "expansion": { "string": "The expiration_time parameter cannot be smaller than 1 day"}, 45 | "languages": ["en"] 46 | }, 47 | { 48 | "error": { "string": "MS_ALREADY_USER"}, 49 | "expansion": { "string": "The proposed address is already a multsig user"}, 50 | "languages": ["en"] 51 | }, 52 | { 53 | "error": { "string": "MS_WRONG_USER"}, 54 | "expansion": { "string": "The proposed address is not a multisig user"}, 55 | "languages": ["en"] 56 | }, 57 | { 58 | "error": { "string": "MS_NOT_EXECUTABLE"}, 59 | "expansion": { "string": "The proposal didn't receive enough positive votes to be executed"}, 60 | "languages": ["en"] 61 | }, 62 | { 63 | "error": { "string": "MS_LAST_USER"}, 64 | "expansion": { "string": "The last user cannot be removed"}, 65 | "languages": ["en"] 66 | }, 67 | { 68 | "error": { "string": "MS_NO_USER_VOTE"}, 69 | "expansion": { "string": "The user didn't vote for the proposal"}, 70 | "languages": ["en"] 71 | } 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /python/contracts/nonCustodialBarterContract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | 4 | class NonCustodialBarterContract(sp.Contract): 5 | """This contract implements a simple non-custodial barter contract where 6 | users can trade FA2 tokens for other FA2 tokens. 7 | 8 | """ 9 | 10 | TOKEN_LIST_TYPE = sp.TList(sp.TRecord( 11 | # The FA2 token contract address 12 | fa2=sp.TAddress, 13 | # The FA2 token id 14 | id=sp.TNat, 15 | # The number of editions to trade 16 | amount=sp.TNat).layout(("fa2", ("id", "amount")))) 17 | 18 | TRADE_TYPE = sp.TRecord( 19 | # Flag to indicate if the trade has been exectuded 20 | executed=sp.TBool, 21 | # Flag to indicate if the trade has been cancelled 22 | cancelled=sp.TBool, 23 | # The first user involved in the trade 24 | user1=sp.TAddress, 25 | # The second user involved in the trade 26 | user2=sp.TOption(sp.TAddress), 27 | # The first user tokens to trade 28 | tokens1=TOKEN_LIST_TYPE, 29 | # The second user tokens to trade 30 | tokens2=TOKEN_LIST_TYPE).layout( 31 | ("executed", ("cancelled", ("user1", ("user2", ("tokens1", "tokens2")))))) 32 | 33 | def __init__(self, metadata): 34 | """Initializes the contract. 35 | 36 | """ 37 | # Define the contract storage data types for clarity 38 | self.init_type(sp.TRecord( 39 | metadata=sp.TBigMap(sp.TString, sp.TBytes), 40 | trades=sp.TBigMap(sp.TNat, NonCustodialBarterContract.TRADE_TYPE), 41 | counter=sp.TNat)) 42 | 43 | # Initialize the contract storage 44 | self.init( 45 | metadata=metadata, 46 | trades=sp.big_map(), 47 | counter=0) 48 | 49 | def check_no_tez_transfer(self): 50 | """Checks that no tez were transferred in the operation. 51 | 52 | """ 53 | sp.verify(sp.amount == sp.tez(0), 54 | message="The operation does not need tez transfers") 55 | 56 | def check_trade_still_open(self, trade_id): 57 | """Checks that the trade id corresponds to an existing trade and that 58 | the trade is still open (not executed and not cancelled). 59 | 60 | """ 61 | # Check that the trade id is present in the trades big map 62 | sp.verify(self.data.trades.contains(trade_id), 63 | message="The provided trade id doesn't exist") 64 | 65 | # Check that the trade was not executed 66 | sp.verify(~self.data.trades[trade_id].executed, 67 | message="The trade was executed") 68 | 69 | # Check that the trade was not cancelled 70 | sp.verify(~self.data.trades[trade_id].cancelled, 71 | message="The trade was cancelled") 72 | 73 | @sp.entry_point 74 | def propose_trade(self, trade_proposal): 75 | """Proposes a trade between two users. 76 | 77 | """ 78 | # Define the input parameter data type 79 | sp.set_type(trade_proposal, sp.TRecord( 80 | tokens=NonCustodialBarterContract.TOKEN_LIST_TYPE, 81 | for_tokens=NonCustodialBarterContract.TOKEN_LIST_TYPE, 82 | with_user=sp.TOption(sp.TAddress)).layout( 83 | ("tokens", ("for_tokens", "with_user")))) 84 | 85 | # Check that no tez have been transferred 86 | self.check_no_tez_transfer() 87 | 88 | # Check that the trade will involve at least one edition of each token 89 | with sp.for_("token", trade_proposal.tokens) as token: 90 | sp.verify(token.amount >= 0, 91 | message="At least one token edition needs to be traded") 92 | 93 | with sp.for_("token", trade_proposal.for_tokens) as token: 94 | sp.verify(token.amount >= 0, 95 | message="At least one token edition needs to be traded") 96 | 97 | # Update the trades bigmap with the new trade information 98 | self.data.trades[self.data.counter] = sp.record( 99 | executed=False, 100 | cancelled=False, 101 | user1=sp.sender, 102 | user2=trade_proposal.with_user, 103 | tokens1=trade_proposal.tokens, 104 | tokens2=trade_proposal.for_tokens) 105 | 106 | # Increase the trades counter 107 | self.data.counter += 1 108 | 109 | @sp.entry_point 110 | def accept_trade(self, trade_id): 111 | """Accepts and executes a trade. 112 | 113 | """ 114 | # Define the input parameter data type 115 | sp.set_type(trade_id, sp.TNat) 116 | 117 | # Check that the trade is still open 118 | self.check_trade_still_open(trade_id) 119 | 120 | # Check that no tez have been transferred 121 | self.check_no_tez_transfer() 122 | 123 | # Check that the sender is the trade second user 124 | trade = self.data.trades[trade_id] 125 | 126 | with sp.if_(trade.user2.is_some()): 127 | sp.verify(sp.sender == trade.user2.open_some(), 128 | message="Only user2 can accept the trade") 129 | with sp.else_(): 130 | # Set the sender as the trade second user 131 | trade.user2 = sp.some(sp.sender) 132 | 133 | # Set the trade as executed 134 | trade.executed = True 135 | 136 | # Transfer the second user tokens to the first user 137 | self.transfer_tokens( 138 | from_=sp.sender, 139 | to_=trade.user1, 140 | tokens=trade.tokens2) 141 | 142 | # Transfer the first user tokens to the second user 143 | self.transfer_tokens( 144 | from_=trade.user1, 145 | to_=sp.sender, 146 | tokens=trade.tokens1) 147 | 148 | @sp.entry_point 149 | def cancel_trade(self, trade_id): 150 | """Cancels a proposed trade. 151 | 152 | """ 153 | # Define the input parameter data type 154 | sp.set_type(trade_id, sp.TNat) 155 | 156 | # Check that the trade is still open 157 | self.check_trade_still_open(trade_id) 158 | 159 | # Check that no tez have been transferred 160 | self.check_no_tez_transfer() 161 | 162 | # Check that the sender is the trade first user 163 | trade = self.data.trades[trade_id] 164 | sp.verify(sp.sender == trade.user1, 165 | message="Only user1 can cancel the trade") 166 | 167 | # Set the trade as cancelled 168 | trade.cancelled = True 169 | 170 | def transfer_tokens(self, from_, to_, tokens): 171 | """Transfers a list of FA2 tokens between two addresses. 172 | 173 | """ 174 | with sp.for_("token", tokens) as token: 175 | self.fa2_transfer( 176 | fa2=token.fa2, 177 | from_=from_, 178 | to_=to_, 179 | token_id=token.id, 180 | token_amount=token.amount) 181 | 182 | def fa2_transfer(self, fa2, from_, to_, token_id, token_amount): 183 | """Transfers a number of editions of a FA2 token between two addresses. 184 | 185 | """ 186 | # Get a handle to the FA2 token transfer entry point 187 | c = sp.contract( 188 | t=sp.TList(sp.TRecord( 189 | from_=sp.TAddress, 190 | txs=sp.TList(sp.TRecord( 191 | to_=sp.TAddress, 192 | token_id=sp.TNat, 193 | amount=sp.TNat).layout(("to_", ("token_id", "amount")))))), 194 | address=fa2, 195 | entry_point="transfer").open_some() 196 | 197 | # Transfer the FA2 token editions to the new address 198 | sp.transfer( 199 | arg=sp.list([sp.record( 200 | from_=from_, 201 | txs=sp.list([sp.record( 202 | to_=to_, 203 | token_id=token_id, 204 | amount=token_amount)]))]), 205 | amount=sp.mutez(0), 206 | destination=c) 207 | 208 | 209 | # Add a compilation target 210 | sp.add_compilation_target("nonCustodialBarter", NonCustodialBarterContract( 211 | metadata=sp.utils.metadata_of_url("ipfs://QmVg6rZq5e4JiFZKGyAFLZxwUC6B3edyJvEqatbA5o5Q5R"))) 212 | -------------------------------------------------------------------------------- /python/contracts/patientContract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | 4 | class PatientContract(sp.Contract): 5 | """This contract implements a basic patient account. 6 | 7 | A patient might get sick and the way to be cured is to go to their doctor 8 | and get some medicament. 9 | 10 | """ 11 | 12 | def __init__(self, doctor): 13 | """Initializes the contract. 14 | 15 | """ 16 | # Define the contract storage data types for clarity 17 | self.init_type(sp.TRecord( 18 | doctor=sp.TAddress, 19 | illness=sp.TOption(sp.TRecord( 20 | name=sp.TString, 21 | medicament=sp.TOption(sp.TString), 22 | cured=sp.TBool)))) 23 | 24 | # Initialize the contract storage 25 | self.init(doctor=doctor, illness=sp.none) 26 | 27 | @sp.entry_point 28 | def get_sick(self, params): 29 | """The patient gets a new illness. 30 | 31 | """ 32 | # Define the input parameter data type 33 | sp.set_type(params, sp.TString) 34 | 35 | # Check that the patient is not already ill 36 | sp.verify( 37 | ~self.data.illness.is_some() | self.data.illness.open_some().cured) 38 | 39 | # Set the patient illness 40 | self.data.illness = sp.some( 41 | sp.record(name=params, medicament=sp.none, cured=False)) 42 | 43 | @sp.entry_point 44 | def get_medicament(self, params): 45 | """The patient gets some medicament from the doctor. 46 | 47 | """ 48 | # Define the input parameter data type 49 | sp.set_type(params, sp.TString) 50 | 51 | # Make sure the medicament comes from the doctor 52 | sp.verify(sp.sender == self.data.doctor) 53 | 54 | # Update the patient illness 55 | name = self.data.illness.open_some().name 56 | self.data.illness = sp.some( 57 | sp.record(name=name, medicament=sp.some(params), cured=True)) 58 | 59 | @sp.entry_point 60 | def visit_doctor(self): 61 | """The patient visits the doctor. 62 | 63 | """ 64 | # Pass the illness information to the doctor and wait for the treatment 65 | doctor = sp.contract( 66 | sp.TString, self.data.doctor, 67 | entry_point="treat_illness").open_some() 68 | sp.transfer(self.data.illness.open_some().name, sp.mutez(0), doctor) 69 | 70 | 71 | # Add a compilation target 72 | sp.add_compilation_target("patient", PatientContract(sp.address("KT111"))) 73 | -------------------------------------------------------------------------------- /python/contracts/pingPongContract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | 4 | class PlayerContract(sp.Contract): 5 | """This contract implements a basic ping-pong player. 6 | 7 | """ 8 | 9 | def __init__(self, player): 10 | """Initializes the contract. 11 | 12 | """ 13 | # Define the contract storage data types for clarity 14 | self.init_type(sp.TRecord( 15 | player=sp.TAddress, 16 | games=sp.TMap(sp.TNat, sp.TRecord( 17 | court=sp.TAddress, 18 | opponent=sp.TAddress, 19 | ball_hits=sp.TNat)), 20 | moves=sp.TMap(sp.TString, sp.TString))) 21 | 22 | # Initialize the contract storage 23 | self.init( 24 | player=player, 25 | games=sp.map(), 26 | moves={"ping": "pong", "pong": "ping"}) 27 | 28 | @sp.entry_point 29 | def add_game(self, params): 30 | """Adds a game to the player games map. 31 | 32 | """ 33 | # Define the input parameter data types 34 | sp.set_type(params.game_id, sp.TNat) 35 | sp.set_type(params.court, sp.TAddress) 36 | sp.set_type(params.opponent, sp.TAddress) 37 | 38 | # Check that the player called the entry point 39 | sp.verify(sp.sender == self.data.player) 40 | 41 | # Check that the game has not been added before 42 | sp.verify(~self.data.games.contains(params.game_id)) 43 | 44 | # Add the game to the games map 45 | self.data.games[params.game_id] = sp.record( 46 | court=params.court, 47 | opponent=params.opponent, 48 | ball_hits=0) 49 | 50 | # Accept the game at the court 51 | accept_game = sp.contract( 52 | sp.TRecord(game_id=sp.TNat, accept=sp.TBool), params.court, 53 | "accept_game").open_some() 54 | sp.transfer(sp.record( 55 | game_id=params.game_id, accept=True), sp.mutez(0), accept_game) 56 | 57 | @sp.entry_point 58 | def reset_game(self, params): 59 | """Resets the game counters. 60 | 61 | """ 62 | # Define the input parameter data type 63 | sp.set_type(params, sp.TNat) 64 | 65 | # Check that the court called the entry point 66 | game = self.data.games[params] 67 | sp.verify(sp.sender == game.court) 68 | 69 | # Reset the ball hits counter 70 | game.ball_hits = 0 71 | 72 | @sp.entry_point 73 | def play_game(self, params): 74 | """Plays a ping-pong game that has been previously registered in the 75 | ping-pong court. 76 | 77 | """ 78 | # Define the input parameter data type 79 | sp.set_type(params, sp.TNat) 80 | 81 | # Check that the player called the entry point 82 | sp.verify(sp.sender == self.data.player) 83 | 84 | # Send the request to play a game to the court 85 | play_game = sp.contract( 86 | sp.TNat, self.data.games[params].court, "play_game").open_some() 87 | sp.transfer(params, sp.mutez(0), play_game) 88 | 89 | @sp.entry_point 90 | def serve_ball(self, params): 91 | """Serves the ball. 92 | 93 | """ 94 | # Define the input parameter data type 95 | sp.set_type(params, sp.TNat) 96 | 97 | # Check that the court called the entry point 98 | game = self.data.games[params] 99 | sp.verify(sp.sender == game.court) 100 | 101 | # Update the ball hits counter 102 | game.ball_hits += 1 103 | 104 | # Send the ball to the other player 105 | receive_ball = sp.contract( 106 | sp.TRecord(game_id=sp.TNat, kind=sp.TString), 107 | game.opponent, "receive_ball").open_some() 108 | sp.transfer( 109 | sp.record(game_id=params, kind="ping"), 110 | sp.mutez(0), receive_ball) 111 | 112 | @sp.entry_point 113 | def receive_ball(self, params): 114 | """Receives the ball from the opponent player and tries to return it. 115 | 116 | """ 117 | # Define the input parameter data types 118 | sp.set_type(params.game_id, sp.TNat) 119 | sp.set_type(params.kind, sp.TString) 120 | 121 | # Check that the opponent called the entry point 122 | game = self.data.games[params.game_id] 123 | sp.verify(sp.sender == game.opponent) 124 | 125 | # Check if the opponent made a mistake 126 | sp.if params.kind == "ouch": 127 | # The player won the game. Send the game result to the court 128 | game_winner = sp.contract( 129 | sp.TNat, game.court, "game_winner").open_some() 130 | sp.transfer(params.game_id, sp.mutez(0), game_winner) 131 | sp.else: 132 | # Update the ball hits counter 133 | game.ball_hits += 1 134 | 135 | # Send the ball back to the opponent 136 | receive_ball = sp.contract( 137 | sp.TRecord(game_id=sp.TNat, kind=sp.TString), 138 | game.opponent, "receive_ball").open_some() 139 | 140 | sp.if game.ball_hits >= 3: 141 | # After more than 3 ball hits the player is tired and makes a 142 | # mistake... 143 | sp.transfer( 144 | sp.record(game_id=params.game_id, kind="ouch"), 145 | sp.mutez(0), receive_ball) 146 | sp.else: 147 | sp.transfer( 148 | sp.record( 149 | game_id=params.game_id, 150 | kind=self.data.moves[params.kind]), 151 | sp.mutez(0), receive_ball) 152 | 153 | 154 | class CourtContract(sp.Contract): 155 | """This contract implements a ping-pong court where ping-pong games can be 156 | played between two players. 157 | 158 | """ 159 | 160 | def __init__(self): 161 | """Initializes the contract. 162 | 163 | """ 164 | # Define the contract storage data types for clarity 165 | self.init_type(sp.TRecord( 166 | games=sp.TMap(sp.TNat, sp.TRecord( 167 | players=sp.TMap(sp.TAddress, sp.TRecord( 168 | accepted=sp.TBool, 169 | opponent=sp.TAddress, 170 | victories=sp.TNat)), 171 | started=sp.TBool, 172 | played_games=sp.TNat)))) 173 | 174 | # Initialize the contract storage 175 | self.init(games=sp.map()) 176 | 177 | @sp.entry_point 178 | def register_game(self, params): 179 | """Registers a ping-pong game between two players. 180 | 181 | """ 182 | # Define the input parameter data types 183 | sp.set_type(params.game_id, sp.TNat) 184 | sp.set_type(params.player_1, sp.TAddress) 185 | sp.set_type(params.player_2, sp.TAddress) 186 | 187 | # Check that the game has not been registered before 188 | sp.verify(~self.data.games.contains(params.game_id)) 189 | 190 | # Register the game in the games map 191 | self.data.games[params.game_id] = sp.record( 192 | players={ 193 | params.player_1: sp.record( 194 | accepted=False, opponent=params.player_2, victories=0), 195 | params.player_2: sp.record( 196 | accepted=False, opponent=params.player_1, victories=0)}, 197 | started=False, 198 | played_games=0) 199 | 200 | @sp.entry_point 201 | def accept_game(self, params): 202 | """The player gives his acceptance or not about a registered ping-pong 203 | game. 204 | 205 | """ 206 | # Define the input parameter data types 207 | sp.set_type(params.game_id, sp.TNat) 208 | sp.set_type(params.accept, sp.TBool) 209 | 210 | # Check that the sender is one of the game players 211 | game = self.data.games[params.game_id] 212 | sp.verify(game.players.contains(sp.sender)) 213 | 214 | # Save the player acceptance 215 | game.players[sp.sender].accepted = params.accept 216 | 217 | @sp.entry_point 218 | def play_game(self, params): 219 | """Plays a ping-pong game in the court between the two players. 220 | 221 | """ 222 | # Define the input parameter data type 223 | sp.set_type(params, sp.TNat) 224 | 225 | # Check that one of the players called the entry point 226 | game = self.data.games[params] 227 | sp.verify(game.players.contains(sp.sender)) 228 | 229 | # Check that both players accecpted to play the game 230 | sp.verify(game.players[sp.sender].accepted) 231 | opponent = game.players[sp.sender].opponent 232 | sp.verify(game.players[opponent].accepted) 233 | 234 | # Check that the game didn't start yet 235 | sp.verify(~game.started) 236 | 237 | # Reset the players counters 238 | reset_game_sender = sp.contract( 239 | sp.TNat, sp.sender, "reset_game").open_some() 240 | sp.transfer(params, sp.mutez(0), reset_game_sender) 241 | reset_game_opponent = sp.contract( 242 | sp.TNat, opponent, "reset_game").open_some() 243 | sp.transfer(params, sp.mutez(0), reset_game_opponent) 244 | 245 | # Set the game as started and increase the played games counter 246 | game.started = True 247 | game.played_games += 1 248 | 249 | # Order the sender to serve the ball 250 | serve_ball = sp.contract(sp.TNat, sp.sender, "serve_ball").open_some() 251 | sp.transfer(params, sp.mutez(0), serve_ball) 252 | 253 | @sp.entry_point 254 | def game_winner(self, params): 255 | """Informs the caller is the winner of the game. 256 | 257 | """ 258 | # Define the input parameter data types 259 | sp.set_type(params, sp.TNat) 260 | 261 | # Check that one of the players called the entry point 262 | game = self.data.games[params] 263 | sp.verify(game.players.contains(sp.sender)) 264 | 265 | # Check that the game was started 266 | sp.verify(game.started) 267 | 268 | # Save the game result 269 | game.players[sp.sender].victories += 1 270 | 271 | # Set the game as finished 272 | game.started = False 273 | 274 | 275 | # Add a compilation target 276 | sp.add_compilation_target("court", CourtContract()) 277 | sp.add_compilation_target("player", PlayerContract(sp.address("tz111"))) 278 | -------------------------------------------------------------------------------- /python/contracts/seedsContract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | 4 | class SeedsContract(sp.Contract): 5 | """This contract implements a simple contract where users can name their 6 | own random seeds. 7 | 8 | """ 9 | 10 | def __init__(self): 11 | """Initializes the contract. 12 | 13 | """ 14 | # Define the contract storage data types for clarity 15 | self.init_type(sp.TRecord( 16 | seeds=sp.TBigMap(sp.TNat, sp.TString))) 17 | 18 | # Initialize the contract storage 19 | self.init(seeds=sp.big_map({42: "The answer to everything"})) 20 | 21 | @sp.entry_point 22 | def name_seed(self, params): 23 | """Names a seed. 24 | 25 | """ 26 | # Define the input parameter data type 27 | sp.set_type(params, sp.TRecord( 28 | seed=sp.TNat, 29 | name=sp.TString).layout(("seed", "name"))) 30 | 31 | # Check that the seeds doesn't have already a name 32 | sp.verify(~self.data.seeds.contains(params.seed), 33 | message="The seed already has a name") 34 | 35 | # Check that the name is not too long 36 | sp.verify(sp.len(params.name) <= 64, 37 | message="The name cannot be longer than 64 characters") 38 | 39 | # Check that no tez have been transferred 40 | sp.verify(sp.amount == sp.tez(0), 41 | message="The operation does not need tez transfers") 42 | 43 | # Add the new seed 44 | self.data.seeds[params.seed] = params.name 45 | 46 | 47 | # Add a compilation target 48 | sp.add_compilation_target("seedsContract", SeedsContract()) 49 | -------------------------------------------------------------------------------- /python/contracts/simpleBarterContract.py: -------------------------------------------------------------------------------- 1 | import smartpy as sp 2 | 3 | 4 | class SimpleBarterContract(sp.Contract): 5 | """This contract implements a simple barter contract where users can trade 6 | FA2 tokens for other FA2 tokens. 7 | 8 | """ 9 | 10 | TOKEN_LIST_TYPE = sp.TList(sp.TRecord( 11 | # The FA2 token contract address 12 | fa2=sp.TAddress, 13 | # The FA2 token id 14 | id=sp.TNat, 15 | # The number of editions to trade 16 | amount=sp.TNat).layout(("fa2", ("id", "amount")))) 17 | 18 | TRADE_TYPE = sp.TRecord( 19 | # Flag to indicate if the trade has been exectuded 20 | executed=sp.TBool, 21 | # Flag to indicate if the trade has been cancelled 22 | cancelled=sp.TBool, 23 | # The first user involved in the trade 24 | user1=sp.TAddress, 25 | # The second user involved in the trade 26 | user2=sp.TOption(sp.TAddress), 27 | # The first user tokens to trade 28 | tokens1=TOKEN_LIST_TYPE, 29 | # The second user tokens to trade 30 | tokens2=TOKEN_LIST_TYPE).layout( 31 | ("executed", ("cancelled", ("user1", ("user2", ("tokens1", "tokens2")))))) 32 | 33 | def __init__(self, metadata): 34 | """Initializes the contract. 35 | 36 | """ 37 | # Define the contract storage data types for clarity 38 | self.init_type(sp.TRecord( 39 | metadata=sp.TBigMap(sp.TString, sp.TBytes), 40 | trades=sp.TBigMap(sp.TNat, SimpleBarterContract.TRADE_TYPE), 41 | counter=sp.TNat)) 42 | 43 | # Initialize the contract storage 44 | self.init( 45 | metadata=metadata, 46 | trades=sp.big_map(), 47 | counter=0) 48 | 49 | def check_no_tez_transfer(self): 50 | """Checks that no tez were transferred in the operation. 51 | 52 | """ 53 | sp.verify(sp.amount == sp.tez(0), 54 | message="The operation does not need tez transfers") 55 | 56 | def check_trade_still_open(self, trade_id): 57 | """Checks that the trade id corresponds to an existing trade and that 58 | the trade is still open (not executed and not cancelled). 59 | 60 | """ 61 | # Check that the trade id is present in the trades big map 62 | sp.verify(self.data.trades.contains(trade_id), 63 | message="The provided trade id doesn't exist") 64 | 65 | # Check that the trade was not executed 66 | sp.verify(~self.data.trades[trade_id].executed, 67 | message="The trade was executed") 68 | 69 | # Check that the trade was not cancelled 70 | sp.verify(~self.data.trades[trade_id].cancelled, 71 | message="The trade was cancelled") 72 | 73 | @sp.entry_point 74 | def propose_trade(self, trade_proposal): 75 | """Proposes a trade between two users. 76 | 77 | """ 78 | # Define the input parameter data type 79 | sp.set_type(trade_proposal, sp.TRecord( 80 | tokens=SimpleBarterContract.TOKEN_LIST_TYPE, 81 | for_tokens=SimpleBarterContract.TOKEN_LIST_TYPE, 82 | with_user=sp.TOption(sp.TAddress)).layout( 83 | ("tokens", ("for_tokens", "with_user")))) 84 | 85 | # Check that no tez have been transferred 86 | self.check_no_tez_transfer() 87 | 88 | # Check that the trade will involve at least one edition of each token 89 | sp.for token in trade_proposal.tokens: 90 | sp.verify(token.amount >= 0, 91 | message="At least one token edition needs to be traded") 92 | 93 | sp.for token in trade_proposal.for_tokens: 94 | sp.verify(token.amount >= 0, 95 | message="At least one token edition needs to be traded") 96 | 97 | # Transfer the proposed tokens to the barter account 98 | self.transfer_tokens( 99 | from_=sp.sender, 100 | to_=sp.self_address, 101 | tokens=trade_proposal.tokens) 102 | 103 | # Update the trades bigmap with the new trade information 104 | self.data.trades[self.data.counter] = sp.record( 105 | executed=False, 106 | cancelled=False, 107 | user1=sp.sender, 108 | user2=trade_proposal.with_user, 109 | tokens1=trade_proposal.tokens, 110 | tokens2=trade_proposal.for_tokens) 111 | 112 | # Increase the trades counter 113 | self.data.counter += 1 114 | 115 | @sp.entry_point 116 | def accept_trade(self, trade_id): 117 | """Accepts and executes a trade. 118 | 119 | """ 120 | # Define the input parameter data type 121 | sp.set_type(trade_id, sp.TNat) 122 | 123 | # Check that the trade is still open 124 | self.check_trade_still_open(trade_id) 125 | 126 | # Check that no tez have been transferred 127 | self.check_no_tez_transfer() 128 | 129 | # Check that the sender is the trade second user 130 | trade = self.data.trades[trade_id] 131 | 132 | sp.if trade.user2.is_some(): 133 | sp.verify(sp.sender == trade.user2.open_some(), 134 | message="Only user2 can accept the trade") 135 | sp.else: 136 | # Set the sender as the trade second user 137 | trade.user2 = sp.some(sp.sender) 138 | 139 | # Set the trade as executed 140 | trade.executed = True 141 | 142 | # Transfer the second user tokens to the first user 143 | self.transfer_tokens( 144 | from_=sp.sender, 145 | to_=trade.user1, 146 | tokens=trade.tokens2) 147 | 148 | # Transfer the first user tokens to the second user 149 | self.transfer_tokens( 150 | from_=sp.self_address, 151 | to_=sp.sender, 152 | tokens=trade.tokens1) 153 | 154 | @sp.entry_point 155 | def cancel_trade(self, trade_id): 156 | """Cancels a proposed trade. 157 | 158 | """ 159 | # Define the input parameter data type 160 | sp.set_type(trade_id, sp.TNat) 161 | 162 | # Check that the trade is still open 163 | self.check_trade_still_open(trade_id) 164 | 165 | # Check that no tez have been transferred 166 | self.check_no_tez_transfer() 167 | 168 | # Check that the sender is the trade first user 169 | trade = self.data.trades[trade_id] 170 | sp.verify(sp.sender == trade.user1, 171 | message="Only user1 can cancel the trade") 172 | 173 | # Set the trade as cancelled 174 | trade.cancelled = True 175 | 176 | # Transfer the tokens back to the sender 177 | self.transfer_tokens( 178 | from_=sp.self_address, 179 | to_=sp.sender, 180 | tokens=trade.tokens1) 181 | 182 | def transfer_tokens(self, from_, to_, tokens): 183 | """Transfers a list of FA2 tokens between two addresses. 184 | 185 | """ 186 | sp.for token in tokens: 187 | self.fa2_transfer( 188 | fa2=token.fa2, 189 | from_=from_, 190 | to_=to_, 191 | token_id=token.id, 192 | token_amount=token.amount) 193 | 194 | def fa2_transfer(self, fa2, from_, to_, token_id, token_amount): 195 | """Transfers a number of editions of a FA2 token between two addresses. 196 | 197 | """ 198 | # Get a handle to the FA2 token transfer entry point 199 | c = sp.contract( 200 | t=sp.TList(sp.TRecord( 201 | from_=sp.TAddress, 202 | txs=sp.TList(sp.TRecord( 203 | to_=sp.TAddress, 204 | token_id=sp.TNat, 205 | amount=sp.TNat).layout(("to_", ("token_id", "amount")))))), 206 | address=fa2, 207 | entry_point="transfer").open_some() 208 | 209 | # Transfer the FA2 token editions to the new address 210 | sp.transfer( 211 | arg=sp.list([sp.record( 212 | from_=from_, 213 | txs=sp.list([sp.record( 214 | to_=to_, 215 | token_id=token_id, 216 | amount=token_amount)]))]), 217 | amount=sp.mutez(0), 218 | destination=c) 219 | 220 | 221 | # Add a compilation target 222 | sp.add_compilation_target("simpleBarter", SimpleBarterContract( 223 | metadata=sp.utils.metadata_of_url("ipfs://QmVg6rZq5e4JiFZKGyAFLZxwUC6B3edyJvEqatbA5o5Q5R"))) 224 | -------------------------------------------------------------------------------- /python/tests/barterContract_test.py: -------------------------------------------------------------------------------- 1 | """Unit tests for the BarterContract class. 2 | 3 | """ 4 | 5 | import smartpy as sp 6 | 7 | # Import the barterContract and fa2Contract module 8 | barterContract = sp.io.import_script_from_url( 9 | "file:python/contracts/barterContract.py") 10 | fa2Contract = sp.io.import_script_from_url( 11 | "file:python/templates/fa2Contract.py") 12 | 13 | 14 | def get_test_environment(): 15 | # Initialize the test scenario 16 | scenario = sp.test_scenario() 17 | 18 | # Create the test accounts 19 | user1 = sp.test_account("user1") 20 | user2 = sp.test_account("user2") 21 | admin = sp.test_account("admin") 22 | fa2_admin = sp.test_account("fa2_admin") 23 | 24 | # Initialize the two FA2 contracts 25 | fa2_1 = fa2Contract.FA2( 26 | config=fa2Contract.FA2_config(), 27 | admin=fa2_admin.address, 28 | metadata=sp.utils.metadata_of_url("ipfs://aaa")) 29 | fa2_2 = fa2Contract.FA2( 30 | config=fa2Contract.FA2_config(), 31 | admin=fa2_admin.address, 32 | metadata=sp.utils.metadata_of_url("ipfs://bbb")) 33 | scenario += fa2_1 34 | scenario += fa2_2 35 | 36 | # Initialize the barter contract 37 | barter = barterContract.BarterContract( 38 | manager=admin.address, 39 | allowed_fa2s=sp.big_map({fa2_1.address : True})) 40 | scenario += barter 41 | 42 | # Save all the variables in a test environment dictionary 43 | testEnvironment = { 44 | "scenario" : scenario, 45 | "user1" : user1, 46 | "user2" : user2, 47 | "admin" : admin, 48 | "fa2_admin" : fa2_admin, 49 | "fa2_1" : fa2_1, 50 | "fa2_2" : fa2_2, 51 | "barter" : barter} 52 | 53 | return testEnvironment 54 | 55 | 56 | @sp.add_test(name="Test propose trade") 57 | def test_propose_trade(): 58 | # Get the test environment 59 | testEnvironment = get_test_environment() 60 | user1 = testEnvironment["user1"] 61 | user2 = testEnvironment["user2"] 62 | admin = testEnvironment["admin"] 63 | fa2_1 = testEnvironment["fa2_1"] 64 | fa2_2 = testEnvironment["fa2_2"] 65 | barter = testEnvironment["barter"] 66 | 67 | # Propose a trade that involves only fa2_1 tokens 68 | barter.propose_trade( 69 | user1=user1.address, 70 | user2=user2.address, 71 | mutez_amount=sp.tez(3), 72 | tokens1=sp.list([ 73 | sp.record(fa2=fa2_1.address, id=sp.nat(1), amount=sp.nat(1)), 74 | sp.record(fa2=fa2_1.address, id=sp.nat(2), amount=sp.nat(2))]), 75 | tokens2=sp.list([ 76 | sp.record(fa2=fa2_1.address, id=sp.nat(3), amount=sp.nat(100))])).run(sender=user1) 77 | 78 | # Propose a trade that also involves fa2_2 tokens and check that it fails 79 | barter.propose_trade( 80 | user1=user1.address, 81 | user2=user2.address, 82 | mutez_amount=sp.tez(5), 83 | tokens1=sp.list([ 84 | sp.record(fa2=fa2_1.address, id=sp.nat(1), amount=sp.nat(1)), 85 | sp.record(fa2=fa2_2.address, id=sp.nat(2), amount=sp.nat(2))]), 86 | tokens2=sp.list([ 87 | sp.record(fa2=fa2_1.address, id=sp.nat(3), amount=sp.nat(100))])).run(valid=False, sender=user2) 88 | 89 | # Add the fa2_2 tokens in the list of allowed tokens 90 | barter.add_fa2(fa2_2.address).run(sender=admin) 91 | 92 | # Propose the trade again and check that now it doesn't fail 93 | barter.propose_trade( 94 | user1=user1.address, 95 | user2=user2.address, 96 | mutez_amount=sp.tez(5), 97 | tokens1=sp.list([ 98 | sp.record(fa2=fa2_1.address, id=sp.nat(1), amount=sp.nat(1)), 99 | sp.record(fa2=fa2_2.address, id=sp.nat(2), amount=sp.nat(2))]), 100 | tokens2=sp.list([ 101 | sp.record(fa2=fa2_1.address, id=sp.nat(3), amount=sp.nat(100))])).run(sender=user2) 102 | 103 | 104 | @sp.add_test(name="Test execute trade") 105 | def test_execute_trade(): 106 | # Get the test environment 107 | testEnvironment = get_test_environment() 108 | user1 = testEnvironment["user1"] 109 | user2 = testEnvironment["user2"] 110 | admin = testEnvironment["admin"] 111 | fa2_admin = testEnvironment["fa2_admin"] 112 | fa2_1 = testEnvironment["fa2_1"] 113 | barter = testEnvironment["barter"] 114 | 115 | # Mint some tokens for the involved users 116 | fa2_1.mint( 117 | address=user1.address, 118 | token_id=sp.nat(0), 119 | amount=sp.nat(100), 120 | metadata={"" : sp.utils.bytes_of_string("ipfs://ccc")}).run(sender=fa2_admin) 121 | fa2_1.mint( 122 | address=user1.address, 123 | token_id=sp.nat(1), 124 | amount=sp.nat(100), 125 | metadata={"" : sp.utils.bytes_of_string("ipfs://ddd")}).run(sender=fa2_admin) 126 | fa2_1.mint( 127 | address=user2.address, 128 | token_id=sp.nat(2), 129 | amount=sp.nat(100), 130 | metadata={"" : sp.utils.bytes_of_string("ipfs://eee")}).run(sender=fa2_admin) 131 | 132 | # Add the barter contract as operator for the tokens 133 | fa2_1.update_operators( 134 | [sp.variant("add_operator", fa2_1.operator_param.make( 135 | owner=user1.address, 136 | operator=barter.address, 137 | token_id=0)), 138 | sp.variant("add_operator", fa2_1.operator_param.make( 139 | owner=user1.address, 140 | operator=barter.address, 141 | token_id=1))]).run(sender=user1) 142 | fa2_1.update_operators( 143 | [sp.variant("add_operator", fa2_1.operator_param.make( 144 | owner=user2.address, 145 | operator=barter.address, 146 | token_id=2))]).run(sender=user2) 147 | 148 | # Propose a trade 149 | barter.propose_trade( 150 | user1=user1.address, 151 | user2=user2.address, 152 | mutez_amount=sp.tez(3), 153 | tokens1=sp.list([ 154 | sp.record(fa2=fa2_1.address, id=sp.nat(0), amount=sp.nat(1)), 155 | sp.record(fa2=fa2_1.address, id=sp.nat(1), amount=sp.nat(2))]), 156 | tokens2=sp.list([ 157 | sp.record(fa2=fa2_1.address, id=sp.nat(2), amount=sp.nat(100))])).run(sender=user1) 158 | 159 | # Accept the trade without sending the tez and check that it fails 160 | barter.accept_trade(0).run(valid=False, sender=user1) 161 | 162 | # Accept the trade with the tez included 163 | barter.accept_trade(0).run(sender=user1, amount=sp.tez(3)) 164 | barter.accept_trade(0).run(sender=user2) 165 | 166 | # Execute the trade 167 | barter.execute_trade(0).run(sender=user2) 168 | 169 | 170 | @sp.add_test(name="Test cancel trade") 171 | def test_cancel_trade(): 172 | # Get the test environment 173 | testEnvironment = get_test_environment() 174 | user1 = testEnvironment["user1"] 175 | user2 = testEnvironment["user2"] 176 | admin = testEnvironment["admin"] 177 | fa2_admin = testEnvironment["fa2_admin"] 178 | fa2_1 = testEnvironment["fa2_1"] 179 | barter = testEnvironment["barter"] 180 | 181 | # Mint some tokens for the involved users 182 | fa2_1.mint( 183 | address=user1.address, 184 | token_id=sp.nat(0), 185 | amount=sp.nat(100), 186 | metadata={"" : sp.utils.bytes_of_string("ipfs://ccc")}).run(sender=fa2_admin) 187 | fa2_1.mint( 188 | address=user1.address, 189 | token_id=sp.nat(1), 190 | amount=sp.nat(100), 191 | metadata={"" : sp.utils.bytes_of_string("ipfs://ddd")}).run(sender=fa2_admin) 192 | fa2_1.mint( 193 | address=user2.address, 194 | token_id=sp.nat(2), 195 | amount=sp.nat(100), 196 | metadata={"" : sp.utils.bytes_of_string("ipfs://eee")}).run(sender=fa2_admin) 197 | 198 | # Add the barter contract as operator for the tokens 199 | fa2_1.update_operators( 200 | [sp.variant("add_operator", fa2_1.operator_param.make( 201 | owner=user1.address, 202 | operator=barter.address, 203 | token_id=0)), 204 | sp.variant("add_operator", fa2_1.operator_param.make( 205 | owner=user1.address, 206 | operator=barter.address, 207 | token_id=1))]).run(sender=user1) 208 | fa2_1.update_operators( 209 | [sp.variant("add_operator", fa2_1.operator_param.make( 210 | owner=user2.address, 211 | operator=barter.address, 212 | token_id=2))]).run(sender=user2) 213 | 214 | # Propose a trade 215 | barter.propose_trade( 216 | user1=user1.address, 217 | user2=user2.address, 218 | mutez_amount=sp.tez(3), 219 | tokens1=sp.list([ 220 | sp.record(fa2=fa2_1.address, id=sp.nat(0), amount=sp.nat(1)), 221 | sp.record(fa2=fa2_1.address, id=sp.nat(1), amount=sp.nat(2))]), 222 | tokens2=sp.list([ 223 | sp.record(fa2=fa2_1.address, id=sp.nat(2), amount=sp.nat(100))])).run(sender=user1) 224 | 225 | # Accept the trade 226 | barter.accept_trade(0).run(sender=user1, amount=sp.tez(3)) 227 | barter.accept_trade(0).run(sender=user2) 228 | 229 | # Cancel the trade 230 | barter.cancel_trade(0).run(sender=user1) 231 | barter.cancel_trade(0).run(sender=user2) 232 | -------------------------------------------------------------------------------- /python/tests/doctorContract_test.py: -------------------------------------------------------------------------------- 1 | """Unit tests for the DoctorContract class. 2 | 3 | """ 4 | 5 | import smartpy as sp 6 | 7 | # Import the patientContract and the doctorContract modules 8 | patientContract = sp.io.import_script_from_url( 9 | "file:python/contracts/patientContract.py") 10 | doctorContract = sp.io.import_script_from_url( 11 | "file:python/contracts/doctorContract.py") 12 | 13 | 14 | @sp.add_test(name="Test initialization") 15 | def test_initialization(): 16 | # Initialize the contract 17 | c = doctorContract.DoctorContract() 18 | 19 | # Add the contract to the test scenario 20 | scenario = sp.test_scenario() 21 | scenario += c 22 | 23 | # Check that the information in the contract strorage is correct 24 | scenario.verify(sp.len(c.data.patients) == 0) 25 | scenario.verify(sp.len(c.data.medicaments) == 3) 26 | 27 | 28 | @sp.add_test(name="Test treat illness") 29 | def test_treat_illness(): 30 | # Initialize the test scenario 31 | scenario = sp.test_scenario() 32 | 33 | # Initialize the doctor contract 34 | doctor = doctorContract.DoctorContract() 35 | scenario += doctor 36 | 37 | # Initialize the patient contract 38 | patient = patientContract.PatientContract(doctor.address) 39 | scenario += patient 40 | 41 | # Make the patient sick 42 | illness = "flu" 43 | patient.get_sick(illness) 44 | 45 | # Treat the patient illness 46 | doctor.treat_illness(illness).run(sender=patient.address) 47 | 48 | # Check that the doctor sent the correct medicament and the patient is cured 49 | scenario.verify(patient.data.illness.open_some().name == illness) 50 | scenario.verify( 51 | patient.data.illness.open_some().medicament.open_some() == doctor.data.medicaments[illness]) 52 | scenario.verify(patient.data.illness.open_some().cured) 53 | 54 | # Check that the patient is in the doctor patients list 55 | scenario.verify(doctor.data.patients.contains(patient.address)) 56 | 57 | # Clean the doctor patients list 58 | doctor.clean_patients().run(sender=doctor.address) 59 | scenario.verify(~doctor.data.patients.contains(patient.address)) 60 | -------------------------------------------------------------------------------- /python/tests/lambdaFunctionUtilContract_test.py: -------------------------------------------------------------------------------- 1 | """Unit tests for the LambdaFunctionUtilContract class. 2 | 3 | """ 4 | 5 | import smartpy as sp 6 | 7 | # Import the lambdaFunctionUtilContract module 8 | lambdaFunctionUtilContract = sp.io.import_script_from_url( 9 | "file:python/contracts/lambdaFunctionUtilContract.py") 10 | 11 | 12 | class DummyContract(sp.Contract): 13 | """This is a dummy contract to be used only for test purposes. 14 | 15 | """ 16 | 17 | def __init__(self): 18 | """Initializes the contract. 19 | 20 | """ 21 | self.init(x=sp.nat(0), y=sp.nat(0)) 22 | 23 | @sp.entry_point 24 | def update_x(self, x): 25 | """Updates the x value. 26 | 27 | """ 28 | self.data.x = x 29 | 30 | @sp.entry_point 31 | def update_y(self, y): 32 | """Updates the y value. 33 | 34 | """ 35 | self.data.y = y 36 | 37 | 38 | @sp.add_test(name="Test lambda function") 39 | def test_lambda_function(): 40 | # Create the test account 41 | user = sp.test_account("user") 42 | 43 | # Initialize the dummy contract and the lambda function util contract 44 | dummyContract = DummyContract() 45 | lambdaFunctionUtil = lambdaFunctionUtilContract.LambdaFunctionUtilContract() 46 | 47 | # Add the contracts to the test scenario 48 | scenario = sp.test_scenario() 49 | scenario += dummyContract 50 | scenario += lambdaFunctionUtil 51 | 52 | # Define the lambda function that will update the dummy contract 53 | def lambda_function(params): 54 | sp.set_type(params, sp.TUnit) 55 | contractHandle = sp.contract(sp.TNat, dummyContract.address, "update_x").open_some() 56 | sp.result([sp.transfer_operation(sp.nat(2), sp.mutez(0), contractHandle)]) 57 | 58 | # Update and execute the lambda function 59 | lambdaFunctionUtil.update_and_execute_lambda(lambda_function).run(sender=user) 60 | 61 | # Check that the dummy contract storage has been updated to the correct vale 62 | scenario.verify(dummyContract.data.x == 2) 63 | scenario.verify(dummyContract.data.y == 0) 64 | -------------------------------------------------------------------------------- /python/tests/managerContract_test.py: -------------------------------------------------------------------------------- 1 | """Unit tests for the ManagerContract class. 2 | 3 | """ 4 | 5 | import smartpy as sp 6 | 7 | # Import the managerContract module 8 | managerContract = sp.io.import_script_from_url( 9 | "file:python/contracts/managerContract.py") 10 | 11 | 12 | @sp.add_test(name="Test default initialization") 13 | def test_default_initialization(): 14 | # Define the test account 15 | user = sp.test_account("user") 16 | 17 | # Initialize the contract 18 | c = managerContract.ManagerContract(user.address) 19 | 20 | # Add the contract to the test scenario 21 | scenario = sp.test_scenario() 22 | scenario += c 23 | 24 | # Check that the information in the contract strorage is correct 25 | scenario.verify(c.data.manager == user.address) 26 | scenario.verify(c.data.rescue_time == managerContract.DEFAULT_RESCUE_TIME) 27 | scenario.verify(sp.len(c.data.rescue_accounts) == 0) 28 | scenario.verify( 29 | sp.as_nat(sp.timestamp_from_utc_now() - c.data.last_ping) == 0) 30 | 31 | 32 | @sp.add_test(name="Test initialization with rescue time") 33 | def test_initialization_with_rescue_time(): 34 | # Define the test account 35 | user = sp.test_account("user") 36 | 37 | # Initialize the contract 38 | rescue_time = 1000 39 | c = managerContract.ManagerContract(user.address, rescue_time) 40 | 41 | # Add the contract to the test scenario 42 | scenario = sp.test_scenario() 43 | scenario += c 44 | 45 | # Check that the rescue time is correct 46 | scenario.verify(c.data.rescue_time == rescue_time) 47 | 48 | 49 | @sp.add_test(name="Test ping") 50 | def test_ping(): 51 | # Define the test accounts 52 | user_1 = sp.test_account("user_1") 53 | user_2 = sp.test_account("user_2") 54 | 55 | # Initialize the contract 56 | c = managerContract.ManagerContract(user_1.address) 57 | 58 | # Add the contract to the test scenario 59 | scenario = sp.test_scenario() 60 | scenario += c 61 | 62 | # Ping the contract with the manager account 63 | c.ping().run(sender=user_1, now=sp.timestamp(1000)) 64 | scenario.verify(c.data.last_ping == sp.timestamp(1000)) 65 | 66 | # Check that the ping will fail if it's not executed by the manager account 67 | c.ping().run(valid=False, sender=user_2, now=sp.timestamp(2000)) 68 | 69 | 70 | @sp.add_test(name="Test update manager") 71 | def test_update_manager(): 72 | # Define the test accounts 73 | user_1 = sp.test_account("user_1") 74 | user_2 = sp.test_account("user_2") 75 | 76 | # Initialize the contract 77 | c = managerContract.ManagerContract(user_1.address) 78 | 79 | # Add the contract to the test scenario 80 | scenario = sp.test_scenario() 81 | scenario += c 82 | 83 | # Set user 2 as the new manager 84 | c.update_manager(user_2.address).run(sender=user_1) 85 | scenario.verify(c.data.manager == user_2.address) 86 | 87 | # Check that user 1 cannot update the manager anymore 88 | c.update_manager(user_1.address).run(valid=False, sender=user_1) 89 | scenario.verify(c.data.manager == user_2.address) 90 | 91 | 92 | @sp.add_test(name="Test update rescue accounts") 93 | def test_update_manager(): 94 | # Define the test accounts 95 | user_1 = sp.test_account("user_1") 96 | user_2 = sp.test_account("user_2") 97 | user_3 = sp.test_account("user_3") 98 | 99 | # Initialize the contract 100 | c = managerContract.ManagerContract(user_1.address) 101 | 102 | # Add the contract to the test scenario 103 | scenario = sp.test_scenario() 104 | scenario += c 105 | 106 | # Add user 2 to the rescue accounts 107 | c.add_rescue_account(user_2.address).run(sender=user_1) 108 | scenario.verify(c.data.rescue_accounts.contains(user_2.address)) 109 | scenario.verify(sp.len(c.data.rescue_accounts) == 1) 110 | 111 | # Add user 3 to the rescue accounts 112 | c.add_rescue_account(user_3.address).run(sender=user_1) 113 | scenario.verify(c.data.rescue_accounts.contains(user_2.address)) 114 | scenario.verify(c.data.rescue_accounts.contains(user_3.address)) 115 | scenario.verify(sp.len(c.data.rescue_accounts) == 2) 116 | 117 | # Add user 1 to the rescue accounts 118 | c.add_rescue_account(user_1.address).run(sender=user_1) 119 | scenario.verify(c.data.rescue_accounts.contains(user_1.address)) 120 | scenario.verify(c.data.rescue_accounts.contains(user_2.address)) 121 | scenario.verify(c.data.rescue_accounts.contains(user_3.address)) 122 | scenario.verify(sp.len(c.data.rescue_accounts) == 3) 123 | 124 | # Remove user 2 from the rescue accounts 125 | c.remove_rescue_account(user_2.address).run(sender=user_1) 126 | scenario.verify(c.data.rescue_accounts.contains(user_1.address)) 127 | scenario.verify(~c.data.rescue_accounts.contains(user_2.address)) 128 | scenario.verify(c.data.rescue_accounts.contains(user_3.address)) 129 | scenario.verify(sp.len(c.data.rescue_accounts) == 2) 130 | 131 | # Check that only the manager can add or remove rescue accounts 132 | c.add_rescue_account(user_2.address).run(valid=False, sender=user_3) 133 | c.remove_rescue_account(user_1.address).run(valid=False, sender=user_3) 134 | scenario.verify(c.data.rescue_accounts.contains(user_1.address)) 135 | scenario.verify(c.data.rescue_accounts.contains(user_3.address)) 136 | scenario.verify(sp.len(c.data.rescue_accounts) == 2) 137 | 138 | 139 | @sp.add_test(name="Test rescue mode") 140 | def test_update_manager(): 141 | # Define the test accounts 142 | user_1 = sp.test_account("user_1") 143 | user_2 = sp.test_account("user_2") 144 | user_3 = sp.test_account("user_3") 145 | 146 | # Initialize the contract 147 | c = managerContract.ManagerContract(user_1.address) 148 | 149 | # Add the contract to the test scenario 150 | scenario = sp.test_scenario() 151 | scenario += c 152 | 153 | # Add user 2 to the rescue accounts 154 | c.add_rescue_account(user_2.address).run(sender=user_1) 155 | scenario.verify(c.data.rescue_accounts.contains(user_2.address)) 156 | scenario.verify(sp.len(c.data.rescue_accounts) == 1) 157 | 158 | # Ping the contract 159 | ping_time = sp.timestamp(1000) 160 | c.ping().run(sender=user_1, now=ping_time) 161 | 162 | # Check that the rescue mode cannot be run before the rescue time has passed 163 | ellapsed_time = managerContract.DEFAULT_RESCUE_TIME - 10 164 | c.rescue().run(valid=False, sender=user_2, now=ping_time.add_seconds(ellapsed_time)) 165 | scenario.verify(c.data.manager == user_1.address) 166 | 167 | # Check that the rescue mode only works for users inside the rescue accounts 168 | ellapsed_time = managerContract.DEFAULT_RESCUE_TIME + 10 169 | c.rescue().run(valid=False, sender=user_3, now=ping_time.add_seconds(ellapsed_time)) 170 | c.rescue().run(sender=user_2, now=ping_time.add_seconds(ellapsed_time)) 171 | scenario.verify(c.data.manager == user_2.address) 172 | scenario.verify(c.data.last_ping == ping_time.add_seconds(ellapsed_time)) 173 | 174 | # Check that the old manager lost its manager rights 175 | c.update_manager(user_1.address).run(valid=False, sender=user_1) 176 | scenario.verify(c.data.manager == user_2.address) 177 | -------------------------------------------------------------------------------- /python/tests/minterContract_test.py: -------------------------------------------------------------------------------- 1 | """Unit tests for the MinterContract class. 2 | 3 | """ 4 | 5 | import smartpy as sp 6 | 7 | # Import the extendedFa2Contract and minterContract modules 8 | extendedFa2Contract = sp.io.import_script_from_url( 9 | "file:python/contracts/extendedFa2Contract.py") 10 | minterContract = sp.io.import_script_from_url( 11 | "file:python/contracts/minterContract.py") 12 | 13 | 14 | def get_test_environment(): 15 | # Initialize the test scenario 16 | scenario = sp.test_scenario() 17 | 18 | # Create the test accounts 19 | admin = sp.test_account("admin") 20 | user1 = sp.test_account("user1") 21 | user2 = sp.test_account("user2") 22 | user3 = sp.test_account("user3") 23 | 24 | # Initialize the extended FA2 25 | fa2 = extendedFa2Contract.FA2( 26 | administrator=admin.address, 27 | metadata=sp.utils.metadata_of_url("ipfs://aaa")) 28 | scenario += fa2 29 | 30 | # Initialize the minter contract 31 | minter = minterContract.MinterContract( 32 | administrator=admin.address, 33 | metadata=sp.utils.metadata_of_url("ipfs://bbb"), 34 | fa2=fa2.address) 35 | scenario += minter 36 | 37 | # Set the minter contract as the admin of the FA2 contract 38 | fa2.transfer_administrator(minter.address).run(sender=admin) 39 | minter.accept_fa2_administrator().run(sender=admin) 40 | 41 | # Save all the variables in a test environment dictionary 42 | testEnvironment = { 43 | "scenario": scenario, 44 | "admin": admin, 45 | "user1": user1, 46 | "user2": user2, 47 | "user3": user3, 48 | "fa2": fa2, 49 | "minter": minter} 50 | 51 | return testEnvironment 52 | 53 | 54 | @sp.add_test(name="Test mint") 55 | def test_mint(): 56 | # Get the test environment 57 | testEnvironment = get_test_environment() 58 | scenario = testEnvironment["scenario"] 59 | user1 = testEnvironment["user1"] 60 | user2 = testEnvironment["user2"] 61 | fa2 = testEnvironment["fa2"] 62 | minter = testEnvironment["minter"] 63 | 64 | # Check that a normal user can mint 65 | editions = 5 66 | metadata = {"": sp.utils.bytes_of_string("ipfs://aaa")} 67 | data = {} 68 | royalties = 100 69 | minter.mint( 70 | editions=editions, 71 | metadata=metadata, 72 | data=data, 73 | royalties=royalties).run(sender=user1) 74 | 75 | # Check that the FA2 contract information has been updated 76 | scenario.verify(fa2.data.ledger[(user1.address, 0)] == editions) 77 | scenario.verify(fa2.data.supply[0] == editions) 78 | scenario.verify(fa2.data.token_metadata[0].token_id == 0) 79 | scenario.verify(fa2.data.token_metadata[0].token_info[""] == metadata[""]) 80 | scenario.verify(sp.len(fa2.data.token_data[0]) == 0) 81 | scenario.verify(fa2.data.token_royalties[0].minter.address == user1.address) 82 | scenario.verify(fa2.data.token_royalties[0].minter.royalties == 0) 83 | scenario.verify(fa2.data.token_royalties[0].creator.address == user1.address) 84 | scenario.verify(fa2.data.token_royalties[0].creator.royalties == royalties) 85 | 86 | # Check that trying to mint a token with zero editions fails 87 | minter.mint( 88 | editions=0, 89 | metadata=metadata, 90 | data=data, 91 | royalties=royalties).run(valid=False, sender=user1) 92 | 93 | # Check that trying to set very hight royalties fails 94 | minter.mint( 95 | editions=editions, 96 | metadata=metadata, 97 | data=data, 98 | royalties=300).run(valid=False, sender=user1) 99 | 100 | # Mint another token 101 | new_editions = 10 102 | new_metadata = {"": sp.utils.bytes_of_string("ipfs://bbb")} 103 | new_data = {"code": sp.utils.bytes_of_string("print('hello world')")} 104 | new_royalties = 150 105 | minter.mint( 106 | editions=new_editions, 107 | metadata=new_metadata, 108 | data=new_data, 109 | royalties=new_royalties).run(sender=user2) 110 | 111 | # Check that the FA2 contract information has been updated 112 | scenario.verify(fa2.data.ledger[(user1.address, 0)] == editions) 113 | scenario.verify(fa2.data.ledger[(user2.address, 1)] == new_editions) 114 | scenario.verify(fa2.data.supply[0] == editions) 115 | scenario.verify(fa2.data.supply[1] == new_editions) 116 | scenario.verify(fa2.data.token_metadata[0].token_id == 0) 117 | scenario.verify(fa2.data.token_metadata[0].token_info[""] == metadata[""]) 118 | scenario.verify(fa2.data.token_metadata[1].token_id == 1) 119 | scenario.verify(fa2.data.token_metadata[1].token_info[""] == new_metadata[""]) 120 | scenario.verify(sp.len(fa2.data.token_data[0]) == 0) 121 | scenario.verify(fa2.data.token_data[1]["code"] == new_data["code"]) 122 | scenario.verify(fa2.token_royalties(0).minter.address == user1.address) 123 | scenario.verify(fa2.token_royalties(0).minter.royalties == 0) 124 | scenario.verify(fa2.token_royalties(1).minter.address == user2.address) 125 | scenario.verify(fa2.token_royalties(1).minter.royalties == 0) 126 | scenario.verify(fa2.token_royalties(0).creator.address == user1.address) 127 | scenario.verify(fa2.token_royalties(0).creator.royalties == royalties) 128 | scenario.verify(fa2.token_royalties(1).creator.address == user2.address) 129 | scenario.verify(fa2.token_royalties(1).creator.royalties == new_royalties) 130 | 131 | 132 | @sp.add_test(name="Test transfer and accept administrator") 133 | def test_transfer_and_accept_administrator(): 134 | # Get the test environment 135 | testEnvironment = get_test_environment() 136 | scenario = testEnvironment["scenario"] 137 | admin = testEnvironment["admin"] 138 | user1 = testEnvironment["user1"] 139 | user2 = testEnvironment["user2"] 140 | minter = testEnvironment["minter"] 141 | 142 | # Check the original administrator 143 | scenario.verify(minter.data.administrator == admin.address) 144 | 145 | # Check that only the admin can transfer the administrator 146 | new_administrator = user1.address 147 | minter.transfer_administrator(new_administrator).run(valid=False, sender=user1) 148 | minter.transfer_administrator(new_administrator).run(sender=admin) 149 | 150 | # Check that the proposed administrator is updated 151 | scenario.verify(minter.data.proposed_administrator.open_some() == new_administrator) 152 | 153 | # Check that only the proposed administrator can accept the administrator position 154 | minter.accept_administrator().run(valid=False, sender=admin) 155 | minter.accept_administrator().run(sender=user1) 156 | 157 | # Check that the administrator is updated 158 | scenario.verify(minter.data.administrator == new_administrator) 159 | scenario.verify(~minter.data.proposed_administrator.is_some()) 160 | 161 | # Check that only the new administrator can propose a new administrator 162 | new_administrator = user2.address 163 | minter.transfer_administrator(new_administrator).run(valid=False, sender=admin) 164 | minter.transfer_administrator(new_administrator).run(sender=user1) 165 | 166 | # Check that the proposed administrator is updated 167 | scenario.verify(minter.data.proposed_administrator.open_some() == new_administrator) 168 | 169 | 170 | @sp.add_test(name="Test transfer and accept FA2 administrator") 171 | def test_transfer_and_accept_fa2_administrator(): 172 | # Get the test environment 173 | testEnvironment = get_test_environment() 174 | scenario = testEnvironment["scenario"] 175 | admin = testEnvironment["admin"] 176 | user1 = testEnvironment["user1"] 177 | fa2 = testEnvironment["fa2"] 178 | minter = testEnvironment["minter"] 179 | 180 | # Initialize a new minter contract and add it to the test scenario 181 | new_minter = minterContract.MinterContract( 182 | administrator=admin.address, 183 | metadata=sp.utils.metadata_of_url("ipfs://ccc"), 184 | fa2=fa2.address) 185 | scenario += new_minter 186 | 187 | # Check the original FA2 token administrator 188 | scenario.verify(fa2.data.administrator == minter.address) 189 | 190 | # Propose the new FA2 token contract administrator 191 | minter.transfer_fa2_administrator(new_minter.address).run(valid=False, sender=user1) 192 | minter.transfer_fa2_administrator(new_minter.address).run(sender=admin) 193 | 194 | # Accept the new FA2 token contract administrator responsabilities 195 | new_minter.accept_fa2_administrator().run(valid=False, sender=user1) 196 | new_minter.accept_fa2_administrator().run(sender=admin) 197 | 198 | # Check that the administrator has been updated 199 | scenario.verify(fa2.data.administrator == new_minter.address) 200 | 201 | # Check that minting with the old minter fails 202 | editions = 5 203 | metadata = {"": sp.utils.bytes_of_string("ipfs://aaa")} 204 | data = {} 205 | royalties = 100 206 | minter.mint( 207 | editions=editions, 208 | metadata=metadata, 209 | data=data, 210 | royalties=royalties).run(valid=False, sender=user1) 211 | 212 | # Check that it's possible to mint with the new minter 213 | new_minter.mint( 214 | editions=editions, 215 | metadata=metadata, 216 | data=data, 217 | royalties=royalties).run(sender=user1) 218 | 219 | 220 | @sp.add_test(name="Test set pause") 221 | def test_set_pause(): 222 | # Get the test environment 223 | testEnvironment = get_test_environment() 224 | scenario = testEnvironment["scenario"] 225 | admin = testEnvironment["admin"] 226 | user1 = testEnvironment["user1"] 227 | minter = testEnvironment["minter"] 228 | 229 | # Pause the contract 230 | minter.set_pause(True).run(valid=False, sender=user1) 231 | minter.set_pause(True).run(sender=admin) 232 | 233 | # Check that the contract is paused 234 | scenario.verify(minter.data.paused) 235 | scenario.verify(minter.is_paused()) 236 | 237 | # Check that minting fails 238 | editions = 5 239 | metadata = {"": sp.utils.bytes_of_string("ipfs://aaa")} 240 | data = {} 241 | royalties = 100 242 | minter.mint( 243 | editions=editions, 244 | metadata=metadata, 245 | data=data, 246 | royalties=royalties).run(valid=False, sender=user1) 247 | 248 | # Unpause the contract 249 | minter.set_pause(False).run(valid=False, sender=user1) 250 | minter.set_pause(False).run(sender=admin) 251 | 252 | # Check that the contract is not paused 253 | scenario.verify(~minter.data.paused) 254 | scenario.verify(~minter.is_paused()) 255 | 256 | # Check that minting is possible again 257 | minter.mint( 258 | editions=editions, 259 | metadata=metadata, 260 | data=data, 261 | royalties=royalties).run(sender=user1) 262 | -------------------------------------------------------------------------------- /python/tests/nonCustodialBarterContract_test.py: -------------------------------------------------------------------------------- 1 | """Unit tests for the NonCustodialBarterContract class. 2 | 3 | """ 4 | 5 | import smartpy as sp 6 | 7 | # Import the nonCustodialBarterContract and fa2Contract modules 8 | nonCustodialBarterContract = sp.io.import_script_from_url( 9 | "file:python/contracts/nonCustodialBarterContract.py") 10 | fa2Contract = sp.io.import_script_from_url( 11 | "file:python/templates/fa2Contract.py") 12 | 13 | 14 | def get_test_environment(): 15 | # Create the test accounts 16 | user1 = sp.test_account("user1") 17 | user2 = sp.test_account("user2") 18 | user3 = sp.test_account("user3") 19 | fa2_admin = sp.test_account("fa2_admin") 20 | 21 | # Initialize the two FA2 contracts 22 | fa2_1 = fa2Contract.FA2( 23 | config=fa2Contract.FA2_config(), 24 | admin=fa2_admin.address, 25 | metadata=sp.utils.metadata_of_url("ipfs://aaa")) 26 | fa2_2 = fa2Contract.FA2( 27 | config=fa2Contract.FA2_config(), 28 | admin=fa2_admin.address, 29 | metadata=sp.utils.metadata_of_url("ipfs://bbb")) 30 | 31 | # Initialize the non-custodial barter contract 32 | barter = nonCustodialBarterContract.NonCustodialBarterContract( 33 | metadata=sp.utils.metadata_of_url("ipfs://ccc")) 34 | 35 | # Add all the contracts to the test scenario 36 | scenario = sp.test_scenario() 37 | scenario += fa2_1 38 | scenario += fa2_2 39 | scenario += barter 40 | 41 | # Save all the variables in a test environment dictionary 42 | testEnvironment = { 43 | "scenario": scenario, 44 | "user1": user1, 45 | "user2": user2, 46 | "user3": user3, 47 | "fa2_admin": fa2_admin, 48 | "fa2_1": fa2_1, 49 | "fa2_2": fa2_2, 50 | "barter": barter} 51 | 52 | return testEnvironment 53 | 54 | 55 | @sp.add_test(name="Test trade with second user") 56 | def test_trade_with_second_user(): 57 | # Get the test environment 58 | testEnvironment = get_test_environment() 59 | scenario = testEnvironment["scenario"] 60 | user1 = testEnvironment["user1"] 61 | user2 = testEnvironment["user2"] 62 | user3 = testEnvironment["user3"] 63 | fa2_admin = testEnvironment["fa2_admin"] 64 | fa2_1 = testEnvironment["fa2_1"] 65 | fa2_2 = testEnvironment["fa2_2"] 66 | barter = testEnvironment["barter"] 67 | 68 | # Mint some tokens 69 | fa2_1.mint( 70 | address=user1.address, 71 | token_id=sp.nat(0), 72 | amount=sp.nat(100), 73 | metadata={"": sp.utils.bytes_of_string("ipfs://ccc")}).run(sender=fa2_admin) 74 | fa2_1.mint( 75 | address=user1.address, 76 | token_id=sp.nat(1), 77 | amount=sp.nat(100), 78 | metadata={"": sp.utils.bytes_of_string("ipfs://ddd")}).run(sender=fa2_admin) 79 | fa2_2.mint( 80 | address=user1.address, 81 | token_id=sp.nat(0), 82 | amount=sp.nat(100), 83 | metadata={"": sp.utils.bytes_of_string("ipfs://eee")}).run(sender=fa2_admin) 84 | fa2_2.mint( 85 | address=user2.address, 86 | token_id=sp.nat(1), 87 | amount=sp.nat(100), 88 | metadata={"": sp.utils.bytes_of_string("ipfs://eee")}).run(sender=fa2_admin) 89 | 90 | # Transfer some tokens to the first and third user 91 | fa2_2.transfer(sp.list([sp.record( 92 | from_=user2.address, 93 | txs=sp.list([ 94 | sp.record(to_=user1.address, token_id=1, amount=30), 95 | sp.record(to_=user3.address, token_id=1, amount=30)]))])).run(sender=user2) 96 | 97 | # Add the barter contract as operator for the tokens 98 | fa2_1.update_operators( 99 | [sp.variant("add_operator", fa2_1.operator_param.make( 100 | owner=user1.address, 101 | operator=barter.address, 102 | token_id=0)), 103 | sp.variant("add_operator", fa2_1.operator_param.make( 104 | owner=user1.address, 105 | operator=barter.address, 106 | token_id=1))]).run(sender=user1) 107 | fa2_2.update_operators( 108 | [sp.variant("add_operator", fa2_2.operator_param.make( 109 | owner=user1.address, 110 | operator=barter.address, 111 | token_id=0)), 112 | sp.variant("add_operator", fa2_2.operator_param.make( 113 | owner=user1.address, 114 | operator=barter.address, 115 | token_id=1))]).run(sender=user1) 116 | fa2_2.update_operators( 117 | [sp.variant("add_operator", fa2_2.operator_param.make( 118 | owner=user2.address, 119 | operator=barter.address, 120 | token_id=1))]).run(sender=user2) 121 | fa2_2.update_operators( 122 | [sp.variant("add_operator", fa2_2.operator_param.make( 123 | owner=user3.address, 124 | operator=barter.address, 125 | token_id=1))]).run(sender=user3) 126 | 127 | # Check that the OBJKT ledger information is correct 128 | scenario.verify(fa2_1.data.ledger[(user1.address, 0)].balance == 100) 129 | scenario.verify(fa2_1.data.ledger[(user1.address, 1)].balance == 100) 130 | scenario.verify(fa2_2.data.ledger[(user1.address, 0)].balance == 100) 131 | scenario.verify(fa2_2.data.ledger[(user1.address, 1)].balance == 30) 132 | scenario.verify(fa2_2.data.ledger[(user2.address, 1)].balance == 40) 133 | scenario.verify(fa2_2.data.ledger[(user3.address, 1)].balance == 30) 134 | 135 | # Propose a trade with the second user 136 | barter.propose_trade( 137 | tokens=sp.list([ 138 | sp.record(fa2=fa2_1.address, id=sp.nat(0), amount=sp.nat(1)), 139 | sp.record(fa2=fa2_1.address, id=sp.nat(1), amount=sp.nat(2)), 140 | sp.record(fa2=fa2_2.address, id=sp.nat(0), amount=sp.nat(2))]), 141 | for_tokens=sp.list([ 142 | sp.record(fa2=fa2_2.address, id=sp.nat(1), amount=sp.nat(10))]), 143 | with_user=sp.some(user2.address)).run(valid=False, sender=user1, amount=sp.tez(3)) 144 | barter.propose_trade( 145 | tokens=sp.list([ 146 | sp.record(fa2=fa2_1.address, id=sp.nat(0), amount=sp.nat(1)), 147 | sp.record(fa2=fa2_1.address, id=sp.nat(1), amount=sp.nat(2)), 148 | sp.record(fa2=fa2_2.address, id=sp.nat(0), amount=sp.nat(2))]), 149 | for_tokens=sp.list([ 150 | sp.record(fa2=fa2_2.address, id=sp.nat(1), amount=sp.nat(10))]), 151 | with_user=sp.some(user2.address)).run(sender=user1) 152 | 153 | # Check that the OBJKT ledger information is correct 154 | scenario.verify(fa2_1.data.ledger[(user1.address, 0)].balance == 100) 155 | scenario.verify(fa2_1.data.ledger[(user1.address, 1)].balance == 100) 156 | scenario.verify(fa2_2.data.ledger[(user1.address, 0)].balance == 100) 157 | scenario.verify(fa2_2.data.ledger[(user1.address, 1)].balance == 30) 158 | scenario.verify(fa2_2.data.ledger[(user2.address, 1)].balance == 40) 159 | scenario.verify(fa2_2.data.ledger[(user3.address, 1)].balance == 30) 160 | 161 | # Check that the first and third users cannot accept the trade because they 162 | # are not the assigned second user 163 | barter.accept_trade(0).run(valid=False, sender=user1) 164 | barter.accept_trade(0).run(valid=False, sender=user3) 165 | 166 | # The second user accepts the trade 167 | barter.accept_trade(0).run(valid=False, sender=user2, amount=sp.tez(3)) 168 | barter.accept_trade(0).run(sender=user2) 169 | 170 | # Check that the OBJKT ledger information is correct 171 | scenario.verify(fa2_1.data.ledger[(user1.address, 0)].balance == 100 - 1) 172 | scenario.verify(fa2_1.data.ledger[(user1.address, 1)].balance == 100 - 2) 173 | scenario.verify(fa2_2.data.ledger[(user1.address, 0)].balance == 100 - 2) 174 | scenario.verify(fa2_2.data.ledger[(user1.address, 1)].balance == 30 + 10) 175 | scenario.verify(fa2_1.data.ledger[(user2.address, 0)].balance == 1) 176 | scenario.verify(fa2_1.data.ledger[(user2.address, 1)].balance == 2) 177 | scenario.verify(fa2_2.data.ledger[(user2.address, 0)].balance == 2) 178 | scenario.verify(fa2_2.data.ledger[(user2.address, 1)].balance == 40 - 10) 179 | scenario.verify(fa2_2.data.ledger[(user3.address, 1)].balance == 30) 180 | 181 | # Check that the second user cannot accept twice the trade 182 | barter.accept_trade(0).run(valid=False, sender=user2) 183 | 184 | 185 | @sp.add_test(name="Test trade without second user") 186 | def test_trade_without_second_user(): 187 | # Get the test environment 188 | testEnvironment = get_test_environment() 189 | scenario = testEnvironment["scenario"] 190 | user1 = testEnvironment["user1"] 191 | user2 = testEnvironment["user2"] 192 | user3 = testEnvironment["user3"] 193 | fa2_admin = testEnvironment["fa2_admin"] 194 | fa2_1 = testEnvironment["fa2_1"] 195 | fa2_2 = testEnvironment["fa2_2"] 196 | barter = testEnvironment["barter"] 197 | 198 | # Mint some tokens 199 | fa2_1.mint( 200 | address=user1.address, 201 | token_id=sp.nat(0), 202 | amount=sp.nat(100), 203 | metadata={"": sp.utils.bytes_of_string("ipfs://ccc")}).run(sender=fa2_admin) 204 | fa2_1.mint( 205 | address=user1.address, 206 | token_id=sp.nat(1), 207 | amount=sp.nat(100), 208 | metadata={"": sp.utils.bytes_of_string("ipfs://ddd")}).run(sender=fa2_admin) 209 | fa2_2.mint( 210 | address=user1.address, 211 | token_id=sp.nat(0), 212 | amount=sp.nat(100), 213 | metadata={"": sp.utils.bytes_of_string("ipfs://eee")}).run(sender=fa2_admin) 214 | fa2_2.mint( 215 | address=user2.address, 216 | token_id=sp.nat(1), 217 | amount=sp.nat(100), 218 | metadata={"": sp.utils.bytes_of_string("ipfs://eee")}).run(sender=fa2_admin) 219 | 220 | # Add the barter contract as operator for the tokens 221 | fa2_1.update_operators( 222 | [sp.variant("add_operator", fa2_1.operator_param.make( 223 | owner=user1.address, 224 | operator=barter.address, 225 | token_id=0)), 226 | sp.variant("add_operator", fa2_1.operator_param.make( 227 | owner=user1.address, 228 | operator=barter.address, 229 | token_id=1))]).run(sender=user1) 230 | fa2_2.update_operators( 231 | [sp.variant("add_operator", fa2_2.operator_param.make( 232 | owner=user1.address, 233 | operator=barter.address, 234 | token_id=0))]).run(sender=user1) 235 | fa2_2.update_operators( 236 | [sp.variant("add_operator", fa2_2.operator_param.make( 237 | owner=user2.address, 238 | operator=barter.address, 239 | token_id=1))]).run(sender=user2) 240 | 241 | # Check that the OBJKT ledger information is correct 242 | scenario.verify(fa2_1.data.ledger[(user1.address, 0)].balance == 100) 243 | scenario.verify(fa2_1.data.ledger[(user1.address, 1)].balance == 100) 244 | scenario.verify(fa2_2.data.ledger[(user1.address, 0)].balance == 100) 245 | scenario.verify(fa2_2.data.ledger[(user2.address, 1)].balance == 100) 246 | 247 | # Propose a trade with no specific second user 248 | barter.propose_trade( 249 | tokens=sp.list([ 250 | sp.record(fa2=fa2_1.address, id=sp.nat(0), amount=sp.nat(1)), 251 | sp.record(fa2=fa2_1.address, id=sp.nat(1), amount=sp.nat(2)), 252 | sp.record(fa2=fa2_2.address, id=sp.nat(0), amount=sp.nat(2))]), 253 | for_tokens=sp.list([ 254 | sp.record(fa2=fa2_2.address, id=sp.nat(1), amount=sp.nat(10))]), 255 | with_user=sp.none).run(sender=user1) 256 | 257 | # Check that the OBJKT ledger information is correct 258 | scenario.verify(fa2_1.data.ledger[(user1.address, 0)].balance == 100) 259 | scenario.verify(fa2_1.data.ledger[(user1.address, 1)].balance == 100) 260 | scenario.verify(fa2_2.data.ledger[(user1.address, 0)].balance == 100) 261 | scenario.verify(fa2_2.data.ledger[(user2.address, 1)].balance == 100) 262 | 263 | # Check that the first and third users cannot accept the trade because they 264 | # don't own the requested token 265 | barter.accept_trade(0).run(valid=False, sender=user1) 266 | barter.accept_trade(0).run(valid=False, sender=user3) 267 | 268 | # The second user accepts the trade 269 | barter.accept_trade(0).run(sender=user2) 270 | 271 | # Check that the OBJKT ledger information is correct 272 | scenario.verify(fa2_1.data.ledger[(user1.address, 0)].balance == 100 - 1) 273 | scenario.verify(fa2_1.data.ledger[(user1.address, 1)].balance == 100 - 2) 274 | scenario.verify(fa2_2.data.ledger[(user1.address, 0)].balance == 100 - 2) 275 | scenario.verify(fa2_2.data.ledger[(user1.address, 1)].balance == 10) 276 | scenario.verify(fa2_1.data.ledger[(user2.address, 0)].balance == 1) 277 | scenario.verify(fa2_1.data.ledger[(user2.address, 1)].balance == 2) 278 | scenario.verify(fa2_2.data.ledger[(user2.address, 0)].balance == 2) 279 | scenario.verify(fa2_2.data.ledger[(user2.address, 1)].balance == 100 - 10) 280 | 281 | # Check that the second user cannot accept twice the trade 282 | barter.accept_trade(0).run(valid=False, sender=user2) 283 | 284 | 285 | @sp.add_test(name="Test trade same user") 286 | def test_trade_same_user(): 287 | # Get the test environment 288 | testEnvironment = get_test_environment() 289 | scenario = testEnvironment["scenario"] 290 | user1 = testEnvironment["user1"] 291 | fa2_admin = testEnvironment["fa2_admin"] 292 | fa2_1 = testEnvironment["fa2_1"] 293 | fa2_2 = testEnvironment["fa2_2"] 294 | barter = testEnvironment["barter"] 295 | 296 | # Mint some tokens 297 | fa2_1.mint( 298 | address=user1.address, 299 | token_id=sp.nat(0), 300 | amount=sp.nat(100), 301 | metadata={"": sp.utils.bytes_of_string("ipfs://ccc")}).run(sender=fa2_admin) 302 | fa2_1.mint( 303 | address=user1.address, 304 | token_id=sp.nat(1), 305 | amount=sp.nat(100), 306 | metadata={"": sp.utils.bytes_of_string("ipfs://ddd")}).run(sender=fa2_admin) 307 | fa2_2.mint( 308 | address=user1.address, 309 | token_id=sp.nat(0), 310 | amount=sp.nat(100), 311 | metadata={"": sp.utils.bytes_of_string("ipfs://eee")}).run(sender=fa2_admin) 312 | fa2_2.mint( 313 | address=user1.address, 314 | token_id=sp.nat(1), 315 | amount=sp.nat(100), 316 | metadata={"": sp.utils.bytes_of_string("ipfs://eee")}).run(sender=fa2_admin) 317 | 318 | # Add the barter contract as operator for the tokens 319 | fa2_1.update_operators( 320 | [sp.variant("add_operator", fa2_1.operator_param.make( 321 | owner=user1.address, 322 | operator=barter.address, 323 | token_id=0)), 324 | sp.variant("add_operator", fa2_1.operator_param.make( 325 | owner=user1.address, 326 | operator=barter.address, 327 | token_id=1))]).run(sender=user1) 328 | fa2_2.update_operators( 329 | [sp.variant("add_operator", fa2_2.operator_param.make( 330 | owner=user1.address, 331 | operator=barter.address, 332 | token_id=0)), 333 | sp.variant("add_operator", fa2_2.operator_param.make( 334 | owner=user1.address, 335 | operator=barter.address, 336 | token_id=1))]).run(sender=user1) 337 | 338 | # Check that the OBJKT ledger information is correct 339 | scenario.verify(fa2_1.data.ledger[(user1.address, 0)].balance == 100) 340 | scenario.verify(fa2_1.data.ledger[(user1.address, 1)].balance == 100) 341 | scenario.verify(fa2_2.data.ledger[(user1.address, 0)].balance == 100) 342 | scenario.verify(fa2_2.data.ledger[(user1.address, 1)].balance == 100) 343 | 344 | # Propose a trade with no specific second user 345 | barter.propose_trade( 346 | tokens=sp.list([ 347 | sp.record(fa2=fa2_1.address, id=sp.nat(0), amount=sp.nat(1)), 348 | sp.record(fa2=fa2_1.address, id=sp.nat(1), amount=sp.nat(2)), 349 | sp.record(fa2=fa2_2.address, id=sp.nat(0), amount=sp.nat(2))]), 350 | for_tokens=sp.list([ 351 | sp.record(fa2=fa2_2.address, id=sp.nat(1), amount=sp.nat(10))]), 352 | with_user=sp.none).run(sender=user1) 353 | 354 | # Check that the OBJKT ledger information is correct 355 | scenario.verify(fa2_1.data.ledger[(user1.address, 0)].balance == 100) 356 | scenario.verify(fa2_1.data.ledger[(user1.address, 1)].balance == 100) 357 | scenario.verify(fa2_2.data.ledger[(user1.address, 0)].balance == 100) 358 | scenario.verify(fa2_2.data.ledger[(user1.address, 1)].balance == 100) 359 | 360 | # The first user accepts its own trade 361 | barter.accept_trade(0).run(sender=user1) 362 | 363 | # Check that the OBJKT ledger information is correct 364 | scenario.verify(fa2_1.data.ledger[(user1.address, 0)].balance == 100) 365 | scenario.verify(fa2_1.data.ledger[(user1.address, 1)].balance == 100) 366 | scenario.verify(fa2_2.data.ledger[(user1.address, 0)].balance == 100) 367 | scenario.verify(fa2_2.data.ledger[(user1.address, 1)].balance == 100) 368 | 369 | # Check that the first user cannot accept twice the trade 370 | barter.accept_trade(0).run(valid=False, sender=user1) 371 | 372 | 373 | @sp.add_test(name="Test cancel trade") 374 | def test_cancel_trade(): 375 | # Get the test environment 376 | testEnvironment = get_test_environment() 377 | scenario = testEnvironment["scenario"] 378 | user1 = testEnvironment["user1"] 379 | user2 = testEnvironment["user2"] 380 | fa2_admin = testEnvironment["fa2_admin"] 381 | fa2_1 = testEnvironment["fa2_1"] 382 | fa2_2 = testEnvironment["fa2_2"] 383 | barter = testEnvironment["barter"] 384 | 385 | # Mint some tokens 386 | fa2_1.mint( 387 | address=user1.address, 388 | token_id=sp.nat(0), 389 | amount=sp.nat(100), 390 | metadata={"": sp.utils.bytes_of_string("ipfs://ccc")}).run(sender=fa2_admin) 391 | fa2_1.mint( 392 | address=user1.address, 393 | token_id=sp.nat(1), 394 | amount=sp.nat(100), 395 | metadata={"": sp.utils.bytes_of_string("ipfs://ddd")}).run(sender=fa2_admin) 396 | fa2_2.mint( 397 | address=user1.address, 398 | token_id=sp.nat(0), 399 | amount=sp.nat(100), 400 | metadata={"": sp.utils.bytes_of_string("ipfs://eee")}).run(sender=fa2_admin) 401 | fa2_2.mint( 402 | address=user2.address, 403 | token_id=sp.nat(1), 404 | amount=sp.nat(100), 405 | metadata={"": sp.utils.bytes_of_string("ipfs://eee")}).run(sender=fa2_admin) 406 | 407 | # Add the barter contract as operator for the tokens 408 | fa2_1.update_operators( 409 | [sp.variant("add_operator", fa2_1.operator_param.make( 410 | owner=user1.address, 411 | operator=barter.address, 412 | token_id=0)), 413 | sp.variant("add_operator", fa2_1.operator_param.make( 414 | owner=user1.address, 415 | operator=barter.address, 416 | token_id=1))]).run(sender=user1) 417 | fa2_2.update_operators( 418 | [sp.variant("add_operator", fa2_2.operator_param.make( 419 | owner=user1.address, 420 | operator=barter.address, 421 | token_id=0))]).run(sender=user1) 422 | fa2_2.update_operators( 423 | [sp.variant("add_operator", fa2_2.operator_param.make( 424 | owner=user2.address, 425 | operator=barter.address, 426 | token_id=1))]).run(sender=user2) 427 | 428 | # Check that the OBJKT ledger information is correct 429 | scenario.verify(fa2_1.data.ledger[(user1.address, 0)].balance == 100) 430 | scenario.verify(fa2_1.data.ledger[(user1.address, 1)].balance == 100) 431 | scenario.verify(fa2_2.data.ledger[(user1.address, 0)].balance == 100) 432 | scenario.verify(fa2_2.data.ledger[(user2.address, 1)].balance == 100) 433 | 434 | # Propose a trade with the second user 435 | barter.propose_trade( 436 | tokens=sp.list([ 437 | sp.record(fa2=fa2_1.address, id=sp.nat(0), amount=sp.nat(1)), 438 | sp.record(fa2=fa2_1.address, id=sp.nat(1), amount=sp.nat(2)), 439 | sp.record(fa2=fa2_2.address, id=sp.nat(0), amount=sp.nat(2))]), 440 | for_tokens=sp.list([ 441 | sp.record(fa2=fa2_2.address, id=sp.nat(1), amount=sp.nat(10))]), 442 | with_user=sp.some(user2.address)).run(sender=user1) 443 | 444 | # Check that the OBJKT ledger information is correct 445 | scenario.verify(fa2_1.data.ledger[(user1.address, 0)].balance == 100) 446 | scenario.verify(fa2_1.data.ledger[(user1.address, 1)].balance == 100) 447 | scenario.verify(fa2_2.data.ledger[(user1.address, 0)].balance == 100) 448 | scenario.verify(fa2_2.data.ledger[(user2.address, 1)].balance == 100) 449 | 450 | # Check that the second user cannot cancel the trade 451 | barter.cancel_trade(0).run(valid=False, sender=user2) 452 | 453 | # Cancel the trade 454 | barter.cancel_trade(0).run(valid=False, sender=user1, amount=sp.tez(3)) 455 | barter.cancel_trade(0).run(sender=user1) 456 | 457 | # Check that the OBJKT ledger information is correct 458 | scenario.verify(fa2_1.data.ledger[(user1.address, 0)].balance == 100) 459 | scenario.verify(fa2_1.data.ledger[(user1.address, 1)].balance == 100) 460 | scenario.verify(fa2_2.data.ledger[(user1.address, 0)].balance == 100) 461 | scenario.verify(fa2_2.data.ledger[(user2.address, 1)].balance == 100) 462 | 463 | # Check that the first user cannot cancel the trade again 464 | barter.cancel_trade(0).run(valid=False, sender=user1) 465 | -------------------------------------------------------------------------------- /python/tests/patientContract_test.py: -------------------------------------------------------------------------------- 1 | """Unit tests for the PatientContract class. 2 | 3 | """ 4 | 5 | import smartpy as sp 6 | 7 | # Import the patientContract and the doctorContract modules 8 | patientContract = sp.io.import_script_from_url( 9 | "file:python/contracts/patientContract.py") 10 | doctorContract = sp.io.import_script_from_url( 11 | "file:python/contracts/doctorContract.py") 12 | 13 | 14 | @sp.add_test(name="Test initialization") 15 | def test_initialization(): 16 | # Define the doctor account 17 | doctor = sp.test_account("doctor") 18 | 19 | # Initialize the contract 20 | c = patientContract.PatientContract(doctor.address) 21 | 22 | # Add the contract to the test scenario 23 | scenario = sp.test_scenario() 24 | scenario += c 25 | 26 | # Check that the information in the contract strorage is correct 27 | scenario.verify(c.data.doctor == doctor.address) 28 | scenario.verify(~c.data.illness.is_some()) 29 | 30 | 31 | @sp.add_test(name="Test get sick") 32 | def test_get_sick(): 33 | # Define the doctor account 34 | doctor = sp.test_account("doctor") 35 | 36 | # Initialize the contract 37 | c = patientContract.PatientContract(doctor.address) 38 | 39 | # Add the contract to the test scenario 40 | scenario = sp.test_scenario() 41 | scenario += c 42 | 43 | # Make the patient sick 44 | c.get_sick("cold") 45 | scenario.verify(c.data.illness.open_some().name == "cold") 46 | scenario.verify(~c.data.illness.open_some().medicament.is_some()) 47 | scenario.verify(~c.data.illness.open_some().cured) 48 | 49 | # Check that the patient cannot get another illness before is cured from the 50 | # previous one 51 | c.get_sick("flu").run(valid=False) 52 | 53 | 54 | @sp.add_test(name="Test get medicament") 55 | def test_get_medicament(): 56 | # Define the test accounts 57 | doctor = sp.test_account("doctor") 58 | friend = sp.test_account("friend") 59 | 60 | # Initialize the contract 61 | c = patientContract.PatientContract(doctor.address) 62 | 63 | # Add the contract to the test scenario 64 | scenario = sp.test_scenario() 65 | scenario += c 66 | 67 | # Make the patient sick 68 | c.get_sick("cold") 69 | 70 | # Get a medicament from the doctor to get cured 71 | c.get_medicament("pills").run(sender=doctor) 72 | scenario.verify( 73 | c.data.illness.open_some().medicament.open_some() == "pills") 74 | scenario.verify(c.data.illness.open_some().cured) 75 | 76 | # Check that it can only get medicaments from the doctor 77 | c.get_medicament("drugs").run(valid=False, sender=friend) 78 | 79 | # Check that it can get sick again 80 | scenario += c.get_sick("flu") 81 | scenario.verify(c.data.illness.open_some().name == "flu") 82 | scenario.verify(~c.data.illness.open_some().medicament.is_some()) 83 | scenario.verify(~c.data.illness.open_some().cured) 84 | 85 | 86 | @sp.add_test(name="Test visit doctor") 87 | def test_visit_doctor(): 88 | # Initialize the test scenario 89 | scenario = sp.test_scenario() 90 | 91 | # Initialize the doctor contract 92 | doctor = doctorContract.DoctorContract() 93 | scenario += doctor 94 | 95 | # Initialize the patient contract 96 | patient = patientContract.PatientContract(doctor.address) 97 | scenario += patient 98 | 99 | # Make the patient sick 100 | illness = "headache" 101 | patient.get_sick(illness) 102 | scenario.verify(patient.data.illness.open_some().name == illness) 103 | scenario.verify(~patient.data.illness.open_some().medicament.is_some()) 104 | scenario.verify(~patient.data.illness.open_some().cured) 105 | 106 | # Make the patient visit the doctor 107 | patient.visit_doctor() 108 | 109 | # Check that the doctor sent the correct medicament and the patient is cured 110 | scenario.verify(patient.data.illness.open_some().name == illness) 111 | scenario.verify( 112 | patient.data.illness.open_some().medicament.open_some() == doctor.data.medicaments[illness]) 113 | scenario.verify(patient.data.illness.open_some().cured) 114 | -------------------------------------------------------------------------------- /python/tests/pingPongContract_test.py: -------------------------------------------------------------------------------- 1 | """Unit tests for the ping-pong game classes. 2 | 3 | """ 4 | 5 | import smartpy as sp 6 | 7 | # Import the pingPongContract module 8 | pingPongContract = sp.io.import_script_from_url( 9 | "file:python/contracts/pingPongContract.py") 10 | 11 | 12 | @sp.add_test(name="Test player initialization") 13 | def test_player_initialization(): 14 | # Define the test account 15 | player = sp.address("tz1Player") 16 | 17 | # Initialize the contract 18 | player_contract = pingPongContract.PlayerContract(player) 19 | 20 | # Add the contract to the test scenario 21 | scenario = sp.test_scenario() 22 | scenario += player_contract 23 | 24 | # Check that the information in the contract strorage is correct 25 | scenario.verify(player_contract.data.player == player) 26 | scenario.verify(sp.len(player_contract.data.games) == 0) 27 | scenario.verify(player_contract.data.moves["ping"] == "pong") 28 | scenario.verify(player_contract.data.moves["pong"] == "ping") 29 | 30 | 31 | @sp.add_test(name="Test court initialization") 32 | def test_court_initialization(): 33 | # Initialize the contract 34 | court_contract = pingPongContract.CourtContract() 35 | 36 | # Add the contract to the test scenario 37 | scenario = sp.test_scenario() 38 | scenario += court_contract 39 | 40 | # Check that the information in the contract strorage is correct 41 | scenario.verify(sp.len(court_contract.data.games) == 0) 42 | 43 | 44 | @sp.add_test(name="Test register game") 45 | def test_register_game(): 46 | # Define the test accounts 47 | player_1 = sp.address("tz1Player1") 48 | player_2 = sp.address("tz1Player2") 49 | 50 | # Initialize the contracts 51 | player_1_contract = pingPongContract.PlayerContract(player_1) 52 | player_2_contract = pingPongContract.PlayerContract(player_2) 53 | court_contract = pingPongContract.CourtContract() 54 | 55 | # Add the scontracts to the test scenario 56 | scenario = sp.test_scenario() 57 | scenario += player_1_contract 58 | scenario += player_2_contract 59 | scenario += court_contract 60 | 61 | # Register a game between the two players 62 | game_id = 3 63 | court_contract.register_game(sp.record( 64 | game_id=game_id, 65 | player_1=player_1_contract.address, 66 | player_2=player_2_contract.address)) 67 | 68 | # Check that the information in the contract strorage is correct 69 | scenario.verify(sp.len(court_contract.data.games) == 1) 70 | game = court_contract.data.games[game_id] 71 | scenario.verify(game.players[player_1_contract.address].accepted == False) 72 | scenario.verify(game.players[ 73 | player_1_contract.address].opponent == player_2_contract.address) 74 | scenario.verify(game.players[player_1_contract.address].victories == 0) 75 | scenario.verify(game.players[player_2_contract.address].accepted == False) 76 | scenario.verify(game.players[ 77 | player_2_contract.address].opponent == player_1_contract.address) 78 | scenario.verify(game.players[player_2_contract.address].victories == 0) 79 | scenario.verify(~game.started) 80 | scenario.verify(game.played_games == 0) 81 | 82 | # Check that one cannot register the same game again 83 | court_contract.register_game(sp.record( 84 | game_id=game_id, 85 | player_1=player_1_contract.address, 86 | player_2=player_2_contract.address)).run(valid=False) 87 | 88 | 89 | @sp.add_test(name="Test accept game") 90 | def test_accept_game(): 91 | # Define the test accounts 92 | player_1 = sp.address("tz1Player1") 93 | player_2 = sp.address("tz1Player2") 94 | 95 | # Initialize the contracts 96 | player_1_contract = pingPongContract.PlayerContract(player_1) 97 | player_2_contract = pingPongContract.PlayerContract(player_2) 98 | court_contract = pingPongContract.CourtContract() 99 | 100 | # Add the scontracts to the test scenario 101 | scenario = sp.test_scenario() 102 | scenario += player_1_contract 103 | scenario += player_2_contract 104 | scenario += court_contract 105 | 106 | # Register a game between the two players 107 | game_id = 5 108 | court_contract.register_game(sp.record( 109 | game_id=game_id, 110 | player_1=player_1_contract.address, 111 | player_2=player_2_contract.address)) 112 | 113 | # Player 2 accepts the game 114 | court_contract.accept_game(sp.record( 115 | game_id=game_id, accept=True)).run(sender=player_2_contract.address) 116 | 117 | # Check that the information in the contract strorage is correct 118 | game = court_contract.data.games[game_id] 119 | scenario.verify(game.players[player_1_contract.address].accepted == False) 120 | scenario.verify(game.players[player_2_contract.address].accepted == True) 121 | 122 | 123 | @sp.add_test(name="Test add game") 124 | def test_add_game(): 125 | # Define the test accounts 126 | player_1 = sp.address("tz1Player1") 127 | player_2 = sp.address("tz1Player2") 128 | 129 | # Initialize the contracts 130 | player_1_contract = pingPongContract.PlayerContract(player_1) 131 | player_2_contract = pingPongContract.PlayerContract(player_2) 132 | court_contract = pingPongContract.CourtContract() 133 | 134 | # Add the scontracts to the test scenario 135 | scenario = sp.test_scenario() 136 | scenario += player_1_contract 137 | scenario += player_2_contract 138 | scenario += court_contract 139 | 140 | # Register a game between the two players 141 | game_id = 5 142 | court_contract.register_game(sp.record( 143 | game_id=game_id, 144 | player_1=player_1_contract.address, 145 | player_2=player_2_contract.address)) 146 | 147 | # Add the game to the two players 148 | player_1_contract.add_game(sp.record( 149 | game_id=game_id, court=court_contract.address, 150 | opponent=player_2_contract.address)).run(sender=player_1) 151 | player_2_contract.add_game(sp.record( 152 | game_id=game_id, court=court_contract.address, 153 | opponent=player_1_contract.address)).run(sender=player_2) 154 | 155 | # Check that the information in the contract strorages is correct 156 | scenario.verify(sp.len(player_1_contract.data.games) == 1) 157 | game = player_1_contract.data.games[game_id] 158 | scenario.verify(game.court == court_contract.address) 159 | scenario.verify(game.opponent == player_2_contract.address) 160 | scenario.verify(game.ball_hits == 0) 161 | game = player_2_contract.data.games[game_id] 162 | scenario.verify(game.court == court_contract.address) 163 | scenario.verify(game.opponent == player_1_contract.address) 164 | scenario.verify(game.ball_hits == 0) 165 | game = court_contract.data.games[game_id] 166 | scenario.verify(game.players[player_1_contract.address].accepted == True) 167 | scenario.verify(game.players[player_2_contract.address].accepted == True) 168 | 169 | 170 | @sp.add_test(name="Test play game") 171 | def test_play_game(): 172 | # Define the test accounts 173 | player_1 = sp.address("tz1Player1") 174 | player_2 = sp.address("tz1Player2") 175 | 176 | # Initialize the contracts 177 | player_1_contract = pingPongContract.PlayerContract(player_1) 178 | player_2_contract = pingPongContract.PlayerContract(player_2) 179 | court_contract = pingPongContract.CourtContract() 180 | 181 | # Add the scontracts to the test scenario 182 | scenario = sp.test_scenario() 183 | scenario += player_1_contract 184 | scenario += player_2_contract 185 | scenario += court_contract 186 | 187 | # Register a game between the two players 188 | game_id = 5 189 | court_contract.register_game(sp.record( 190 | game_id=game_id, 191 | player_1=player_1_contract.address, 192 | player_2=player_2_contract.address)) 193 | 194 | # Add the game to the two players 195 | player_1_contract.add_game(sp.record( 196 | game_id=game_id, court=court_contract.address, 197 | opponent=player_2_contract.address)).run(sender=player_1) 198 | player_2_contract.add_game(sp.record( 199 | game_id=game_id, court=court_contract.address, 200 | opponent=player_1_contract.address)).run(sender=player_2) 201 | 202 | # Play one game 203 | player_1_contract.play_game(game_id).run(sender=player_1) 204 | 205 | # Check that the information in the contract strorages is correct 206 | scenario.verify(player_1_contract.data.games[game_id].ball_hits == 3) 207 | scenario.verify(player_2_contract.data.games[game_id].ball_hits == 2) 208 | game = court_contract.data.games[game_id] 209 | scenario.verify(game.players[player_1_contract.address].victories == 0) 210 | scenario.verify(game.players[player_2_contract.address].victories == 1) 211 | scenario.verify(~game.started) 212 | scenario.verify(game.played_games == 1) 213 | 214 | # Play another game 215 | player_2_contract.play_game(game_id).run(sender=player_2) 216 | 217 | # Check that the information in the contract strorages is correct 218 | scenario.verify(player_1_contract.data.games[game_id].ball_hits == 2) 219 | scenario.verify(player_2_contract.data.games[game_id].ball_hits == 3) 220 | game = court_contract.data.games[game_id] 221 | scenario.verify(game.players[player_1_contract.address].victories == 1) 222 | scenario.verify(game.players[player_2_contract.address].victories == 1) 223 | scenario.verify(~game.started) 224 | scenario.verify(game.played_games == 2) 225 | 226 | # Play another game 227 | player_2_contract.play_game(game_id).run(sender=player_2) 228 | 229 | # Check that the information in the contract strorages is correct 230 | scenario.verify(player_1_contract.data.games[game_id].ball_hits == 2) 231 | scenario.verify(player_2_contract.data.games[game_id].ball_hits == 3) 232 | game = court_contract.data.games[game_id] 233 | scenario.verify(game.players[player_1_contract.address].victories == 2) 234 | scenario.verify(game.players[player_2_contract.address].victories == 1) 235 | scenario.verify(~game.started) 236 | scenario.verify(game.played_games == 3) 237 | --------------------------------------------------------------------------------