├── .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 |
--------------------------------------------------------------------------------