├── .gitignore ├── README.md ├── build └── tz │ ├── basic_proxy.tz │ ├── contract_proxy_bigmap.tz │ ├── factory.tz │ ├── hic_proxy.tz │ ├── lambdas │ ├── call │ │ ├── hic_mint_OBJKT.tz │ │ ├── teiaMarketplaceCancelSwap.json │ │ ├── teiaMarketplaceSwap.json │ │ ├── teia_marketplace_cancel_swap.tz │ │ └── teia_marketplace_swap.tz │ └── originate │ │ ├── basic_proxy.tz │ │ └── hic_proxy.tz │ ├── mock_view.tz │ ├── packer.tz │ ├── sign.tz │ └── swap_admin.tz ├── compile_and_test.sh ├── contracts ├── lambdas │ ├── call │ │ ├── hicMintOBJKT.ligo │ │ ├── teiaMarketplaceCancelSwap.ligo │ │ └── teiaMarketplaceSwap.ligo │ └── originate │ │ ├── basicProxy.ligo │ │ └── hicProxy.ligo ├── main │ ├── BasicProxy.ligo │ ├── ContractProxyBigMap.ligo │ ├── Factory.ligo │ ├── HicProxy.ligo │ ├── Migrations.ligo │ ├── MockView.ligo │ ├── Packer.ligo │ ├── Sign.ligo │ └── SwapAdmin.ligo └── partials │ ├── errors.ligo │ ├── fa2.ligo │ ├── factory.ligo │ ├── general.ligo │ ├── hicetnunc.ligo │ ├── management.ligo │ ├── proxyTypes.ligo │ └── sign.ligo ├── docs ├── factory_reference.md ├── hic_proxy_reference.md └── split-contract-mechanics.png ├── pytezos_tests ├── bigmap_interaction_test.py ├── contracts │ ├── curation.json │ ├── curation.tz │ ├── fa2_hdao.json │ ├── fa2_hdao.tz │ ├── fa2_objkts.json │ ├── fa2_objkts.tz │ ├── marketplace.json │ ├── marketplace.tz │ ├── marketplace_v3.json │ ├── marketplace_v3.tz │ ├── objkt_swap.json │ ├── objkt_swap.tz │ ├── subjkt.json │ └── subjkt.tz ├── factory_test.py ├── hic_base.py ├── hic_proxy_test.py ├── requirements.txt ├── sandbox_base.py ├── sandbox_hic_proxy_test.py ├── sign_test.py └── test_data.py └── scripts └── deploy_test_collab.py /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/contracts/ 3 | package-lock.json 4 | faucet.json 5 | *.ipynb 6 | **/.ipynb_checkpoints/* 7 | **/__pycache__/* 8 | pytezos_tests/new_contracts/* 9 | ithacanet.json 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Split-sales proxy contacts for collaboration 2 | 3 | This is experimental smart contract **factory** that allows to create **split-contracts**. Split-contracts can be used as a proxy to another contracts replacing `tz` address with `KT`, so this is kind of admin-managed entity. This enables onchain distribution for all incoming xtz. The project received support durring hicathon and this contracts was used to demonstrate various collaborative concepts within hic et nunc ecosystem. 4 | 5 | -------------------------------------------------------------------------------- 6 | NOTE: This contracts are not audited and they may have undetected vulnerabilities. This is just proof of concept. Use it for own risk and fun. 7 | -------------------------------------------------------------------------------- 8 | 9 | ## How it works? 10 | ![factory-split-contract-schema](docs/split-contract-mechanics.png) 11 | 12 | ### Factory and new split-contract creation 13 | Smart contract factory deployed in the mainnet: [`KT1..KP1N`](https://better-call.dev/mainnet/KT1DoyD6kr8yLK8mRBFusyKYJUk2ZxNHKP1N/operations). To create new collab, anyone can call `create_proxy` with two params: 14 | * `templateName`: string with template lambda used to originate new contract (`hic_proxy`) 15 | * `params`: bytes with data that unpacked inside selected template lambda. For `hic_proxy` template this is `participantsMap` that contains info about participant shares and roles 16 | 17 | You can find example of split-contract origination in mainnet using this op hash: [`oovy..G2PX`](https://better-call.dev/mainnet/opg/oovy63fP6XmzDvMy5LnaeKPzhe81ur8oTNDQQW7kspepiU2G2PX/contents) 18 | 19 | ### Templates and records 20 | Factory allow admin to add different proxy-contract templates using `add_template` entrypoint. There was template that was added during factory deploy: [`hic_proxy`](https://better-call.dev/mainnet/opg/ontPJgVMWCmQ1VbWSsEKs35uQNAFFRnXKBmjxXDDYu4ByDTPMaQ/contents). This template allows to create contracts that can mint and swap in h=n ecosystem. It is possible to add more templates with different functionality. It is possible to [update this template](https://better-call.dev/mainnet/opg/oofYy6YoDWbSNkwTTX85ZqzwwuA8aeQ8QF5uN6gd5QRNE8isEvf/contents) that will change the code of all newly created collab contracts. 21 | 22 | Special entrypoint `add_record` allows factory admin to add / rewrite some packed data with values that used for contract creation. Currently this used to manage ecosystem addresses that are added to the split-contracts storage during origination: minter address, FA2 token address, marketplace address, and registry address. 23 | 24 | ### Factory deploy process 25 | 1. Originating factory contract with empty storage (only admin should be specified) 26 | 2. Adding `hic_proxy` template for h=n 27 | 3. Adding all required records using `add_record` 28 | 29 | TODO: there should be pytezos script with all this deploy process 30 | Example with configuration batch call: [`ontP..PMaQ`](https://better-call.dev/mainnet/opg/ontPJgVMWCmQ1VbWSsEKs35uQNAFFRnXKBmjxXDDYu4ByDTPMaQ/contents) 31 | 32 | There is more details about Factory described [here](docs/factory_reference.md) 33 | 34 | ### HicProxy: the first split-contract implementation 35 | HicProxy is the first example of the split-contract proxy that allows to communicate with h=n ecosystem (mint, swap v2, registry, transfer tokens). It consists of two parts: [the contract](contracts/main/HicProxy.ligo) and lambda function used to originate this contract, [the template](contracts/lambdas/originate/hicProxy.ligo). 36 | 37 | HicProxy entrypoints described in [separate reference doc](docs/hic_proxy_reference.md). 38 | 39 | Admin can not change shares distribution after contract creation. Admin can mint new objkts from the contract name and swap them. Admin have rights to run any operation from the contract name. 40 | 41 | ### Sign: UI shield 42 | While working in wg3.2 during hicathon we found that there is potential vulnerability in UI that supports this split-contracts. The one can exploit UI by creating fake collaborations with famous artists. There is nothing bad to split your revenues with another artists without asking their permission to do this (at least this is impossible to stop). However, UI implementation supposed to show collaborations on the participant pages as special tab, so the one can exploit this to promote his works. 43 | 44 | In order to solve this issue **sign** contract added. This is fully independent contract that allows anyone to claim that he is one of the core participants of some objkt created using h=n. There are no checks that the one who claiming this actually participated in the collab (and there are no checks that signed objkt exist at all), so anyone can claim that he is participant of any objkt. However this is useful to verify objkts that was created under collabs and UI required to all core participants sign that they agreed to be shown as a core participant of the given objkt. 45 | 46 | As far as this information used only in UI and in combination with split-contract storage info, this is not a problem. In the future storage views can be implemented in Tezos which would simplify this onchain check process. 47 | 48 | ### SwapAdmin 49 | This is experimental smart contract that can be used as an admin replacement that allows everyone to swap their tokens from the split-contract name. This can be used in charity events when split-contract redistributes all incoming xtz to the given benefactor addresses and acts as a Gallery or Collection. Anyone can put their tokens for a given price to be sold from this Gallery. Royalties would go to the original artist and exceeded sale amount whould be distributed between Gallery benefactors. 50 | 51 | Example of this SwapAdmin in the mainnet: [`KT1..fYdP`](https://better-call.dev/mainnet/KT1C3xC8gcHzVLn3BJvAdjHRwY9PKiinfYdP/operations). You can go and swap whatever you want [using BCD](https://better-call.dev/mainnet/KT1C3xC8gcHzVLn3BJvAdjHRwY9PKiinfYdP/interact?entrypoint=swap) 52 | 53 | Example of the Gallery with swapped token: [SwapAdmin test](https://www.hicetnunc.xyz/objkt/297301) 54 | 55 | ## Technologies 56 | All contracts written in [PascaLIGO](https://ligolang.org/) v0.24. Docker image used to compile this contracts. There are some tests written using pytezos in pytezos\_tests directory. To run tests it is required to install [pytezos](https://pytezos.org/quick_start.html#installation), pytest and run `pytest -v` command. 57 | 58 | You can have a look at the compilation+test script: [compile\_and\_test](compile_and_test.sh). Yep, this is not the highest standards devops but I found this way to be simple and powerfull enough to use. Also there `package.json` and other node.js truffle operations that can be used as alternative way to compile and deploy contracts but I found this process not very transparent and they will be removed in the future. 59 | 60 | ## Unlocked use cases for this split-contracts 61 | - collaboration (multiple artists making one piece: all core participants) 62 | - remix/derivative works (remixer as core participant and original artist as benefactor) 63 | - artist management (artist as core participant + manager benefactor as admin) 64 | - benefactors gallery and charity events (benefactors only without core participants) 65 | - artist supporting open source (artist as core + benefactor) 66 | - you can suggest more? 67 | 68 | ## Next tasks: 69 | - looks like the thing that blocks UI from merge is indexer issues, so we need to run our own dipdup indexer that would be indexing split-contracts events 70 | 71 | ## Next mad ideas: 72 | - contract with dynamic shares (extra-mad if this shares would be FA2 token) 73 | - extra logic in `default` entrypoint 74 | * broker collaborator: buying hDAO from QuipuSwap and providing liquidity back to the hDAO/xtz pair 75 | * mint/distribute FA2 tokens on the primary sales (for everyone who buys from Gallery split-contract, for example). This may be possible if check transaction Sender is h=n marketplace 76 | * whitelisted gallery that allow to buy only for given list of allowed addresses (that either added during origination, either with some token staking logic) 77 | * FA2 token redistribution 78 | * Also this factory can be used to create this split-contracts for another cases, for example dividends split can be implemented or some split payments (we used this split-contract in wg3.2 to receive hicathon rewards) 79 | 80 | ## UI 81 | Here you can find forked hicetnunc frontend, that allows you to pick proxy contract by it address and mint/swap objects using it: https://github.com/ztepler/hicetnunc 82 | 83 | Big thanks to codewithfeeling who lead all this fork-end work and make this UI possible. 84 | 85 | -------------------------------------------------------------------------------- /build/tz/basic_proxy.tz: -------------------------------------------------------------------------------- 1 | { parameter 2 | (or (unit %default) 3 | (lambda %execute 4 | (pair (pair (address %administrator) (address %factory)) 5 | (pair (map %shares address nat) (nat %totalShares))) 6 | (list operation))) ; 7 | storage 8 | (pair (pair (address %administrator) (address %factory)) 9 | (pair (map %shares address nat) (nat %totalShares))) ; 10 | code { UNPAIR ; 11 | IF_LEFT 12 | { DROP ; NIL operation ; PAIR } 13 | { SWAP ; 14 | DUP ; 15 | DUG 2 ; 16 | CAR ; 17 | CAR ; 18 | SENDER ; 19 | COMPARE ; 20 | EQ ; 21 | IF {} { PUSH string "Entrypoint can call only administrator" ; FAILWITH } ; 22 | SWAP ; 23 | DUP ; 24 | DUG 2 ; 25 | EXEC ; 26 | PAIR } } } 27 | 28 | -------------------------------------------------------------------------------- /build/tz/contract_proxy_bigmap.tz: -------------------------------------------------------------------------------- 1 | { parameter 2 | (or (or (unit %default) 3 | (pair %mint_OBJKT 4 | (pair (address %address) (nat %amount)) 5 | (pair (bytes %metadata) (nat %royalties)))) 6 | (or (pair %swap 7 | (pair (address %creator) (nat %objkt_amount)) 8 | (pair (nat %objkt_id) (pair (nat %royalties) (mutez %xtz_per_objkt)))) 9 | (unit %withdraw))) ; 10 | storage 11 | (pair (pair (pair (big_map %accounts address (pair (nat %share) (nat %withdrawalsSum))) 12 | (address %administrator)) 13 | (pair (address %marketplaceAddress) (address %minterAddress))) 14 | (nat %totalWithdrawalsSum)) ; 15 | code { LAMBDA 16 | (pair (pair (pair (big_map address (pair nat nat)) address) (pair address address)) nat) 17 | unit 18 | { CAR ; 19 | CAR ; 20 | CDR ; 21 | SENDER ; 22 | COMPARE ; 23 | EQ ; 24 | IF { UNIT } 25 | { PUSH string "Entrypoint can call only administrator" ; FAILWITH } } ; 26 | SWAP ; 27 | UNPAIR ; 28 | IF_LEFT 29 | { IF_LEFT 30 | { DIG 2 ; DROP 2 ; NIL operation ; PAIR } 31 | { SWAP ; 32 | DUP ; 33 | DIG 3 ; 34 | SWAP ; 35 | EXEC ; 36 | DROP ; 37 | DUP ; 38 | DUG 2 ; 39 | CAR ; 40 | CDR ; 41 | CDR ; 42 | CONTRACT %mint_OBJKT 43 | (pair (pair (address %address) (nat %amount)) (pair (bytes %metadata) (nat %royalties))) ; 44 | IF_NONE { PUSH string "MINT_NF" ; FAILWITH } {} ; 45 | PUSH mutez 0 ; 46 | DIG 2 ; 47 | TRANSFER_TOKENS ; 48 | SWAP ; 49 | NIL operation ; 50 | DIG 2 ; 51 | CONS ; 52 | PAIR } } 53 | { IF_LEFT 54 | { SWAP ; 55 | DUP ; 56 | DIG 3 ; 57 | SWAP ; 58 | EXEC ; 59 | DROP ; 60 | DUP ; 61 | DUG 2 ; 62 | CAR ; 63 | CDR ; 64 | CAR ; 65 | CONTRACT %swap 66 | (pair (pair (address %creator) (nat %objkt_amount)) 67 | (pair (nat %objkt_id) (pair (nat %royalties) (mutez %xtz_per_objkt)))) ; 68 | IF_NONE { PUSH string "SWAP_NF" ; FAILWITH } {} ; 69 | PUSH mutez 0 ; 70 | DIG 2 ; 71 | TRANSFER_TOKENS ; 72 | SWAP ; 73 | NIL operation ; 74 | DIG 2 ; 75 | CONS ; 76 | PAIR } 77 | { DIG 2 ; 78 | DROP 2 ; 79 | DUP ; 80 | CDR ; 81 | BALANCE ; 82 | PUSH mutez 1 ; 83 | SWAP ; 84 | EDIV ; 85 | IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; 86 | CAR ; 87 | ADD ; 88 | SWAP ; 89 | DUP ; 90 | DUG 2 ; 91 | SENDER ; 92 | SWAP ; 93 | CAR ; 94 | CAR ; 95 | CAR ; 96 | SWAP ; 97 | GET ; 98 | IF_NONE { PUSH nat 0 ; PUSH nat 0 ; PAIR } {} ; 99 | PUSH nat 1000 ; 100 | DUG 2 ; 101 | DUP ; 102 | DUG 3 ; 103 | CAR ; 104 | MUL ; 105 | EDIV ; 106 | IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; 107 | CAR ; 108 | SWAP ; 109 | DUP ; 110 | DUG 2 ; 111 | CDR ; 112 | SWAP ; 113 | SUB ; 114 | ABS ; 115 | PUSH nat 0 ; 116 | SWAP ; 117 | DUP ; 118 | DUG 2 ; 119 | COMPARE ; 120 | EQ ; 121 | IF { PUSH string "Nothing to withdraw" ; FAILWITH } {} ; 122 | SENDER ; 123 | CONTRACT unit ; 124 | IF_NONE { PUSH string "ADDR_NF" ; FAILWITH } {} ; 125 | PUSH mutez 1 ; 126 | DUP 3 ; 127 | MUL ; 128 | UNIT ; 129 | TRANSFER_TOKENS ; 130 | SWAP ; 131 | DUP ; 132 | DUG 2 ; 133 | DUP 5 ; 134 | CDR ; 135 | ADD ; 136 | DIG 4 ; 137 | CAR ; 138 | PAIR ; 139 | DIG 2 ; 140 | DUP 4 ; 141 | CDR ; 142 | ADD ; 143 | DIG 3 ; 144 | CAR ; 145 | PAIR ; 146 | SWAP ; 147 | DUP ; 148 | DUG 2 ; 149 | CDR ; 150 | DUP 3 ; 151 | CAR ; 152 | CDR ; 153 | DUP 4 ; 154 | CAR ; 155 | CAR ; 156 | CDR ; 157 | DIG 4 ; 158 | CAR ; 159 | CAR ; 160 | CAR ; 161 | DIG 4 ; 162 | SOME ; 163 | SENDER ; 164 | UPDATE ; 165 | PAIR ; 166 | PAIR ; 167 | PAIR ; 168 | NIL operation ; 169 | DIG 2 ; 170 | CONS ; 171 | PAIR } } } } 172 | 173 | -------------------------------------------------------------------------------- /build/tz/factory.tz: -------------------------------------------------------------------------------- 1 | { parameter 2 | (or (or (or (unit %accept_ownership) (pair %add_record (string %name) (bytes %value))) 3 | (or (pair %add_template 4 | (string %name) 5 | (lambda %originateFunc 6 | (pair (big_map string bytes) bytes) 7 | (pair (pair (address %address) (bytes %metadata)) (operation %operation)))) 8 | (pair %create_proxy (bytes %params) (string %templateName)))) 9 | (or (or (pair %is_originated_contract 10 | (contract %callback bool) 11 | (address %contractAddress)) 12 | (string %remove_record)) 13 | (or (string %remove_template) (address %update_admin)))) ; 14 | storage 15 | (pair (pair (pair (address %administrator) (big_map %originatedContracts address bytes)) 16 | (pair (option %proposedAdministrator address) (big_map %records string bytes))) 17 | (map %templates 18 | string 19 | (lambda 20 | (pair (big_map string bytes) bytes) 21 | (pair (pair (address %address) (bytes %metadata)) (operation %operation))))) ; 22 | code { LAMBDA 23 | unit 24 | unit 25 | { DROP ; 26 | PUSH mutez 0 ; 27 | AMOUNT ; 28 | COMPARE ; 29 | EQ ; 30 | IF { UNIT } { PUSH string "AMNT_FRBD" ; FAILWITH } } ; 31 | LAMBDA 32 | (pair (pair (pair address (big_map address bytes)) 33 | (pair (option address) (big_map string bytes))) 34 | (map string 35 | (lambda (pair (big_map string bytes) bytes) (pair (pair address bytes) operation)))) 36 | unit 37 | { CAR ; 38 | CAR ; 39 | CAR ; 40 | SENDER ; 41 | COMPARE ; 42 | EQ ; 43 | IF { UNIT } { PUSH string "NOT_ADM" ; FAILWITH } } ; 44 | DIG 2 ; 45 | UNPAIR ; 46 | IF_LEFT 47 | { IF_LEFT 48 | { IF_LEFT 49 | { DIG 2 ; 50 | DROP 2 ; 51 | UNIT ; 52 | DIG 2 ; 53 | SWAP ; 54 | EXEC ; 55 | DROP ; 56 | DUP ; 57 | CAR ; 58 | CDR ; 59 | CAR ; 60 | IF_NONE { PUSH string "NOT_PROPOSED" ; FAILWITH } {} ; 61 | DUP ; 62 | SENDER ; 63 | COMPARE ; 64 | EQ ; 65 | IF { SWAP ; 66 | DUP ; 67 | DUG 2 ; 68 | CDR ; 69 | DUP 3 ; 70 | CAR ; 71 | CDR ; 72 | DIG 3 ; 73 | CAR ; 74 | CAR ; 75 | CDR ; 76 | DIG 3 ; 77 | PAIR ; 78 | PAIR ; 79 | PAIR ; 80 | DUP ; 81 | CDR ; 82 | SWAP ; 83 | DUP ; 84 | DUG 2 ; 85 | CAR ; 86 | CDR ; 87 | CDR ; 88 | NONE address ; 89 | PAIR ; 90 | DIG 2 ; 91 | CAR ; 92 | CAR ; 93 | PAIR ; 94 | PAIR } 95 | { DROP ; PUSH string "NOT_PROPOSED" ; FAILWITH } ; 96 | NIL operation ; 97 | PAIR } 98 | { UNIT ; 99 | DIG 4 ; 100 | SWAP ; 101 | EXEC ; 102 | DROP ; 103 | SWAP ; 104 | DUP ; 105 | DUG 2 ; 106 | DIG 3 ; 107 | SWAP ; 108 | EXEC ; 109 | DROP ; 110 | SWAP ; 111 | DUP ; 112 | DUG 2 ; 113 | CDR ; 114 | DUP 3 ; 115 | CAR ; 116 | CDR ; 117 | CDR ; 118 | DUP 3 ; 119 | CDR ; 120 | DIG 3 ; 121 | CAR ; 122 | SWAP ; 123 | SOME ; 124 | SWAP ; 125 | UPDATE ; 126 | DUP 3 ; 127 | CAR ; 128 | CDR ; 129 | CAR ; 130 | PAIR ; 131 | DIG 2 ; 132 | CAR ; 133 | CAR ; 134 | PAIR ; 135 | PAIR ; 136 | NIL operation ; 137 | PAIR } } 138 | { IF_LEFT 139 | { UNIT ; 140 | DIG 4 ; 141 | SWAP ; 142 | EXEC ; 143 | DROP ; 144 | SWAP ; 145 | DUP ; 146 | DUG 2 ; 147 | DIG 3 ; 148 | SWAP ; 149 | EXEC ; 150 | DROP ; 151 | SWAP ; 152 | DUP ; 153 | DUG 2 ; 154 | CDR ; 155 | SWAP ; 156 | DUP ; 157 | DUG 2 ; 158 | CDR ; 159 | DIG 2 ; 160 | CAR ; 161 | SWAP ; 162 | SOME ; 163 | SWAP ; 164 | UPDATE ; 165 | SWAP ; 166 | CAR ; 167 | PAIR ; 168 | NIL operation ; 169 | PAIR } 170 | { DIG 2 ; 171 | DROP ; 172 | UNIT ; 173 | DIG 3 ; 174 | SWAP ; 175 | EXEC ; 176 | DROP ; 177 | SWAP ; 178 | DUP ; 179 | DUG 2 ; 180 | CDR ; 181 | SWAP ; 182 | DUP ; 183 | DUG 2 ; 184 | CDR ; 185 | GET ; 186 | IF_NONE { PUSH string "TEMPLATE_NF" ; FAILWITH } {} ; 187 | SWAP ; 188 | CAR ; 189 | DUP 3 ; 190 | CAR ; 191 | CDR ; 192 | CDR ; 193 | PAIR ; 194 | EXEC ; 195 | SWAP ; 196 | DUP ; 197 | DUG 2 ; 198 | CDR ; 199 | DUP 3 ; 200 | CAR ; 201 | CDR ; 202 | DUP 4 ; 203 | CAR ; 204 | CAR ; 205 | CDR ; 206 | DUP 4 ; 207 | CAR ; 208 | CDR ; 209 | DUP 5 ; 210 | CAR ; 211 | CAR ; 212 | SWAP ; 213 | SOME ; 214 | SWAP ; 215 | UPDATE ; 216 | DIG 4 ; 217 | CAR ; 218 | CAR ; 219 | CAR ; 220 | PAIR ; 221 | PAIR ; 222 | PAIR ; 223 | NIL operation ; 224 | DIG 2 ; 225 | CDR ; 226 | CONS ; 227 | PAIR } } } 228 | { IF_LEFT 229 | { IF_LEFT 230 | { DIG 2 ; 231 | DROP ; 232 | UNIT ; 233 | DIG 3 ; 234 | SWAP ; 235 | EXEC ; 236 | DROP ; 237 | DUP ; 238 | CAR ; 239 | PUSH mutez 0 ; 240 | DUP 4 ; 241 | CAR ; 242 | CAR ; 243 | CDR ; 244 | DIG 3 ; 245 | CDR ; 246 | GET ; 247 | IF_NONE { PUSH bool False } { DROP ; PUSH bool True } ; 248 | TRANSFER_TOKENS ; 249 | SWAP ; 250 | NIL operation ; 251 | DIG 2 ; 252 | CONS ; 253 | PAIR } 254 | { UNIT ; 255 | DIG 4 ; 256 | SWAP ; 257 | EXEC ; 258 | DROP ; 259 | SWAP ; 260 | DUP ; 261 | DUG 2 ; 262 | DIG 3 ; 263 | SWAP ; 264 | EXEC ; 265 | DROP ; 266 | SWAP ; 267 | DUP ; 268 | DUG 2 ; 269 | CDR ; 270 | DUP 3 ; 271 | CAR ; 272 | CDR ; 273 | CDR ; 274 | DIG 2 ; 275 | NONE bytes ; 276 | SWAP ; 277 | UPDATE ; 278 | DUP 3 ; 279 | CAR ; 280 | CDR ; 281 | CAR ; 282 | PAIR ; 283 | DIG 2 ; 284 | CAR ; 285 | CAR ; 286 | PAIR ; 287 | PAIR ; 288 | NIL operation ; 289 | PAIR } } 290 | { IF_LEFT 291 | { UNIT ; 292 | DIG 4 ; 293 | SWAP ; 294 | EXEC ; 295 | DROP ; 296 | SWAP ; 297 | DUP ; 298 | DUG 2 ; 299 | DIG 3 ; 300 | SWAP ; 301 | EXEC ; 302 | DROP ; 303 | SWAP ; 304 | DUP ; 305 | DUG 2 ; 306 | CDR ; 307 | SWAP ; 308 | NONE (lambda (pair (big_map string bytes) bytes) (pair (pair address bytes) operation)) ; 309 | SWAP ; 310 | UPDATE ; 311 | SWAP ; 312 | CAR ; 313 | PAIR ; 314 | NIL operation ; 315 | PAIR } 316 | { SWAP ; 317 | UNIT ; 318 | DIG 4 ; 319 | SWAP ; 320 | EXEC ; 321 | DROP ; 322 | DUP ; 323 | DIG 3 ; 324 | SWAP ; 325 | EXEC ; 326 | DROP ; 327 | DUP ; 328 | CDR ; 329 | SWAP ; 330 | DUP ; 331 | DUG 2 ; 332 | CAR ; 333 | CDR ; 334 | CDR ; 335 | DIG 3 ; 336 | SOME ; 337 | PAIR ; 338 | DIG 2 ; 339 | CAR ; 340 | CAR ; 341 | PAIR ; 342 | PAIR ; 343 | NIL operation ; 344 | PAIR } } } } } 345 | 346 | -------------------------------------------------------------------------------- /build/tz/lambdas/call/hic_mint_OBJKT.tz: -------------------------------------------------------------------------------- 1 | { UNPAIR ; 2 | SWAP ; 3 | UNPACK 4 | (pair (pair (address %address) (nat %amount)) (pair (bytes %metadata) (nat %royalties))) ; 5 | IF_NONE { PUSH string "Unpack failed" ; FAILWITH } {} ; 6 | SWAP ; 7 | CAR ; 8 | CDR ; 9 | CAR ; 10 | CAR ; 11 | CONTRACT %mint_OBJKT 12 | (pair (pair (address %address) (nat %amount)) (pair (bytes %metadata) (nat %royalties))) ; 13 | IF_NONE { PUSH string "MINT_NF" ; FAILWITH } {} ; 14 | PUSH mutez 0 ; 15 | DIG 2 ; 16 | TRANSFER_TOKENS ; 17 | NIL operation ; 18 | SWAP ; 19 | CONS } 20 | 21 | -------------------------------------------------------------------------------- /build/tz/lambdas/call/teiaMarketplaceCancelSwap.json: -------------------------------------------------------------------------------- 1 | [ { "prim": "CDR" }, 2 | { "prim": "UNPACK", 3 | "args": 4 | [ { "prim": "pair", 5 | "args": 6 | [ { "prim": "address", "annots": [ "%marketplaceAddress" ] }, 7 | { "prim": "nat", "annots": [ "%swap_id" ] } ] } ] }, 8 | { "prim": "IF_NONE", 9 | "args": 10 | [ [ { "prim": "PUSH", 11 | "args": [ { "prim": "string" }, { "string": "Unpack failed" } ] }, 12 | { "prim": "FAILWITH" } ], [] ] }, { "prim": "DUP" }, 13 | { "prim": "CAR" }, 14 | { "prim": "CONTRACT", "args": [ { "prim": "nat" } ], 15 | "annots": [ "%cancel_swap" ] }, 16 | { "prim": "IF_NONE", 17 | "args": 18 | [ [ { "prim": "PUSH", 19 | "args": 20 | [ { "prim": "string" }, 21 | { "string": "Marketplace V3 is not found" } ] }, 22 | { "prim": "FAILWITH" } ], [] ] }, 23 | { "prim": "PUSH", "args": [ { "prim": "mutez" }, { "int": "0" } ] }, 24 | { "prim": "DIG", "args": [ { "int": "2" } ] }, { "prim": "CDR" }, 25 | { "prim": "TRANSFER_TOKENS" }, 26 | { "prim": "NIL", "args": [ { "prim": "operation" } ] }, { "prim": "SWAP" }, 27 | { "prim": "CONS" } ] 28 | 29 | -------------------------------------------------------------------------------- /build/tz/lambdas/call/teiaMarketplaceSwap.json: -------------------------------------------------------------------------------- 1 | [ { "prim": "CDR" }, 2 | { "prim": "UNPACK", 3 | "args": 4 | [ { "prim": "pair", 5 | "args": 6 | [ { "prim": "address", "annots": [ "%marketplaceAddress" ] }, 7 | { "prim": "pair", 8 | "args": 9 | [ { "prim": "address", "annots": [ "%fa2" ] }, 10 | { "prim": "pair", 11 | "args": 12 | [ { "prim": "nat", "annots": [ "%objkt_id" ] }, 13 | { "prim": "pair", 14 | "args": 15 | [ { "prim": "nat", 16 | "annots": [ "%objkt_amount" ] }, 17 | { "prim": "pair", 18 | "args": 19 | [ { "prim": "mutez", 20 | "annots": [ "%xtz_per_objkt" ] }, 21 | { "prim": "pair", 22 | "args": 23 | [ { "prim": "nat", 24 | "annots": [ "%royalties" ] }, 25 | { "prim": "address", 26 | "annots": [ "%creator" ] } ] } ] } ] } ] } ], 27 | "annots": [ "%params" ] } ] } ] }, 28 | { "prim": "IF_NONE", 29 | "args": 30 | [ [ { "prim": "PUSH", 31 | "args": [ { "prim": "string" }, { "string": "Unpack failed" } ] }, 32 | { "prim": "FAILWITH" } ], [] ] }, { "prim": "DUP" }, 33 | { "prim": "CAR" }, 34 | { "prim": "CONTRACT", 35 | "args": 36 | [ { "prim": "pair", 37 | "args": 38 | [ { "prim": "address", "annots": [ "%fa2" ] }, 39 | { "prim": "pair", 40 | "args": 41 | [ { "prim": "nat", "annots": [ "%objkt_id" ] }, 42 | { "prim": "pair", 43 | "args": 44 | [ { "prim": "nat", "annots": [ "%objkt_amount" ] }, 45 | { "prim": "pair", 46 | "args": 47 | [ { "prim": "mutez", 48 | "annots": [ "%xtz_per_objkt" ] }, 49 | { "prim": "pair", 50 | "args": 51 | [ { "prim": "nat", 52 | "annots": [ "%royalties" ] }, 53 | { "prim": "address", 54 | "annots": [ "%creator" ] } ] } ] } ] } ] } ] } ], 55 | "annots": [ "%swap" ] }, 56 | { "prim": "IF_NONE", 57 | "args": 58 | [ [ { "prim": "PUSH", 59 | "args": 60 | [ { "prim": "string" }, 61 | { "string": "Marketplace V3 is not found" } ] }, 62 | { "prim": "FAILWITH" } ], [] ] }, 63 | { "prim": "PUSH", "args": [ { "prim": "mutez" }, { "int": "0" } ] }, 64 | { "prim": "DIG", "args": [ { "int": "2" } ] }, { "prim": "CDR" }, 65 | { "prim": "TRANSFER_TOKENS" }, 66 | { "prim": "NIL", "args": [ { "prim": "operation" } ] }, { "prim": "SWAP" }, 67 | { "prim": "CONS" } ] 68 | 69 | -------------------------------------------------------------------------------- /build/tz/lambdas/call/teia_marketplace_cancel_swap.tz: -------------------------------------------------------------------------------- 1 | { CDR ; 2 | UNPACK (pair (address %marketplaceAddress) (nat %swap_id)) ; 3 | IF_NONE { PUSH string "Unpack failed" ; FAILWITH } {} ; 4 | DUP ; 5 | CAR ; 6 | CONTRACT %cancel_swap nat ; 7 | IF_NONE { PUSH string "Marketplace V3 is not found" ; FAILWITH } {} ; 8 | PUSH mutez 0 ; 9 | DIG 2 ; 10 | CDR ; 11 | TRANSFER_TOKENS ; 12 | NIL operation ; 13 | SWAP ; 14 | CONS } 15 | 16 | -------------------------------------------------------------------------------- /build/tz/lambdas/call/teia_marketplace_swap.tz: -------------------------------------------------------------------------------- 1 | { CDR ; 2 | UNPACK 3 | (pair (address %marketplaceAddress) 4 | (pair %params 5 | (address %fa2) 6 | (pair (nat %objkt_id) 7 | (pair (nat %objkt_amount) 8 | (pair (mutez %xtz_per_objkt) (pair (nat %royalties) (address %creator))))))) ; 9 | IF_NONE { PUSH string "Unpack failed" ; FAILWITH } {} ; 10 | DUP ; 11 | CAR ; 12 | CONTRACT %swap 13 | (pair (address %fa2) 14 | (pair (nat %objkt_id) 15 | (pair (nat %objkt_amount) 16 | (pair (mutez %xtz_per_objkt) (pair (nat %royalties) (address %creator)))))) ; 17 | IF_NONE { PUSH string "Marketplace V3 is not found" ; FAILWITH } {} ; 18 | PUSH mutez 0 ; 19 | DIG 2 ; 20 | CDR ; 21 | TRANSFER_TOKENS ; 22 | NIL operation ; 23 | SWAP ; 24 | CONS } 25 | 26 | -------------------------------------------------------------------------------- /build/tz/lambdas/originate/basic_proxy.tz: -------------------------------------------------------------------------------- 1 | { CDR ; 2 | UNPACK (map address nat) ; 3 | IF_NONE { PUSH string "Unpack failed" ; FAILWITH } {} ; 4 | PUSH nat 0 ; 5 | SWAP ; 6 | DUP ; 7 | DUG 2 ; 8 | ITER { SWAP ; PAIR ; DUP ; CDR ; CDR ; SWAP ; CAR ; ADD } ; 9 | PUSH nat 0 ; 10 | SWAP ; 11 | DUP ; 12 | DUG 2 ; 13 | COMPARE ; 14 | EQ ; 15 | IF { PUSH string "Sum of the shares should be more than 0n" ; FAILWITH } {} ; 16 | SWAP ; 17 | PAIR ; 18 | SELF_ADDRESS ; 19 | SENDER ; 20 | PAIR ; 21 | PAIR ; 22 | PUSH mutez 0 ; 23 | NONE key_hash ; 24 | PAIR ; 25 | PAIR ; 26 | { UNPAIR ; UNPAIR } ; 27 | CREATE_CONTRACT 28 | { parameter 29 | (or (unit %default) 30 | (lambda %execute 31 | (pair (pair (address %administrator) (address %factory)) 32 | (pair (map %shares address nat) (nat %totalShares))) 33 | (list operation))) ; 34 | storage 35 | (pair (pair (address %administrator) (address %factory)) 36 | (pair (map %shares address nat) (nat %totalShares))) ; 37 | code { UNPAIR ; 38 | IF_LEFT 39 | { DROP ; NIL operation ; PAIR } 40 | { SWAP ; 41 | DUP ; 42 | DUG 2 ; 43 | CAR ; 44 | CAR ; 45 | SENDER ; 46 | COMPARE ; 47 | EQ ; 48 | IF {} { PUSH string "Entrypoint can call only administrator" ; FAILWITH } ; 49 | SWAP ; 50 | DUP ; 51 | DUG 2 ; 52 | EXEC ; 53 | PAIR } } } ; 54 | PAIR ; 55 | DUP ; 56 | CAR ; 57 | PUSH string "basic_proxy" ; 58 | PACK ; 59 | DIG 2 ; 60 | CDR ; 61 | PAIR ; 62 | PAIR } 63 | 64 | -------------------------------------------------------------------------------- /build/tz/mock_view.tz: -------------------------------------------------------------------------------- 1 | { parameter (or (bool %bool_view) (nat %nat_view)) ; 2 | storage (pair (option %boolValue bool) (option %natValue nat)) ; 3 | code { CAR ; 4 | IF_LEFT { NONE nat ; SWAP ; SOME ; PAIR } { SOME ; NONE bool ; PAIR } ; 5 | NIL operation ; 6 | PAIR } } 7 | 8 | -------------------------------------------------------------------------------- /build/tz/packer.tz: -------------------------------------------------------------------------------- 1 | { parameter 2 | (or (or (or (address %pack_address) 3 | (pair %pack_mint_OBJKT 4 | (pair (address %address) (nat %amount)) 5 | (pair (bytes %metadata) (nat %royalties)))) 6 | (or (nat %pack_nat) (map %pack_originate_basic_proxy address nat))) 7 | (map %pack_originate_hic_proxy address (pair (bool %isCore) (nat %share)))) ; 8 | storage bytes ; 9 | code { CAR ; 10 | IF_LEFT 11 | { IF_LEFT 12 | { IF_LEFT { PACK ; NIL operation ; PAIR } { PACK ; NIL operation ; PAIR } } 13 | { IF_LEFT { PACK ; NIL operation ; PAIR } { PACK ; NIL operation ; PAIR } } } 14 | { PACK ; NIL operation ; PAIR } } } 15 | 16 | -------------------------------------------------------------------------------- /build/tz/sign.tz: -------------------------------------------------------------------------------- 1 | { parameter 2 | (or (or (pair %is_signed (pair (contract %callback bool) (nat %id)) (address %participant)) 3 | (nat %sign)) 4 | (nat %unsign)) ; 5 | storage (big_map (pair address nat) unit) ; 6 | code { LAMBDA 7 | unit 8 | unit 9 | { DROP ; 10 | PUSH mutez 0 ; 11 | AMOUNT ; 12 | COMPARE ; 13 | EQ ; 14 | IF { UNIT } { PUSH string "AMNT_FRBD" ; FAILWITH } } ; 15 | SWAP ; 16 | UNPAIR ; 17 | IF_LEFT 18 | { IF_LEFT 19 | { SWAP ; 20 | UNIT ; 21 | DIG 3 ; 22 | SWAP ; 23 | EXEC ; 24 | DROP ; 25 | SWAP ; 26 | DUP ; 27 | DUG 2 ; 28 | CAR ; 29 | CAR ; 30 | PUSH mutez 0 ; 31 | DUP 3 ; 32 | DUP 5 ; 33 | CAR ; 34 | CDR ; 35 | DIG 5 ; 36 | CDR ; 37 | PAIR ; 38 | GET ; 39 | IF_NONE { PUSH bool False } { DROP ; PUSH bool True } ; 40 | TRANSFER_TOKENS ; 41 | SWAP ; 42 | NIL operation ; 43 | DIG 2 ; 44 | CONS ; 45 | PAIR } 46 | { SWAP ; 47 | UNIT ; 48 | DIG 3 ; 49 | SWAP ; 50 | EXEC ; 51 | DROP ; 52 | SWAP ; 53 | SENDER ; 54 | PAIR ; 55 | SWAP ; 56 | UNIT ; 57 | DIG 2 ; 58 | SWAP ; 59 | SOME ; 60 | SWAP ; 61 | UPDATE ; 62 | NIL operation ; 63 | PAIR } } 64 | { SWAP ; 65 | UNIT ; 66 | DIG 3 ; 67 | SWAP ; 68 | EXEC ; 69 | DROP ; 70 | SWAP ; 71 | SENDER ; 72 | PAIR ; 73 | NONE unit ; 74 | SWAP ; 75 | UPDATE ; 76 | NIL operation ; 77 | PAIR } } } 78 | 79 | -------------------------------------------------------------------------------- /build/tz/swap_admin.tz: -------------------------------------------------------------------------------- 1 | { parameter 2 | (or (or (unit %accept_gallery_ownership) (unit %return_admin)) 3 | (pair %swap 4 | (pair (address %creator) (nat %objkt_amount)) 5 | (pair (nat %objkt_id) (pair (nat %royalties) (mutez %xtz_per_objkt))))) ; 6 | storage 7 | (pair (pair (address %administrator) (address %galleryAddress)) 8 | (pair (address %marketplaceAddress) (address %tokenAddress))) ; 9 | code { LAMBDA 10 | unit 11 | unit 12 | { DROP ; 13 | PUSH mutez 0 ; 14 | AMOUNT ; 15 | COMPARE ; 16 | EQ ; 17 | IF { UNIT } { PUSH string "AMNT_FRBD" ; FAILWITH } } ; 18 | LAMBDA 19 | (pair address (list (or (pair address address nat) (pair address address nat)))) 20 | operation 21 | { UNPAIR ; 22 | CONTRACT %update_operators 23 | (list (or (pair %add_operator (address %owner) (address %operator) (nat %token_id)) 24 | (pair %remove_operator (address %owner) (address %operator) (nat %token_id)))) ; 25 | IF_NONE { PUSH string "FA2_NF" ; FAILWITH } {} ; 26 | PUSH mutez 0 ; 27 | DIG 2 ; 28 | TRANSFER_TOKENS } ; 29 | DIG 2 ; 30 | UNPAIR ; 31 | IF_LEFT 32 | { DIG 2 ; 33 | DROP ; 34 | IF_LEFT 35 | { DROP ; 36 | UNIT ; 37 | DIG 2 ; 38 | SWAP ; 39 | EXEC ; 40 | DROP ; 41 | DUP ; 42 | CAR ; 43 | CDR ; 44 | CONTRACT %accept_ownership unit ; 45 | IF_NONE { PUSH string "No gallery found" ; FAILWITH } {} ; 46 | PUSH mutez 0 ; 47 | UNIT ; 48 | TRANSFER_TOKENS ; 49 | SWAP ; 50 | NIL operation ; 51 | DIG 2 ; 52 | CONS ; 53 | PAIR } 54 | { DROP ; 55 | UNIT ; 56 | DIG 2 ; 57 | SWAP ; 58 | EXEC ; 59 | DROP ; 60 | DUP ; 61 | CAR ; 62 | CAR ; 63 | SENDER ; 64 | COMPARE ; 65 | EQ ; 66 | IF {} { PUSH string "Entrypoint can call only administrator" ; FAILWITH } ; 67 | DUP ; 68 | CAR ; 69 | CAR ; 70 | SWAP ; 71 | DUP ; 72 | DUG 2 ; 73 | CAR ; 74 | CDR ; 75 | CONTRACT %update_admin address ; 76 | IF_NONE { PUSH string "No gallery found" ; FAILWITH } {} ; 77 | PUSH mutez 0 ; 78 | DIG 2 ; 79 | TRANSFER_TOKENS ; 80 | SWAP ; 81 | NIL operation ; 82 | DIG 2 ; 83 | CONS ; 84 | PAIR } } 85 | { SWAP ; 86 | UNIT ; 87 | DIG 4 ; 88 | SWAP ; 89 | EXEC ; 90 | DROP ; 91 | NIL (pair address (list (pair address nat nat))) ; 92 | NIL (pair address nat nat) ; 93 | DUP 4 ; 94 | CAR ; 95 | CDR ; 96 | DUP 5 ; 97 | CDR ; 98 | CAR ; 99 | DUP 5 ; 100 | CAR ; 101 | CDR ; 102 | PAIR 3 ; 103 | CONS ; 104 | SENDER ; 105 | PAIR ; 106 | CONS ; 107 | SWAP ; 108 | DUP ; 109 | DUG 2 ; 110 | CDR ; 111 | CDR ; 112 | CONTRACT %transfer 113 | (list (pair (address %from_) (list %txs (pair (address %to_) (nat %token_id) (nat %amount))))) ; 114 | IF_NONE { PUSH string "FA2_NF" ; FAILWITH } {} ; 115 | PUSH mutez 0 ; 116 | DIG 2 ; 117 | TRANSFER_TOKENS ; 118 | DUP 3 ; 119 | CDR ; 120 | CAR ; 121 | DUP 3 ; 122 | CDR ; 123 | CAR ; 124 | DUP 4 ; 125 | CAR ; 126 | CDR ; 127 | PAIR 3 ; 128 | NIL (or (pair address address nat) (pair address address nat)) ; 129 | SWAP ; 130 | DUP ; 131 | DUG 2 ; 132 | LEFT (pair address address nat) ; 133 | CONS ; 134 | DUP 4 ; 135 | CAR ; 136 | CDR ; 137 | PAIR ; 138 | DUP 6 ; 139 | SWAP ; 140 | EXEC ; 141 | NIL (or (pair address address nat) (pair address address nat)) ; 142 | DIG 2 ; 143 | RIGHT (pair address address nat) ; 144 | CONS ; 145 | DUP 4 ; 146 | CAR ; 147 | CDR ; 148 | PAIR ; 149 | DIG 5 ; 150 | SWAP ; 151 | EXEC ; 152 | DIG 4 ; 153 | DUP 5 ; 154 | CAR ; 155 | CDR ; 156 | CONTRACT %swap 157 | (pair (pair (address %creator) (nat %objkt_amount)) 158 | (pair (nat %objkt_id) (pair (nat %royalties) (mutez %xtz_per_objkt)))) ; 159 | IF_NONE { PUSH string "SWAP_NF" ; FAILWITH } {} ; 160 | PUSH mutez 0 ; 161 | DIG 2 ; 162 | TRANSFER_TOKENS ; 163 | DIG 4 ; 164 | NIL operation ; 165 | DIG 3 ; 166 | CONS ; 167 | DIG 2 ; 168 | CONS ; 169 | DIG 2 ; 170 | CONS ; 171 | DIG 2 ; 172 | CONS ; 173 | PAIR } } } 174 | 175 | -------------------------------------------------------------------------------- /compile_and_test.sh: -------------------------------------------------------------------------------- 1 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile contract contracts/main/HicProxy.ligo -e main --protocol hangzhou > build/tz/hic_proxy.tz 2 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile contract contracts/main/BasicProxy.ligo -e main --protocol hangzhou > build/tz/basic_proxy.tz 3 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile contract contracts/main/ContractProxyBigMap.ligo -e main --protocol hangzhou > build/tz/contract_proxy_bigmap.tz 4 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile contract contracts/main/Factory.ligo -e main --protocol hangzhou > build/tz/factory.tz 5 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile contract contracts/main/Sign.ligo -e main --protocol hangzhou > build/tz/sign.tz 6 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile contract contracts/main/Packer.ligo -e main --protocol hangzhou > build/tz/packer.tz 7 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile contract contracts/main/SwapAdmin.ligo -e main --protocol hangzhou > build/tz/swap_admin.tz 8 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile contract contracts/main/MockView.ligo -e main > build/tz/mock_view.tz 9 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile expression pascaligo lambda --init-file "contracts/lambdas/call/hicMintOBJKT.ligo" > build/tz/lambdas/call/hic_mint_OBJKT.tz 10 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile expression pascaligo lambda --init-file "contracts/lambdas/originate/hicProxy.ligo" > build/tz/lambdas/originate/hic_proxy.tz 11 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile expression pascaligo lambda --init-file "contracts/lambdas/originate/basicProxy.ligo" > build/tz/lambdas/originate/basic_proxy.tz 12 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile expression pascaligo lambda --init-file "contracts/lambdas/call/teiaMarketplaceSwap.ligo" > build/tz/lambdas/call/teia_marketplace_swap.tz 13 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile expression pascaligo lambda --init-file "contracts/lambdas/call/teiaMarketplaceCancelSwap.ligo" > build/tz/lambdas/call/teia_marketplace_cancel_swap.tz 14 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile expression pascaligo lambda --init-file "contracts/lambdas/call/teiaMarketplaceSwap.ligo" --michelson-format json > build/tz/lambdas/call/teiaMarketplaceSwap.json 15 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile expression pascaligo lambda --init-file "contracts/lambdas/call/teiaMarketplaceCancelSwap.ligo" --michelson-format json > build/tz/lambdas/call/teiaMarketplaceCancelSwap.json 16 | pytest -v 17 | -------------------------------------------------------------------------------- /contracts/lambdas/call/hicMintOBJKT.ligo: -------------------------------------------------------------------------------- 1 | #include "../../main/HicProxy.ligo" 2 | 3 | 4 | function lambda(const store : storage; const packedParams : bytes) : list(operation) is 5 | 6 | block { 7 | 8 | const paramsOption: option(mintParams) = Bytes.unpack(packedParams); 9 | const params : mintParams = case paramsOption of [ 10 | | None -> (failwith("Unpack failed") : mintParams) 11 | | Some(p) -> p 12 | ]; 13 | 14 | const callToHic = callMintOBJKT(store.minterAddress, params); 15 | 16 | } with list[callToHic] 17 | -------------------------------------------------------------------------------- /contracts/lambdas/call/teiaMarketplaceCancelSwap.ligo: -------------------------------------------------------------------------------- 1 | #include "../../main/HicProxy.ligo" 2 | 3 | type callParams is record [ 4 | marketplaceAddress : address; 5 | swap_id : nat; 6 | ] 7 | 8 | 9 | function lambda(const _store : storage; const packedParams : bytes) : list(operation) is 10 | 11 | block { 12 | 13 | const unpacked = case (Bytes.unpack(packedParams) : option(callParams)) of [ 14 | | None -> (failwith("Unpack failed") : callParams) 15 | | Some(p) -> p 16 | ]; 17 | 18 | const marketplace : contract(nat) = 19 | case (Tezos.get_entrypoint_opt("%cancel_swap", unpacked.marketplaceAddress) 20 | : option(contract(nat))) of [ 21 | | None -> (failwith("Marketplace V3 is not found") : contract(nat)) 22 | | Some(con) -> con 23 | ]; 24 | 25 | const callToV3 : operation = Tezos.transaction(unpacked.swap_id, 0tez, marketplace); 26 | 27 | } with list[callToV3] 28 | 29 | 30 | -------------------------------------------------------------------------------- /contracts/lambdas/call/teiaMarketplaceSwap.ligo: -------------------------------------------------------------------------------- 1 | #include "../../main/HicProxy.ligo" 2 | 3 | type marketplaceV3Params is michelson_pair( 4 | address, "fa2", 5 | michelson_pair( 6 | nat, "objkt_id", 7 | michelson_pair( 8 | nat, "objkt_amount", 9 | michelson_pair( 10 | tez, "xtz_per_objkt", 11 | michelson_pair( 12 | nat, "royalties", 13 | address, "creator" 14 | ), "" 15 | ), "" 16 | ), "" 17 | ), "" 18 | ) 19 | 20 | type callParams is record [ 21 | marketplaceAddress : address; 22 | params : marketplaceV3Params; 23 | ] 24 | 25 | 26 | function lambda(const _store : storage; const packedParams : bytes) : list(operation) is 27 | 28 | block { 29 | 30 | const unpacked = case (Bytes.unpack(packedParams) : option(callParams)) of [ 31 | | None -> (failwith("Unpack failed") : callParams) 32 | | Some(p) -> p 33 | ]; 34 | 35 | const marketplace : contract(marketplaceV3Params) = 36 | case (Tezos.get_entrypoint_opt("%swap", unpacked.marketplaceAddress) 37 | : option(contract(marketplaceV3Params))) of [ 38 | | None -> (failwith("Marketplace V3 is not found") : contract(marketplaceV3Params)) 39 | | Some(con) -> con 40 | ]; 41 | 42 | const callToV3 : operation = Tezos.transaction(unpacked.params, 0tez, marketplace); 43 | 44 | } with list[callToV3] 45 | 46 | -------------------------------------------------------------------------------- /contracts/lambdas/originate/basicProxy.ligo: -------------------------------------------------------------------------------- 1 | #include "../../partials/factory.ligo" 2 | #include "../../main/BasicProxy.ligo" 3 | 4 | 5 | (* I was unable to make contract using Tezos.create_contract. 6 | I don't undertand what happened in next few lines. 7 | I just copypasted and adapted next code from QuipuSwap factory: 8 | *) 9 | type createProxyFuncType is (option(key_hash) * tez * storage) -> (operation * address) 10 | 11 | const createProxyFunc : createProxyFuncType = 12 | [%Michelson ( {| { UNPPAIIR ; 13 | CREATE_CONTRACT 14 | #include "../../../build/tz/basic_proxy.tz" 15 | ; 16 | PAIR } |} 17 | : createProxyFuncType)]; 18 | 19 | 20 | function lambda( 21 | const _records : recordsType; 22 | const packedParams : bytes) : originationResult is 23 | 24 | block { 25 | 26 | const shares : shares = case (Bytes.unpack(packedParams) : option(shares)) of [ 27 | | None -> (failwith("Unpack failed") : shares) 28 | | Some(p) -> p 29 | ]; 30 | 31 | (* Calculating total shares and core participants: *) 32 | var totalShares : nat := 0n; 33 | 34 | for _participantAddress -> share in map shares block { 35 | totalShares := totalShares + share; 36 | }; 37 | 38 | if totalShares = 0n then failwith("Sum of the shares should be more than 0n") 39 | else skip; 40 | 41 | (* TODO: check how much participants it can handle and limit this count here *) 42 | (* Preparing initial storage: *) 43 | 44 | const initialStore : storage = record [ 45 | factory = Tezos.self_address; 46 | administrator = Tezos.sender; 47 | totalShares = totalShares; 48 | shares = shares; 49 | ]; 50 | 51 | (* Making originate operation: *) 52 | const origination : operation * address = createProxyFunc ( 53 | (None : option(key_hash)), 54 | 0tz, 55 | initialStore); 56 | 57 | const result : originationResult = record [ 58 | operation = origination.0; 59 | address = origination.1; 60 | metadata = Bytes.pack("basic_proxy") 61 | ]; 62 | 63 | } with result 64 | -------------------------------------------------------------------------------- /contracts/lambdas/originate/hicProxy.ligo: -------------------------------------------------------------------------------- 1 | #include "../../partials/factory.ligo" 2 | #include "../../main/HicProxy.ligo" 3 | #include "../../partials/proxyTypes.ligo" 4 | 5 | 6 | (* I was unable to make contract using Tezos.create_contract. 7 | I don't undertand what happened in next few lines. 8 | I just copypasted and adapted next code from QuipuSwap factory: 9 | *) 10 | type createProxyFuncType is (option(key_hash) * tez * storage) -> (operation * address) 11 | 12 | const createProxyFunc : createProxyFuncType = 13 | [%Michelson ( {| { UNPPAIIR ; 14 | CREATE_CONTRACT 15 | #include "../../../build/tz/hic_proxy.tz" 16 | ; 17 | PAIR } |} 18 | : createProxyFuncType)]; 19 | 20 | 21 | function lambda( 22 | const records : recordsType; 23 | const packedParams : bytes) : originationResult is 24 | 25 | block { 26 | 27 | const participantsOption: option(participantsMap) = Bytes.unpack(packedParams); 28 | const participants : participantsMap = case participantsOption of [ 29 | | None -> (failwith("UNPK_FAIL") : participantsMap) 30 | | Some(p) -> p 31 | ]; 32 | 33 | (* Calculating total shares and core participants: *) 34 | var shares : map(address, nat) := map []; 35 | var undistributed : map(address, nat) := map []; 36 | var coreParticipants : set (address) := set []; 37 | var totalShares : nat := 0n; 38 | 39 | for participantAddress -> participantRec in map participants block { 40 | shares[participantAddress] := participantRec.share; 41 | undistributed[participantAddress] := 0n; 42 | totalShares := totalShares + participantRec.share; 43 | 44 | if participantRec.isCore 45 | then coreParticipants := Set.add (participantAddress, coreParticipants) 46 | else skip; 47 | }; 48 | 49 | if totalShares = 0n then failwith("Sum of the shares should be more than 0n") 50 | else skip; 51 | 52 | if totalShares > 1_000_000_000_000n then 53 | failwith("EXCEED_MAX_SHARES") 54 | else skip; 55 | 56 | if Map.size(participants) > 108n then failwith("EXCEED_MAX_PARTICIPANTS") 57 | else skip; 58 | 59 | (* Preparing initial storage: *) 60 | const initialStore : storage = record [ 61 | administrator = Tezos.sender; 62 | shares = shares; 63 | totalShares = totalShares; 64 | minterAddress = unpackAddressRecord("hicMinterAddress", records); 65 | marketplaceAddress = unpackAddressRecord("hicMarketplaceAddress", records); 66 | tokenAddress = unpackAddressRecord("hicTokenAddress", records); 67 | registryAddress = unpackAddressRecord("hicRegistryAddress", records); 68 | coreParticipants = coreParticipants; 69 | isPaused = False; 70 | totalReceived = 0n; 71 | threshold = 0n; 72 | undistributed = undistributed; 73 | residuals = 0n; 74 | ]; 75 | 76 | (* Making originate operation: *) 77 | const origination : operation * address = createProxyFunc ( 78 | (None : option(key_hash)), 79 | 0tz, 80 | initialStore); 81 | 82 | const result : originationResult = record [ 83 | operation = origination.0; 84 | address = origination.1; 85 | metadata = Bytes.pack("hic_proxy"); 86 | ]; 87 | 88 | } with result 89 | -------------------------------------------------------------------------------- /contracts/main/BasicProxy.ligo: -------------------------------------------------------------------------------- 1 | type shares is map(address, nat); 2 | 3 | 4 | type storage is record [ 5 | factory : address; 6 | administrator : address; 7 | totalShares : nat; 8 | shares : shares; 9 | ] 10 | 11 | type executableCall is storage -> list(operation) 12 | 13 | 14 | type action is 15 | | Execute of executableCall 16 | | Default of unit 17 | 18 | 19 | function default(const store : storage) : (list(operation) * storage) is 20 | block { 21 | var operations : list(operation) := nil; 22 | (* Just simple operation to the factory with nat contract ID or without *) 23 | } with (operations, store) 24 | 25 | 26 | function checkSenderIsAdmin(var store : storage) : unit is 27 | if (Tezos.sender = store.administrator) then unit 28 | else failwith("Entrypoint can call only administrator"); 29 | 30 | 31 | function execute(const call : executableCall; const store : storage) 32 | : (list(operation) * storage) is 33 | 34 | block { 35 | checkSenderIsAdmin(store); 36 | const operations : list(operation) = call(store); 37 | } with (operations, store) 38 | 39 | 40 | function main (const params : action; const store : storage) : (list(operation) * storage) is 41 | case params of [ 42 | | Execute(call) -> execute(call, store) 43 | | Default -> default(store) 44 | ] 45 | -------------------------------------------------------------------------------- /contracts/main/ContractProxyBigMap.ligo: -------------------------------------------------------------------------------- 1 | (* Including hic et nunc interface: *) 2 | #include "../partials/hicetnunc.ligo" 3 | 4 | 5 | (* Including common functions: *) 6 | #include "../partials/general.ligo" 7 | 8 | 9 | type account is record [ 10 | (* share value would be nat that in sum should be equal to 1000 *) 11 | share : nat; 12 | 13 | (* withdrawalsSum is the sum that already withdrawn from contract by this participant *) 14 | withdrawalsSum : nat; 15 | ] 16 | 17 | 18 | type action is 19 | | Mint_OBJKT of mintParams 20 | | Swap of swapParams 21 | | Withdraw of unit 22 | | Default of unit 23 | 24 | 25 | type storage is record [ 26 | (* administrator is originator of the contract, this is the only one who can call mint *) 27 | administrator : address; 28 | 29 | (* account is map of all participants with their shares and the sum, that rhey already withdrawn *) 30 | accounts : big_map(address, account); 31 | 32 | (* totalWithdrawalsSum is the total amount of all withdrawals from all of the participants *) 33 | totalWithdrawalsSum : nat; 34 | 35 | (* address of the Hic Et Nunc Minter (mainnet: KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9) *) 36 | minterAddress : address; 37 | 38 | (* address of the Hic Et Nunc Marketplace (mainnet: KT1HbQepzV1nVGg8QVznG7z4RcHseD5kwqBn) *) 39 | marketplaceAddress : address; 40 | ] 41 | 42 | 43 | function getAccount(var participant : address; var s : storage) : account is 44 | case Big_map.find_opt(participant, s.accounts) of [ 45 | | Some(acc) -> acc 46 | | None -> record[ share = 0n; withdrawalsSum = 0n ] 47 | ]; 48 | 49 | 50 | function withdraw(var s : storage) : (list(operation) * storage) is 51 | block { 52 | 53 | const totalRevenue = tezToNat(Tezos.balance) + s.totalWithdrawalsSum; 54 | 55 | var participant : account := getAccount(Tezos.sender, s); 56 | 57 | (* NOTE: there are can be not equal division, what would happen? *) 58 | const totalParticipantEarnings = participant.share * totalRevenue / 1000n; 59 | (* TODO: assert that participant.withdrawalsSum <= totalParticipantEarnings *) 60 | const payoutAmount = abs(totalParticipantEarnings - participant.withdrawalsSum); 61 | 62 | if (payoutAmount = 0n) then failwith("Nothing to withdraw") else skip; 63 | 64 | const receiver : contract(unit) = getReceiver(Tezos.sender); 65 | const payout = natToTez(payoutAmount); 66 | const payoutOperation : operation = Tezos.transaction(unit, payout, receiver); 67 | 68 | s.totalWithdrawalsSum := s.totalWithdrawalsSum + payoutAmount; 69 | participant.withdrawalsSum := participant.withdrawalsSum + payoutAmount; 70 | s.accounts[Tezos.sender] := participant 71 | 72 | } with (list[payoutOperation], s) 73 | 74 | 75 | function checkSenderIsAdmin(var store : storage) : unit is 76 | if (Tezos.sender = store.administrator) then unit 77 | else failwith("Entrypoint can call only administrator"); 78 | 79 | 80 | function mint_OBJKT(var store : storage; var params : mintParams) : (list(operation) * storage) is 81 | block { 82 | checkSenderIsAdmin(store); 83 | const callToHic = callMintOBJKT(store.minterAddress, params); 84 | } with (list[callToHic], store) 85 | 86 | 87 | function swap(var store : storage; var params : swapParams) : (list(operation) * storage) is 88 | block { 89 | checkSenderIsAdmin(store); 90 | const callToHic = callSwap(store.marketplaceAddress, params); 91 | } with (list[callToHic], store) 92 | 93 | 94 | function main (var params : action; var store : storage) : (list(operation) * storage) is 95 | case params of [ 96 | | Mint_OBJKT(p) -> mint_OBJKT(store, p) 97 | | Swap(p) -> swap(store, p) 98 | | Withdraw -> withdraw(store) 99 | | Default -> ((nil: list(operation)), store) 100 | ] 101 | -------------------------------------------------------------------------------- /contracts/main/Factory.ligo: -------------------------------------------------------------------------------- 1 | #include "../partials/factory.ligo" 2 | #include "../partials/general.ligo" 3 | 4 | 5 | type factoryAction is 6 | | Create_proxy of originationParams 7 | | Add_template of addTemplateParams 8 | | Remove_template of string 9 | | Is_originated_contract of isOriginatedParams 10 | | Update_admin of address 11 | | Accept_ownership of unit 12 | | Add_record of addRecordParams 13 | | Remove_record of string 14 | 15 | 16 | function checkSenderIsAdmin(const factoryStore : factoryStorage) : unit is 17 | if (Tezos.sender = factoryStore.administrator) then unit 18 | else failwith("NOT_ADM"); 19 | 20 | 21 | function createProxy(const params : originationParams; var factoryStore : factoryStorage) 22 | : (list(operation) * factoryStorage) is 23 | block { 24 | 25 | checkNoAmount(Unit); 26 | const optionalOriginator = Map.find_opt(params.templateName, factoryStore.templates); 27 | const proxyOriginator : originateContractFunc = case optionalOriginator of [ 28 | | Some(originator) -> originator 29 | | None -> (failwith("TEMPLATE_NF") : originateContractFunc) 30 | ]; 31 | 32 | const result : originationResult = proxyOriginator( 33 | factoryStore.records, params.params); 34 | 35 | factoryStore.originatedContracts[result.address] := result.metadata; 36 | 37 | } with (list[result.operation], factoryStore) 38 | 39 | 40 | function addTemplate( 41 | const params : addTemplateParams; 42 | var factoryStore : factoryStorage) : (list(operation) * factoryStorage) is 43 | 44 | block { 45 | checkNoAmount(Unit); 46 | checkSenderIsAdmin(factoryStore); 47 | (* Rewriting contract with the same name is allowed: *) 48 | factoryStore.templates[params.name] := params.originateFunc; 49 | } with ((nil : list(operation)), factoryStore) 50 | 51 | 52 | function removeTemplate( 53 | const templateName : string; 54 | var factoryStore : factoryStorage) : (list(operation) * factoryStorage) is 55 | 56 | block { 57 | checkNoAmount(Unit); 58 | checkSenderIsAdmin(factoryStore); 59 | factoryStore.templates := Big_map.remove(templateName, factoryStore.templates); 60 | } with ((nil : list(operation)), factoryStore) 61 | 62 | 63 | 64 | function isOriginatedContract( 65 | const params : isOriginatedParams; 66 | var factoryStore : factoryStorage) is 67 | 68 | block { 69 | checkNoAmount(Unit); 70 | const isOriginatedOption = Big_map.find_opt(params.contractAddress, factoryStore.originatedContracts); 71 | const isOriginated : bool = case isOriginatedOption of [ 72 | | Some(_metadata) -> True 73 | | None -> False 74 | ]; 75 | 76 | const returnOperation = Tezos.transaction(isOriginated, 0mutez, params.callback); 77 | } with (list[returnOperation], factoryStore) 78 | 79 | 80 | function updateAdmin(var factoryStore : factoryStorage; const newAdmin : address) : (list(operation) * factoryStorage) is 81 | block { 82 | checkNoAmount(Unit); 83 | checkSenderIsAdmin(factoryStore); 84 | factoryStore.proposedAdministrator := Some(newAdmin); 85 | } with ((nil: list(operation)), factoryStore) 86 | 87 | 88 | function acceptOwnership(var factoryStore : factoryStorage) : (list(operation) * factoryStorage) is 89 | block { 90 | checkNoAmount(Unit); 91 | 92 | const proposedAdministrator : address = case factoryStore.proposedAdministrator of [ 93 | | Some(proposed) -> proposed 94 | | None -> (failwith("NOT_PROPOSED") : address) 95 | ]; 96 | 97 | if Tezos.sender = proposedAdministrator then 98 | block { 99 | factoryStore.administrator := proposedAdministrator; 100 | factoryStore.proposedAdministrator := (None : option(address)); 101 | } else failwith("NOT_PROPOSED") 102 | 103 | } with ((nil: list(operation)), factoryStore) 104 | 105 | 106 | function addRecord( 107 | const params : addRecordParams; 108 | var factoryStore : factoryStorage) : (list(operation) * factoryStorage) is 109 | 110 | block { 111 | checkNoAmount(Unit); 112 | checkSenderIsAdmin(factoryStore); 113 | (* Rewriting record with the same name is allowed: *) 114 | factoryStore.records[params.name] := params.value; 115 | } with ((nil : list(operation)), factoryStore) 116 | 117 | 118 | function removeRecord( 119 | const name : string; 120 | var factoryStore : factoryStorage) : (list(operation) * factoryStorage) is 121 | 122 | block { 123 | checkNoAmount(Unit); 124 | checkSenderIsAdmin(factoryStore); 125 | factoryStore.records := Big_map.remove(name, factoryStore.records); 126 | } with ((nil : list(operation)), factoryStore) 127 | 128 | 129 | function main (const params : factoryAction; var factoryStore : factoryStorage) 130 | : (list(operation) * factoryStorage) is 131 | case params of [ 132 | | Create_proxy(p) -> createProxy(p, factoryStore) 133 | | Add_template(p) -> addTemplate(p, factoryStore) 134 | | Remove_template(p) -> removeTemplate(p, factoryStore) 135 | | Is_originated_contract(p) -> isOriginatedContract(p, factoryStore) 136 | | Update_admin(p) -> updateAdmin(factoryStore, p) 137 | | Accept_ownership -> acceptOwnership(factoryStore) 138 | | Add_record(p) -> addRecord(p, factoryStore) 139 | | Remove_record(p) -> removeRecord(p, factoryStore) 140 | ] 141 | -------------------------------------------------------------------------------- /contracts/main/HicProxy.ligo: -------------------------------------------------------------------------------- 1 | (* Including hic et nunc interface: *) 2 | #include "../partials/hicetnunc.ligo" 3 | 4 | 5 | (* Including common functions: *) 6 | #include "../partials/general.ligo" 7 | 8 | 9 | (* Including sign interface *) 10 | #include "../partials/sign.ligo" 11 | 12 | 13 | (* FA2 interface *) 14 | #include "../partials/fa2.ligo" 15 | 16 | (* 17 | - administrator is originator of the contract, this is the only one who can call mint 18 | - shares is map of all participants with the shares that they would recieve 19 | - totalShares is the sum of the shares should be equal to totalShares 20 | - tokenAddress is hicetnunc fa2 token address 21 | - miterAddress is address of the Hic Et Nunc Minter 22 | - marketplaceAddress address of the Hic Et Nunc V2 marketplace 23 | - registryAddress is address of the Hic Et Nunc registry 24 | - coreParticipants set of participants that should sign and signs itself 25 | - isPaused is flag that can be triggered by admin that turns off mint/swap entries 26 | - totalReceived - is amount of mutez that was received by a collab 27 | - threshold - is minimal amount that can be payed to the participant during default split 28 | - undistributed - is mapping with all undistributed values 29 | - residuals - is amount of mutez that wasn't distributed and kept until next income 30 | *) 31 | 32 | type storage is record [ 33 | administrator : address; 34 | shares : map(address, nat); 35 | totalShares : nat; 36 | tokenAddress : address; 37 | minterAddress : address; 38 | marketplaceAddress : address; 39 | registryAddress : address; 40 | coreParticipants : set(address); 41 | isPaused : bool; 42 | totalReceived : nat; 43 | threshold : nat; 44 | undistributed : map(address, nat); 45 | residuals : nat; 46 | ] 47 | 48 | type executableCall is storage*bytes -> list(operation) 49 | 50 | 51 | type executeParams is record [ 52 | lambda : executableCall; 53 | packedParams : bytes; 54 | ] 55 | 56 | 57 | type action is 58 | | Execute of executeParams 59 | | Default of unit 60 | | Mint_OBJKT of mintParams 61 | | Swap of swapParams 62 | | Cancel_swap of cancelSwapParams 63 | | Collect of collectParams 64 | | Registry of registryParams 65 | | Unregistry of unit 66 | | Update_operators of updateOperatorsParam 67 | | Update_admin of address 68 | | Transfer of transferParams 69 | | Set_threshold of nat 70 | | Withdraw of address 71 | 72 | 73 | function checkSenderIsAdmin(var store : storage) : unit is 74 | if (Tezos.sender = store.administrator) then unit 75 | else failwith("NOT_ADMIN"); 76 | 77 | 78 | function execute(const params : executeParams; const store : storage) 79 | : (list(operation) * storage) is 80 | 81 | block { 82 | (* This is the only entrypoint (besides default) that allows tez in *) 83 | checkSenderIsAdmin(store); 84 | const operations : list(operation) = 85 | params.lambda(store, params.packedParams); 86 | } with (operations, store) 87 | 88 | 89 | function mint_OBJKT(var store : storage; const params: mintParams) : (list(operation) * storage) is 90 | block { 91 | checkNoAmount(Unit); 92 | checkSenderIsAdmin(store); 93 | const callToHic = callMintOBJKT(store.minterAddress, params); 94 | } with (list[callToHic], store) 95 | 96 | 97 | function swap(var store : storage; var params : swapParams) : (list(operation) * storage) is 98 | block { 99 | checkNoAmount(Unit); 100 | checkSenderIsAdmin(store); 101 | const callToHic = callSwap(store.marketplaceAddress, params); 102 | } with (list[callToHic], store) 103 | 104 | 105 | function cancelSwap(var store : storage; var params : cancelSwapParams) : (list(operation) * storage) is 106 | block { 107 | checkNoAmount(Unit); 108 | checkSenderIsAdmin(store); 109 | const callToHic = callCancelSwap(store.marketplaceAddress, params); 110 | } with (list[callToHic], store) 111 | 112 | 113 | function collect(var store : storage; var params : collectParams) : (list(operation) * storage) is 114 | block { 115 | checkNoAmount(Unit); 116 | checkSenderIsAdmin(store); 117 | const callToHic = callCollect(store.marketplaceAddress, params); 118 | } with (list[callToHic], store) 119 | 120 | 121 | [@inline] function getUndistributed(const participant : address; const store : storage) is 122 | case Map.find_opt(participant, store.undistributed) of [ 123 | | Some(value) -> value 124 | | None -> 0n 125 | ] 126 | 127 | 128 | function makePayment(const payoutAmount : nat; const participant : address) is 129 | Tezos.transaction(unit, natToTez(payoutAmount), getReceiver(participant)) 130 | 131 | 132 | function default(var store : storage) : (list(operation) * storage) is 133 | block { 134 | var operations : list(operation) := nil; 135 | var allocatedPayouts := 0n; 136 | const natAmount = tezToNat(Tezos.amount); 137 | const distAmount = natAmount + store.residuals; 138 | store.totalReceived := store.totalReceived + natAmount; 139 | 140 | for participant -> share in map store.shares block { 141 | var payoutAmount := distAmount * share / store.totalShares; 142 | allocatedPayouts := allocatedPayouts + payoutAmount; 143 | 144 | payoutAmount := payoutAmount + getUndistributed(participant, store); 145 | 146 | store.undistributed[participant] := if payoutAmount >= store.threshold 147 | then 0n 148 | else payoutAmount; 149 | 150 | payoutAmount := if payoutAmount >= store.threshold 151 | then payoutAmount 152 | else 0n; 153 | 154 | operations := if payoutAmount > 0n 155 | then makePayment(payoutAmount, participant) # operations 156 | else operations; 157 | }; 158 | 159 | const residuals = distAmount - allocatedPayouts; 160 | if residuals >= 0 161 | then store.residuals := abs(residuals) 162 | else failwith("WR_SHARES") 163 | 164 | } with (operations, store) 165 | 166 | 167 | function updateAdmin(var store : storage; var newAdmin : address) : (list(operation) * storage) is 168 | block { 169 | checkNoAmount(Unit); 170 | checkSenderIsAdmin(store); 171 | store.administrator := newAdmin; 172 | } with ((nil: list(operation)), store) 173 | 174 | 175 | function registry(var store : storage; var params : registryParams) : (list(operation) * storage) is 176 | block { 177 | checkNoAmount(Unit); 178 | checkSenderIsAdmin(store); 179 | const callToHic = callRegistry(store.registryAddress, params); 180 | } with (list[callToHic], store) 181 | 182 | 183 | function unregistry(var store : storage) : (list(operation) * storage) is 184 | block { 185 | checkNoAmount(Unit); 186 | checkSenderIsAdmin(store); 187 | const callToHic = callUnregistry(store.registryAddress); 188 | } with (list[callToHic], store) 189 | 190 | 191 | function updateOperators(var store : storage; var params : updateOperatorsParam) : (list(operation) * storage) is 192 | block { 193 | checkNoAmount(Unit); 194 | checkSenderIsAdmin(store); 195 | const callToHic = callUpdateOperators(store.tokenAddress, params); 196 | } with (list[callToHic], store) 197 | 198 | 199 | function transfer(var store : storage; var params : transferParams) : (list(operation) * storage) is 200 | block { 201 | checkNoAmount(Unit); 202 | checkSenderIsAdmin(store); 203 | const callToHic = callTransfer(store.tokenAddress, params); 204 | } with (list[callToHic], store) 205 | 206 | 207 | function set_threshold(const store : storage; const newThreshold : nat) is 208 | block { 209 | checkNoAmount(Unit); 210 | checkSenderIsAdmin(store); 211 | } with ((nil: list(operation)), store with record [threshold = newThreshold]) 212 | 213 | 214 | function withdraw(var store : storage; var recipient : address) is 215 | block { 216 | (* anyone can trigger withdraw for anyone in share mapping *) 217 | checkNoAmount(Unit); 218 | const receiver = getReceiver(recipient); 219 | const payout = natToTez(getUndistributed(recipient, store)); 220 | store.undistributed[recipient] := if Map.mem(recipient, store.shares) 221 | then 0n 222 | else (failwith("WR_ADDR") : nat); 223 | const op = Tezos.transaction(unit, payout, receiver); 224 | } with (list[op], store) 225 | 226 | 227 | function main (const params : action; const store : storage) : (list(operation) * storage) is 228 | case params of [ 229 | | Execute(call) -> execute(call, store) 230 | | Mint_OBJKT(p) -> mint_OBJKT(store, p) 231 | | Swap(p) -> swap(store, p) 232 | | Cancel_swap(p) -> cancelSwap(store, p) 233 | | Collect(p) -> collect(store, p) 234 | | Default -> default(store) 235 | | Update_admin(p) -> updateAdmin(store, p) 236 | | Registry(p) -> registry(store, p) 237 | | Unregistry -> unregistry(store) 238 | | Update_operators(p) -> updateOperators(store, p) 239 | | Transfer(p) -> transfer(store, p) 240 | | Set_threshold(p) -> set_threshold(store, p) 241 | | Withdraw(p) -> withdraw(store, p) 242 | ] 243 | 244 | [@view] function get_shares (const _ : unit ; const s : storage) is s.shares 245 | [@view] function get_core_participants (const _ : unit; const s : storage) is s.coreParticipants 246 | [@view] function get_administrator (const _ : unit; const s : storage) is s.administrator 247 | [@view] function get_total_received (const _ : unit; const s : storage) is s.totalReceived 248 | [@view] function get_total_shares (const _ : unit; const s : storage) is s.totalShares 249 | 250 | -------------------------------------------------------------------------------- /contracts/main/Migrations.ligo: -------------------------------------------------------------------------------- 1 | type migrations is record 2 | owner : address; 3 | last_completed_migration : int; 4 | end 5 | 6 | function main (const completed_migration: int ; var migrations : migrations) : (list(operation) * migrations) is 7 | block { 8 | if sender =/= migrations.owner 9 | then 10 | skip 11 | else 12 | migrations.last_completed_migration := completed_migration; 13 | } with ((nil : list(operation)), migrations); 14 | -------------------------------------------------------------------------------- /contracts/main/MockView.ligo: -------------------------------------------------------------------------------- 1 | type storage is record [ 2 | natValue : option(nat); 3 | boolValue : option(bool); 4 | ] 5 | 6 | type action is 7 | | Nat_view of nat 8 | | Bool_view of bool 9 | 10 | function dispatchAction(const param : action) : storage is 11 | case param of [ 12 | | Nat_view(p) -> record [ 13 | natValue = Some(p); 14 | boolValue = (None : option(bool)) 15 | ] 16 | 17 | | Bool_view(p) -> record [ 18 | natValue = (None : option(nat)); 19 | boolValue = Some(p) 20 | ] 21 | ] 22 | 23 | function main(const param : action; const _store : storage) 24 | : (list(operation) * storage) is 25 | ((nil: list(operation)), dispatchAction(param)) 26 | -------------------------------------------------------------------------------- /contracts/main/Packer.ligo: -------------------------------------------------------------------------------- 1 | #include "../partials/hicetnunc.ligo" 2 | #include "../partials/proxyTypes.ligo" 3 | 4 | // TODO: include basicProxy is failed it use Default entry with HicProxy 5 | // so it is required to refactor structure and move types somewhere 6 | // #include "../lambdas/originate/basicProxy.ligo" 7 | type shares is map(address, nat); 8 | 9 | type action is 10 | | Pack_nat of nat 11 | | Pack_address of address 12 | | Pack_mint_OBJKT of mintParams 13 | | Pack_originate_hic_proxy of participantsMap 14 | | Pack_originate_basic_proxy of shares 15 | 16 | function pack_nat(var params : nat) : bytes is Bytes.pack(params) 17 | function pack_address(var params : address) : bytes is Bytes.pack(params) 18 | function mint_OBJKT(var params : mintParams) : bytes is Bytes.pack(params) 19 | function originate_hic_proxy(var params : participantsMap) : bytes is Bytes.pack(params) 20 | function originate_basic_proxy(var params : shares) : bytes is Bytes.pack(params) 21 | 22 | function main (var params : action; var _store : bytes) : (list(operation) * bytes) is 23 | case params of [ 24 | | Pack_nat(p) -> ((nil: list(operation)), pack_nat(p)) 25 | | Pack_address(p) -> ((nil: list(operation)), pack_address(p)) 26 | | Pack_mint_OBJKT(p) -> ((nil: list(operation)), mint_OBJKT(p)) 27 | | Pack_originate_hic_proxy(p) -> ((nil: list(operation)), originate_hic_proxy(p)) 28 | | Pack_originate_basic_proxy(p) -> ((nil: list(operation)), originate_basic_proxy(p)) 29 | ] 30 | -------------------------------------------------------------------------------- /contracts/main/Sign.ligo: -------------------------------------------------------------------------------- 1 | #include "../partials/general.ligo" 2 | 3 | 4 | type isSignedResponse is bool 5 | 6 | type isSignedParams is record [ 7 | participant: address; 8 | id: nat; 9 | callback: contract(isSignedResponse) 10 | ] 11 | 12 | 13 | type action is 14 | | Sign of nat 15 | | Unsign of nat 16 | | Is_signed of isSignedParams 17 | (* TODO: maybe add method for oracle that can confirm that this OBJKT 18 | is really made by this participant? *) 19 | 20 | type signKey is (address*nat) 21 | 22 | type storage is record [ 23 | signatures: big_map(signKey, unit); 24 | ] 25 | 26 | 27 | function sign(var store : storage; const signId : nat) : (list(operation) * storage) is 28 | block { 29 | checkNoAmount(Unit); 30 | const key : signKey = (Tezos.sender, signId); 31 | store.signatures[key] := Unit; 32 | } with ((nil: list(operation)), store) 33 | 34 | 35 | function unsign(var store : storage; const signId : nat) : (list(operation) * storage) is 36 | block { 37 | checkNoAmount(Unit); 38 | const key : signKey = (Tezos.sender, signId); 39 | store.signatures := Big_map.remove(key, store.signatures); 40 | } with ((nil: list(operation)), store) 41 | 42 | 43 | function isSigned(var store : storage; var params : isSignedParams) : (list(operation) * storage) is 44 | block { 45 | checkNoAmount(Unit); 46 | const key : signKey = (params.participant, params.id); 47 | const isSigned : bool = case Big_map.find_opt(key, store.signatures) of [ 48 | | Some(_u) -> True 49 | | None -> False 50 | ]; 51 | const returnOperation = Tezos.transaction(isSigned, 0mutez, params.callback); 52 | } with (list[returnOperation], store) 53 | 54 | 55 | function main (var params : action; var store : storage) : (list(operation) * storage) is 56 | case params of [ 57 | | Sign(p) -> sign(store, p) 58 | | Unsign(p) -> unsign(store, p) 59 | | Is_signed(p) -> isSigned(store, p) 60 | ] 61 | -------------------------------------------------------------------------------- /contracts/main/SwapAdmin.ligo: -------------------------------------------------------------------------------- 1 | (* Simple contract that can be used as a Gallery collab contract admin that 2 | allows anyone to swap thier item for a given price using Gallery collab. 3 | This contract is almost independent from Factory and Collabs but I decided 4 | that it would be easier to develop and experiment with it here. 5 | *) 6 | 7 | 8 | (* Including hic et nunc interface: *) 9 | #include "../partials/hicetnunc.ligo" 10 | 11 | 12 | (* Including common functions: *) 13 | #include "../partials/general.ligo" 14 | 15 | 16 | (* FA2 interface *) 17 | #include "../partials/fa2.ligo" 18 | 19 | 20 | (* Management interface *) 21 | #include "../partials/management.ligo" 22 | 23 | 24 | type storage is record [ 25 | galleryAddress : address; 26 | tokenAddress : address; 27 | marketplaceAddress : address; 28 | administrator : address; 29 | // TODO: swap owners to make possible to cancel swaps? 30 | // TODO: isPaused : bool; 31 | ] 32 | 33 | type action is 34 | | Swap of swapParams 35 | | Accept_gallery_ownership of unit 36 | | Return_admin of unit 37 | // TODO: | Trigger_pause of unit 38 | // TODO: | Cancel_swap of cancelSwapParams 39 | 40 | (* 41 | function checkIsNotPaused(var store : storage) : unit is 42 | if store.isPaused then failwith("Contract is paused") 43 | else unit; 44 | *) 45 | 46 | function swap(const store : storage; var params : swapParams) : (list(operation) * storage) is 47 | block { 48 | checkNoAmount(Unit); 49 | // checkIsNotPaused(store); 50 | 51 | (* Transfering token from sender to the Gallery: *) 52 | const transferTokenParams = list[record [ 53 | from_ = Tezos.sender; 54 | txs = list[ record [ 55 | to_ = store.galleryAddress; 56 | // 1.0 is token_id: 57 | token_id = params.1.0; 58 | // 0.1 is amount: 59 | amount = params.0.1; 60 | ]]; 61 | ]]; 62 | const callTransferToken = callTransfer(store.tokenAddress, transferTokenParams); 63 | 64 | (* Allowing h=n Marketplace to spend token from the Gallery address 65 | - this call would be executed from Gallery using update_operators proxy 66 | entrypoint in the Gallery *) 67 | const operator = record [ 68 | owner = store.galleryAddress; 69 | operator = store.marketplaceAddress; 70 | // 1.0 is token_id: 71 | token_id = params.1.0; 72 | ]; 73 | const addOperators = list[Add_operator(operator)]; 74 | const removeOperators = list[Remove_operator(operator)]; 75 | 76 | (* Note that update operators calls goes to the gallery/proxy, not to the token itself: *) 77 | const allow = callUpdateOperators(store.galleryAddress, addOperators); 78 | const revoke = callUpdateOperators(store.galleryAddress, removeOperators); 79 | 80 | (* Calling swap *) 81 | const callToGallery = callSwap(store.galleryAddress, params); 82 | 83 | const operations = list[ 84 | callTransferToken; 85 | allow; 86 | callToGallery; 87 | revoke 88 | ] 89 | 90 | } with (operations, store) 91 | 92 | 93 | function acceptGalleryOwnership(const store : storage) : (list(operation) * storage) is 94 | block { 95 | checkNoAmount(Unit); 96 | const acceptCall = callAcceptOwnership(store.galleryAddress); 97 | } with (list[acceptCall], store) 98 | 99 | 100 | function checkSenderIsAdmin(var store : storage) : unit is 101 | if (Tezos.sender = store.administrator) then unit 102 | else failwith("Entrypoint can call only administrator"); 103 | 104 | 105 | (* Return gallery rights to the administrator *) 106 | function returnAdmin(const store : storage) : (list(operation) * storage) is 107 | block { 108 | checkNoAmount(Unit); 109 | checkSenderIsAdmin(store); 110 | const returnUpdateCall = callUpdateAdmin(store.galleryAddress, store.administrator); 111 | } with (list[returnUpdateCall], store) 112 | 113 | 114 | function main (const params : action; const store : storage) : (list(operation) * storage) is 115 | case params of [ 116 | | Swap(p) -> swap(store, p) 117 | | Accept_gallery_ownership -> acceptGalleryOwnership(store) 118 | | Return_admin -> returnAdmin(store) 119 | ] 120 | 121 | -------------------------------------------------------------------------------- /contracts/partials/errors.ligo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ztepler/hic-contract-proxy/16485e0f6445c8d23ba31b62edb91d6622126cad/contracts/partials/errors.ligo -------------------------------------------------------------------------------- /contracts/partials/fa2.ligo: -------------------------------------------------------------------------------- 1 | type operatorParam is 2 | [@layout:comb] 3 | record [ 4 | owner : address; 5 | operator : address; 6 | token_id : nat; 7 | ] 8 | 9 | type updateAction is 10 | | Add_operator of operatorParam 11 | | Remove_operator of operatorParam 12 | 13 | type updateOperatorParam is updateAction 14 | type updateOperatorsParam is list(updateOperatorParam) 15 | 16 | type transactionType is 17 | [@layout:comb] 18 | record [ 19 | to_ : address; 20 | token_id : nat; 21 | amount : nat; 22 | ] 23 | 24 | type singleTransferParams is 25 | [@layout:comb] 26 | record [ 27 | from_ : address; 28 | txs : list(transactionType) 29 | ] 30 | 31 | type transferParams is list(singleTransferParams) 32 | 33 | 34 | function callUpdateOperators( 35 | const tokenAddress : address; 36 | const params : updateOperatorsParam) : operation is 37 | 38 | block { 39 | const receiver : contract(updateOperatorsParam) = 40 | case (Tezos.get_entrypoint_opt("%update_operators", tokenAddress) 41 | : option(contract(updateOperatorsParam))) of [ 42 | | None -> (failwith("FA2_NF") : contract(updateOperatorsParam)) 43 | | Some(con) -> con 44 | ]; 45 | 46 | const callToFa2 : operation = Tezos.transaction(params, 0tez, receiver); 47 | 48 | } with callToFa2; 49 | 50 | 51 | function callTransfer( 52 | const tokenAddress : address; 53 | const params : transferParams) : operation is 54 | 55 | block { 56 | const receiver : contract(transferParams) = 57 | case (Tezos.get_entrypoint_opt("%transfer", tokenAddress) 58 | : option(contract(transferParams))) of [ 59 | | None -> (failwith("FA2_NF") : contract(transferParams)) 60 | | Some(con) -> con 61 | ]; 62 | 63 | const callToFa2 : operation = Tezos.transaction(params, 0tez, receiver); 64 | 65 | } with callToFa2; 66 | 67 | -------------------------------------------------------------------------------- /contracts/partials/factory.ligo: -------------------------------------------------------------------------------- 1 | type originationResult is record [ 2 | operation : operation; 3 | address : address; 4 | metadata : bytes; 5 | ] 6 | 7 | 8 | type recordsType is big_map(string, bytes) 9 | 10 | 11 | type originateContractFunc is (recordsType * bytes) -> originationResult 12 | 13 | 14 | type factoryStorage is record [ 15 | (* Records is packed factory params that can be accessed in originate 16 | lambdas, so it can be used to store contract-specific data: *) 17 | records : recordsType; 18 | 19 | (* Templates is map of lambdas each of one should originate contract *) 20 | templates : map(string, originateContractFunc); 21 | 22 | (* Ledger with all originated contracts and their params *) 23 | originatedContracts : big_map(address, bytes); 24 | 25 | administrator : address; 26 | proposedAdministrator : option(address); 27 | ] 28 | 29 | 30 | type originationParams is record [ 31 | templateName : string; 32 | params : bytes; 33 | ] 34 | 35 | 36 | type addTemplateParams is record [ 37 | name : string; 38 | originateFunc : originateContractFunc; 39 | ] 40 | 41 | 42 | type isOriginatedResponse is bool 43 | 44 | 45 | type isOriginatedParams is record [ 46 | contractAddress: address; 47 | callback: contract(isOriginatedResponse) 48 | ] 49 | 50 | 51 | type addRecordParams is record [ 52 | name : string; 53 | value : bytes; 54 | ] 55 | 56 | 57 | function unpackAddressRecord( 58 | const name : string; 59 | const records : recordsType 60 | ) : address is 61 | block { 62 | 63 | (* Getting record by its name: *) 64 | const packedRecord : bytes = case Big_map.find_opt(name, records) of [ 65 | | None -> (failwith("RECORD_NF") : bytes) 66 | | Some(rec) -> rec 67 | ]; 68 | 69 | (* Unpacking record to address type: *) 70 | const addressOption: option(address) = Bytes.unpack(packedRecord); 71 | const unpackedAddress : address = case addressOption of [ 72 | | None -> (failwith("UNPK_FAIL") : address) 73 | | Some(adr) -> adr 74 | ]; 75 | 76 | } with unpackedAddress 77 | -------------------------------------------------------------------------------- /contracts/partials/general.ligo: -------------------------------------------------------------------------------- 1 | function getReceiver(var a : address) : contract(unit) is 2 | case (Tezos.get_contract_opt(a): option(contract(unit))) of [ 3 | | Some (con) -> con 4 | | None -> (failwith ("ADDR_NF") : (contract(unit))) 5 | ]; 6 | 7 | 8 | function checkAllCoreSigned(const core : set(address); const signs : set(address)) : unit is 9 | block { 10 | var isAllSigned : bool := True; 11 | for participant in set core block { 12 | isAllSigned := signs contains participant and isAllSigned 13 | }; 14 | 15 | if isAllSigned then skip else failwith("NOT_SIGNED"); 16 | } with unit 17 | 18 | 19 | function tezToNat(const value : tez) : nat is value / 1mutez 20 | function natToTez(const value : nat) : tez is value * 1mutez 21 | 22 | 23 | function checkNoAmount(const _p : unit) : unit is 24 | if (Tezos.amount = 0tez) then unit 25 | else failwith("AMNT_FRBD"); 26 | 27 | -------------------------------------------------------------------------------- /contracts/partials/hicetnunc.ligo: -------------------------------------------------------------------------------- 1 | (* This is params that used in h=n mint call *) 2 | type mintParams is record [ 3 | address : address; 4 | amount : nat; 5 | metadata : bytes; 6 | royalties : nat 7 | ] 8 | 9 | 10 | (* This is params that used in h=n marketplace swap call *) 11 | type swapParams is michelson_pair( 12 | michelson_pair(address, "creator", nat, "objkt_amount"), "", 13 | michelson_pair( 14 | nat, "objkt_id", 15 | michelson_pair(nat, "royalties", tez, "xtz_per_objkt"), 16 | "" 17 | ), "") 18 | 19 | 20 | (* This is params that used in h=n cancel_swap call *) 21 | type cancelSwapParams is nat 22 | 23 | 24 | (* This is params that used in h=n marketplace collect call *) 25 | type collectParams is nat 26 | 27 | 28 | (* This is params that used in h=n curate call *) 29 | type curateParams is record [ 30 | hDAO_amount : nat; 31 | objkt_id : nat 32 | ] 33 | 34 | 35 | (* Params used in SUBJKT *) 36 | type registryParams is record [ 37 | metadata : bytes; 38 | subjkt : bytes; 39 | ] 40 | 41 | 42 | (* TODO: the next methods look very similar. I tried to make an abstract call to hicetnunc 43 | method to reduce code repetition, but it became very complicated (because of the 44 | different params types in these calls). Maybe there are the way to simplify all this calls. 45 | *) 46 | 47 | 48 | (* This function used to make mint_OBJKT entrypoint operation *) 49 | function callMintOBJKT(var minterAddress : address; var params : mintParams) : operation is 50 | block { 51 | const hicReceiver : contract(mintParams) = 52 | case (Tezos.get_entrypoint_opt("%mint_OBJKT", minterAddress) 53 | : option(contract(mintParams))) of [ 54 | | None -> (failwith("MINT_NF") : contract(mintParams)) 55 | | Some(con) -> con 56 | ]; 57 | 58 | const callToHic : operation = Tezos.transaction(params, 0tez, hicReceiver); 59 | } with callToHic; 60 | 61 | 62 | (* This function used to redirect swap call to hic et nunc swap entrypoint *) 63 | function callSwap(var marketplaceAddress : address; var params : swapParams) : operation is 64 | block { 65 | const hicReceiver : contract(swapParams) = 66 | case (Tezos.get_entrypoint_opt("%swap", marketplaceAddress) 67 | : option(contract(swapParams))) of [ 68 | | None -> (failwith("SWAP_NF") : contract(swapParams)) 69 | | Some(con) -> con 70 | ]; 71 | 72 | const callToHic : operation = Tezos.transaction(params, 0tez, hicReceiver); 73 | 74 | } with callToHic; 75 | 76 | 77 | (* This function used to redirect cancel swap call to hic et nunc cancel_swap entrypoint *) 78 | function callCancelSwap(var marketplaceAddress : address; var params : cancelSwapParams) : operation is 79 | block { 80 | const hicReceiver : contract(cancelSwapParams) = 81 | case (Tezos.get_entrypoint_opt("%cancel_swap", marketplaceAddress) 82 | : option(contract(cancelSwapParams))) of [ 83 | | None -> (failwith("SWAP_NF") : contract(cancelSwapParams)) 84 | | Some(con) -> con 85 | ]; 86 | 87 | const callToHic : operation = Tezos.transaction(params, 0tez, hicReceiver); 88 | 89 | } with callToHic; 90 | 91 | 92 | (* This function used to redirect collect call to hic et nunc collect entrypoint *) 93 | function callCollect(var marketplaceAddress : address; var params : collectParams) : operation is 94 | block { 95 | const hicReceiver : contract(collectParams) = 96 | case (Tezos.get_entrypoint_opt("%collect", marketplaceAddress) 97 | : option(contract(collectParams))) of [ 98 | | None -> (failwith("SWAP_NF") : contract(collectParams)) 99 | | Some(con) -> con 100 | ]; 101 | 102 | const callToHic : operation = Tezos.transaction(params, 0tez, hicReceiver); 103 | 104 | } with callToHic; 105 | 106 | 107 | (* This function used to redirect curate call to hic et nunc curate entrypoint *) 108 | function callCurate(var minterAddress : address; var params : curateParams) : operation is 109 | block { 110 | const hicReceiver : contract(curateParams) = 111 | case (Tezos.get_entrypoint_opt("%curate", minterAddress) 112 | : option(contract(curateParams))) of [ 113 | | None -> (failwith("MINT_NF") : contract(curateParams)) 114 | | Some(con) -> con 115 | ]; 116 | 117 | const callToHic : operation = Tezos.transaction(params, 0tez, hicReceiver); 118 | 119 | } with callToHic; 120 | 121 | 122 | (* This function used to redirect registry call to hic et nunc registry entrypoint *) 123 | function callRegistry(var registryAddress : address; var params : registryParams) : operation is 124 | block { 125 | const receiver : contract(registryParams) = 126 | case (Tezos.get_entrypoint_opt("%registry", registryAddress) 127 | : option(contract(registryParams))) of [ 128 | | None -> (failwith("REG_NF") : contract(registryParams)) 129 | | Some(con) -> con 130 | ]; 131 | 132 | const callToHic : operation = Tezos.transaction(params, 0tez, receiver); 133 | 134 | } with callToHic; 135 | 136 | 137 | (* This function used to redirect unregistry call to hic et nunc registry entrypoint *) 138 | function callUnregistry(var registryAddress : address) : operation is 139 | block { 140 | const receiver : contract(unit) = 141 | case (Tezos.get_entrypoint_opt("%unregistry", registryAddress) 142 | : option(contract(unit))) of [ 143 | | None -> (failwith("REG_NF") : contract(unit)) 144 | | Some(con) -> con 145 | ]; 146 | 147 | const callToHic : operation = Tezos.transaction(Unit, 0tez, receiver); 148 | 149 | } with callToHic; 150 | 151 | -------------------------------------------------------------------------------- /contracts/partials/management.ligo: -------------------------------------------------------------------------------- 1 | function callAcceptOwnership(const galleryAddress : address) : operation is 2 | block { 3 | const receiver : contract(unit) = 4 | case (Tezos.get_entrypoint_opt("%accept_ownership", galleryAddress) 5 | : option(contract(unit))) of [ 6 | | None -> (failwith("No gallery found") : contract(unit)) 7 | | Some(con) -> con 8 | ]; 9 | 10 | const acceptCall = Tezos.transaction(Unit, 0tez, receiver); 11 | } with acceptCall 12 | 13 | 14 | function callUpdateAdmin(const galleryAddress : address; const newAdminAddress : address) : operation is 15 | block { 16 | const receiver : contract(address) = 17 | case (Tezos.get_entrypoint_opt("%update_admin", galleryAddress) 18 | : option(contract(address))) of [ 19 | | None -> (failwith("No gallery found") : contract(address)) 20 | | Some(con) -> con 21 | ]; 22 | 23 | const acceptCall = Tezos.transaction(newAdminAddress, 0tez, receiver); 24 | } with acceptCall 25 | 26 | -------------------------------------------------------------------------------- /contracts/partials/proxyTypes.ligo: -------------------------------------------------------------------------------- 1 | type participantRec is record [ 2 | (* share is the fraction that participant would receive from every sale *) 3 | share : nat; 4 | 5 | (* role isCore allow participant to sign as one of the creator *) 6 | isCore : bool; 7 | ] 8 | 9 | type participantsMap is map(address, participantRec); 10 | 11 | -------------------------------------------------------------------------------- /contracts/partials/sign.ligo: -------------------------------------------------------------------------------- 1 | (* isParticipantCore & isParticipantAdministrator 2 | - request have address type 3 | - response have bool type 4 | - params are address*contract(bool) 5 | *) 6 | type isParticipantParams is record [ 7 | participantAddress: address; 8 | callback: contract(bool); 9 | ] 10 | 11 | (* getTotalShares have no request payload and returns nat 12 | so it just have this contract(nat) request params: *) 13 | type getTotalSharesParams is contract(nat); 14 | 15 | (* getParticipantShares request address and returns nat: *) 16 | type getParticipantShares is record [ 17 | participantAddress: address; 18 | callback: contract(nat); 19 | ] 20 | 21 | type isMintedHashParams is record [ 22 | metadata: bytes; 23 | callback: contract(bool); 24 | ] 25 | -------------------------------------------------------------------------------- /docs/factory_reference.md: -------------------------------------------------------------------------------- 1 | # Factory 2 | Factory allows admin to create set of templates that can be then used by anyone to originate any kind of contracts. Factory records any metadata after each contract creation. This can be used to keep tracking contract versions after template update. All this Factory mechanics is experiment that allow to create contracts that follow strictly defined properties (for example: admin can't change splits of the collab contract after it was created). Factory allows to have some `records` to be set to allow more variety in lambda execution (but this variety can be achieved by updating template lambdas). 3 | 4 | # Factory entrypoints: 5 | ## create\_proxy 6 | - Expects record with `templateName` which is `string` and `params` which is packed `bytes` 7 | - Expects packed parameters 8 | - Expects no attached amount 9 | - Originates collab contract using given template and parameters 10 | - Adds originated contract address to the `originatedContracts` ledger with `metadata` returned from template execution 11 | - Anyone can call this entrypoint 12 | 13 | This entrypoint allows anyone to originate new collab contract using one of the templates provided by factory administrator. To create collab user should provide `templateName`, currently in mainnet only one template is supported which is `hic_collab` used to interact with h=n and Teia contracts. Calling this entrypoint runs template origination lambda that expecting some packed `params` to be executed. This params may vary for different templates. This params should be packed before passing to `create_proxy` entrypoint. See [MichelsonType.pack](https://pytezos.org/types.html) method to pack params using PyTezos. See [MichelCodePacker.packData](https://tezostaquito.io/typedoc/classes/_taquito_taquito.michelcodecpacker.html) method to pack params using taquito. 14 | 15 | ## add\_template 16 | - Expects record with `name` which is `string` and `originateFunc` which is `(recordsType * bytes) -> originationResult`. And `originationResult` is record with origination `operation`, new contract `address` and `metadata` which is any `bytes` that will be written in `originatedContracts` ledger (it is easier to just read the code I suppose) 17 | - Updates given template in `templates` ledger if it was existed under this `name` or adds new template if it wasn't 18 | - Expects no attached amount 19 | - Only admin can call this entrypoint 20 | 21 | This entrypoint allows to add new templates to the Factory, as well as update existing templates. 22 | 23 | ## remove\_template 24 | - Expects `string` with `name` of template that should be removed 25 | - Removes template from `templates` ledger disallowing to create new contracts using this template 26 | - Expects no attached amount 27 | - Only admin can call this entrypoint 28 | 29 | ## add\_record 30 | - Expects record with `name` which is `string` with name of added/updated record and `value` which is `bytes` value of this record 31 | - Updates given record in `records` ledger if it was existed under this `name` or adds new record if it wasn't 32 | - Expects no attached amount 33 | - Only admin can call this entrypoint 34 | 35 | This entrypoint allows to add/update record that can be used in templates. 36 | 37 | ## remove\_record 38 | - Expects `string` with `name` of record that should be removed 39 | - Removes record from `records` ledger 40 | - Expects no attached amount 41 | - Only admin can call this entrypoint 42 | 43 | ## update\_admin 44 | - Expects `address` of proposed administrator 45 | - Expects no attached amount 46 | - Changes storage `proposedAdministrator` to proposed administrator 47 | - Only admin can call this entrypoint 48 | 49 | This entrypoint allows to transfer ownership. Proposed administrator should call `accept_ownership` to finish ownership transfer. 50 | 51 | ## accept\_ownership 52 | - Expects `unit` as parameter 53 | - Expects no attached amount 54 | - Changes storage `administator` to proposed administrator 55 | - Only proposed administrator can call this entrypoint 56 | 57 | This entrypoint allows to finish ownership transfer. 58 | 59 | ## is\_originated\_contract 60 | - Expects record with `contractAddress` which used to request and `callback` with type `contract(bool)` 61 | - Emits transaction with `bool` answer whether this contract address was created by this Factory or it is not 62 | - Expects no attached amount 63 | - Anyone can call this entrypoint 64 | 65 | This is callback view that can be used to request if given contract address was created by this factory. 66 | 67 | # Errors 68 | - `AMNT_FRBD`: error raised if entrypoint is not expecting to receive any amount but gets some 69 | - `NOT_ADM`: error raised if entrypoint is expected to be called by admin only and called by anyone else 70 | - `TEMPLATE_NF`: error raised if trying to `create_proxy` with template name that not existed 71 | - `NOT_PROPOSED`: error raised if someone tries to `accept_ownership` but his address was not proposed 72 | - `RECORD_NF`: error raised if template during execution tries to read record that not existed 73 | 74 | ### Hic origination lambda errors 75 | - `UNPK_FAIL`: error raised if provided `params` can not be unpacked to be used in origination template 76 | - `EXCEED_MAX_SHARES`: error raised if trying to create collab contract with more shares that it was tested for 77 | - `EXCEED_MAX_PARTICIPANTS`: error raised if trying to create collab with too much participants 78 | 79 | -------------------------------------------------------------------------------- /docs/hic_proxy_reference.md: -------------------------------------------------------------------------------- 1 | # Split contract entrypoints: 2 | ## default 3 | - Expects `unit` as parameter 4 | - Expects some xtz amount attached to the transaction 5 | - Returns operations with distributed splits to participants 6 | - Changes storage `undistributed` ledger 7 | - Changes storage `residuals` sum 8 | - Anyone can call this entrypoint 9 | - Behaviour depends on `threshold` settings 10 | - Some xtz amount might be locked in `residuals` 11 | 12 | This entrypoint allows HicProxy contract to receive xtz and split it among participants. When this contract used as a recepient of xtz (as a seller in the marketplace or as a royalty receiver) it runs logic that redistribute incoming value using participant shares. By default all incoming values automaticly sent to participants, but admin can set threshold that determines the minimal value that will trigger automatic payout to participant. If value is less than threshold it will be kept in contract until it reaches it. If incoming amount can't be split equally between all participants, the residuals left on the contract and will be reused next time contract recieves some payment (during next `default` call). 13 | 14 | ## withdraw 15 | - Expects `address` of the participant that should receive undistributed amount 16 | - Expects no attached amount 17 | - Returns operation with this undistributed amount from `undistributed` ledger 18 | - Sets `undistributed` ledger for `address` to `0n` 19 | - Anyone can call this entrypoint for any address in `undistributed` ledger 20 | 21 | This entrypoint allow participant to withdraw its undistributed earnings if there is some threshold set and participant wants to receive his share before this threshold is reached. Anyone can call this withdraw for anyone, so it is possible to withdraw for a contract if it had no logic to call withdraw by itself, it can be useful for stacked collab contracts. 22 | 23 | ## set\_threshold 24 | - Expects `nat` with new threshold value 25 | - Expects no attached amount 26 | - Changes storage `threshold` value 27 | - Only admin can call this entrypoint 28 | 29 | This entrypoint changes threshold value that used in `default` redistribution mechanics. Default value is `0n` which means no threshold is set. 30 | 31 | ## execute 32 | - Expects `executeParams` which is record of `lambda` with `executableCall` and `packedParams` with `bytes` payload 33 | - Might allow to attach some amount to the call 34 | - Emits operations that encoded in lambda 35 | - Only admin can call this entrypoint 36 | 37 | Allows administrator to make any operation from the contract name (this can be useful to support another marketplaces that can arise in the future). Shortly: this entrypoint allows admin to run any code that does not change contract storage. There are examples of lambdas in the repository, one of which is [marketplace V3 swap](../contracts/lambdas/call/marketplaceV3Swap.ligo) 38 | 39 | To compile lambda you can use docker version of LIGO compiler: 40 | ```console 41 | docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.39.0 compile expression pascaligo lambda --init-file "contracts/lambdas/call/marketplaceV3Swap.ligo" > build/tz/lambdas/call/marketplace_v3_swap.tz 42 | ``` 43 | 44 | # Teia / h=n related entrypoints: 45 | ## mint\_OBJKT 46 | - Expects `mintParams` that have the same interface that h=n `mint_OBJKT` entrypoint 47 | - Expects no attached amount 48 | - Emits mint operation to `mint_OBJKT` to the storage `minterAddress` 49 | - Only admin can call this entrypoint 50 | 51 | When this entrypoint is called all given params redirected to the h=n minter using split-contract name as a Source, so split-contract became creator of the minted objkt. This allows this contract receive and redistribute royalties in the future. 52 | 53 | ## swap 54 | - Expects `swapParams` that have the same interface that h=n V2 marketplace `swap` entrypoint 55 | - Expects no attached amount 56 | - Emits mint operation to `swap` to the storage `marketplaceAddress` 57 | - Only admin can call this entrypoint 58 | 59 | The same as `mint_OBJKT` this `swap` call allows to swap token on V2 marketplace from the name of the collab contract. This entrypoint was not updated to V3 marketplace to keep backward compatibility. This is possible to swap using Teia V3 marketplace with lambda call. 60 | 61 | ## cancel\_swap 62 | - Expects `cancelSwapParams` that have the same interface that h=n V2 marketplace `cancel_swap` entrypoint 63 | - Expects no attached amount 64 | - Emits mint operation to `cancel_swap` to the storage `marketplaceAddress` 65 | - Only admin can call this entrypoint 66 | 67 | This entrypoint allow to cancel swap from V2 marketplace. 68 | 69 | ## collect 70 | - Expects `collectParams` that have the same interface that h=n V2 marketplace `collect` entrypoint 71 | - Expects no attached amount 72 | - Emits mint operation to `collect` to the storage `marketplaceAddress` 73 | - Only admin can call this entrypoint 74 | 75 | This entrypoint allows to collect other tokens from V2 marketplace. 76 | 77 | ## registry 78 | - Expects `registryParams` that have the same interface that h=n registry `registry` entrypoint 79 | - Expects no attached amount 80 | - Emits mint operation to `registry` to the storage `registryAddress` 81 | - Only admin can call this entrypoint 82 | 83 | This entrypoint allows to change collab name / description in registry. 84 | 85 | ## unregistry 86 | - Expects `unit` as parameter 87 | - Expects no attached amount 88 | - Emits mint operation to `unregistry` to the storage `registryAddress` 89 | - Only admin can call this entrypoint 90 | 91 | ## update\_operators 92 | - Expects FA2 `update_operators` as parameter 93 | - Expects no attached amount 94 | - Emits mint operation to `update_operators` to the storage `tokenAddress` 95 | - Only admin can call this entrypoint 96 | 97 | This entrypoint allows to make `update_operators` call from collab contract name which is required to interact from collab contract with another contracts (including marketplaces). 98 | 99 | ## transfer 100 | - Expects FA2 `transfer` as parameter 101 | - Expects no attached amount 102 | - Emits mint operation to `transfer` to the storage `tokenAddress` 103 | - Only admin can call this entrypoint 104 | 105 | This entrypoint allows to make transfers from the collab contract name. It might be useful for airdrops. 106 | 107 | # Management entrypoints: 108 | ## update\_admin 109 | - Expects `address` of new administrator 110 | - Expects no attached amount 111 | - Changes storage `administrator` to new administrator 112 | - Only admin can call this entrypoint 113 | 114 | This can be useful to transfer admin rights to another address. 115 | 116 | # Onchain views: 117 | Onchain views is feature that was added in hangzhou protocol upgrade. This allows to make read-only synchronous calls to the contract to get some info onchain. 118 | ## get\_shares 119 | - Expects `unit` as parameter 120 | - Returns map with addresses as keys and shares as values for each participant from the contract 121 | 122 | ## get\_core\_participants 123 | - Expects `unit` as parameter 124 | - Returns set of participants that required to sign to be shown in UI 125 | 126 | ## get\_administrator 127 | - Expects `unit` as parameter 128 | - Returns administrator address 129 | 130 | ## get\_total\_received 131 | - Expects `unit` as parameter 132 | - Returns amount of xtz that was received by this collab during its existance time (might have interesting usecases I suppose) 133 | 134 | ## get\_total\_shares 135 | - Expects `unit` as parameter 136 | - Return amount of total shares in contract 137 | 138 | # Errors: 139 | - `MINT_NF`: error raised if minter is not properly configured during contract origination 140 | - `SWAP_NF`: error raised if marketplace is not properly configured during contract origination 141 | - `REG_NF`: error raised if registry is not properly configured during contract origination 142 | - `ADDR_NF`: error raised if recipient address is not found 143 | - `FA2_NF`: error raised if token is not properly configured during contract origination 144 | - `AMNT_FRBD`: error raised if entrypoint is not expecting to receive any amount but gets some 145 | - `NOT_ADM`: error raised if entrypoint is expected to be called by admin only and called by anyone else 146 | - `WR_SHARES`: error raised during distribution if the sum of shares is not equal to total shares 147 | - `WR_ADDR`: error raised if recipient address requested for withdraw have no splits in collab 148 | 149 | -------------------------------------------------------------------------------- /docs/split-contract-mechanics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ztepler/hic-contract-proxy/16485e0f6445c8d23ba31b62edb91d6622126cad/docs/split-contract-mechanics.png -------------------------------------------------------------------------------- /pytezos_tests/bigmap_interaction_test.py: -------------------------------------------------------------------------------- 1 | from pytezos import ContractInterface, pytezos, MichelsonRuntimeError 2 | from hic_base import HicBaseCase 3 | from unittest import TestCase 4 | from os.path import dirname, join 5 | 6 | 7 | CONTRACT_FN = '../build/tz/contract_proxy_bigmap.tz' 8 | 9 | 10 | class BigMapInteractionTest(HicBaseCase): 11 | 12 | def setUp(self): 13 | super().setUp() 14 | self.collab = ContractInterface.from_file(join(dirname(__file__), CONTRACT_FN)) 15 | 16 | # TODO: replace this with factory creating bigmap contract: 17 | self.collab_storage = { 18 | 'administrator': self.p1, 19 | 'accounts': { 20 | self.p1: {'share': 330, 'withdrawalsSum': 0}, 21 | self.p2: {'share': 500, 'withdrawalsSum': 0}, 22 | self.tips: {'share': 170, 'withdrawalsSum': 0} 23 | }, 24 | 'totalWithdrawalsSum': 0, 25 | 'minterAddress': 'KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9', 26 | 'marketplaceAddress': 'KT1HbQepzV1nVGg8QVznG7z4RcHseD5kwqBn', 27 | } 28 | 29 | 30 | def _test_nothing_withdraw_error(self): 31 | """ Testing that withdrawing with zero balance raises MichelsonRuntimeError """ 32 | 33 | with self.assertRaises(MichelsonRuntimeError): 34 | self.result = self.collab.withdraw().interpret( 35 | storage=self.result.storage, sender=self.p1, balance=0) 36 | 37 | 38 | def _test_mint_call_without_admin_role(self): 39 | """ Testing that calling mint from non-administrator address is not possible """ 40 | 41 | with self.assertRaises(MichelsonRuntimeError): 42 | self.result = self.collab.mint_OBJKT(self.default_params['mint_OBJKT']).interpret( 43 | storage=self.result.storage, sender=self.p2) 44 | 45 | 46 | def _swap_call_without_admin_role(self): 47 | """ Testing that calling swap from non-administrator address is not possible """ 48 | 49 | with self.assertRaises(MichelsonRuntimeError): 50 | self.result = self.collab.swap(self.swap_params).interpret( 51 | storage=self.result.storage, sender=self.p2) 52 | 53 | 54 | def _test_p1_withdraw(self): 55 | """ Testing withdraw from p1 with positive balance """ 56 | 57 | self.total_incomings += 10_000_000 58 | self.result = self.collab.withdraw().interpret( 59 | storage=self.result.storage, sender=self.p1, balance=self.total_incomings) 60 | 61 | assert len(self.result.operations) == 1 62 | op = self.result.operations[0] 63 | 64 | assert op['destination'] == self.p1 65 | assert int(op['amount']) == 3_300_000 66 | assert self.result.storage['totalWithdrawalsSum'] == 3_300_000 67 | 68 | assert self.result.storage['accounts'][ self.p1 ]['withdrawalsSum'] == 3_300_000 69 | assert self.result.storage['accounts'][ self.p2 ]['withdrawalsSum'] == 0 70 | assert self.result.storage['accounts'][ self.tips ]['withdrawalsSum'] == 0 71 | 72 | 73 | def _test_p2_withdraw(self): 74 | """ Testing withdrawals of p2 without any new incomings """ 75 | 76 | self.total_incomings -= 3_300_000 77 | 78 | # testing withdraw from p2 with the balance from prev transaction: 79 | self.result = self.collab.withdraw().interpret( 80 | storage=self.result.storage, sender=self.p2, balance=self.total_incomings) 81 | 82 | assert len(self.result.operations) == 1 83 | op = self.result.operations[0] 84 | 85 | assert op['destination'] == self.p2 86 | assert int(op['amount']) == 5_000_000 87 | assert self.result.storage['totalWithdrawalsSum'] == 8_300_000 88 | 89 | assert self.result.storage['accounts'][ self.p1 ]['withdrawalsSum'] == 3_300_000 90 | assert self.result.storage['accounts'][ self.p2 ]['withdrawalsSum'] == 5_000_000 91 | assert self.result.storage['accounts'][ self.tips ]['withdrawalsSum'] == 0 92 | 93 | 94 | def _test_p3_withdraw(self): 95 | """ Testing withdraw from p3 with updated balance """ 96 | 97 | # incomings without withdrawal of p2: 98 | self.total_incomings -= 5_000_000 99 | 100 | # new sales: 101 | self.total_incomings += 20_000_000 102 | 103 | self.result = self.collab.withdraw().interpret( 104 | storage=self.result.storage, sender=self.tips, balance=self.total_incomings) 105 | 106 | assert len(self.result.operations) == 1 107 | op = self.result.operations[0] 108 | 109 | assert op['destination'] == self.tips 110 | assert int(op['amount']) == 5_100_000 111 | assert self.result.storage['totalWithdrawalsSum'] == 13_400_000 112 | 113 | assert self.result.storage['accounts'][ self.p1 ]['withdrawalsSum'] == 3_300_000 114 | assert self.result.storage['accounts'][ self.p2 ]['withdrawalsSum'] == 5_000_000 115 | assert self.result.storage['accounts'][ self.tips ]['withdrawalsSum'] == 5_100_000 116 | 117 | 118 | def _test_p1_withdraw_again(self): 119 | """ Again p1 withdraws their part from second sale """ 120 | 121 | self.total_incomings -= 5_100_000 122 | 123 | self.result = self.collab.withdraw().interpret( 124 | storage=self.result.storage, sender=self.p1, balance=self.total_incomings) 125 | 126 | assert len(self.result.operations) == 1 127 | op = self.result.operations[0] 128 | 129 | assert op['destination'] == self.p1 130 | assert int(op['amount']) == 6_600_000 131 | assert self.result.storage['totalWithdrawalsSum'] == 20_000_000 132 | 133 | assert self.result.storage['accounts'][ self.p1 ]['withdrawalsSum'] == 9_900_000 134 | assert self.result.storage['accounts'][ self.p2 ]['withdrawalsSum'] == 5_000_000 135 | assert self.result.storage['accounts'][ self.tips ]['withdrawalsSum'] == 5_100_000 136 | 137 | 138 | def test_interactions(self): 139 | self._collab_mint(sender=self.p1) 140 | self._test_mint_call_without_admin_role() 141 | self._test_nothing_withdraw_error() 142 | self._collab_swap(sender=self.p1) 143 | self._swap_call_without_admin_role() 144 | self._test_p1_withdraw() 145 | self._test_p2_withdraw() 146 | self._test_p3_withdraw() 147 | self._test_p1_withdraw_again() 148 | 149 | -------------------------------------------------------------------------------- /pytezos_tests/contracts/curation.json: -------------------------------------------------------------------------------- 1 | { 2 | "curations": {}, 3 | "locked": false, 4 | "manager": "tz1UBZUkXpKGhYsP5KtzDNqLLchwF4uHrGjw", 5 | "metadata": {}, 6 | "protocol": "KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9", 7 | "fa2": "KT1AFA2mwNUMNd4SsujE1YYp29vd8BZejyKW" 8 | } -------------------------------------------------------------------------------- /pytezos_tests/contracts/curation.tz: -------------------------------------------------------------------------------- 1 | storage (pair 2 | (pair (big_map %curations nat (pair (nat %hDAO_balance) (address %issuer))) 3 | (pair (address %fa2) (bool %locked))) 4 | (pair (address %manager) 5 | (pair (big_map %metadata string bytes) (address %protocol)))); 6 | parameter (or (pair %claim_hDAO (nat %hDAO_amount) (nat %objkt_id)) 7 | (or (pair %configure (address %fa2) (address %protocol)) 8 | (pair %curate (nat %hDAO_amount) 9 | (pair (address %issuer) (nat %objkt_id))))); 10 | code { DUP ; 11 | CDR ; 12 | SWAP ; 13 | CAR ; 14 | IF_LEFT 15 | { SWAP ; 16 | DUP ; 17 | DUG 2 ; 18 | CAR ; 19 | CAR ; 20 | SWAP ; 21 | DUP ; 22 | DUG 2 ; 23 | CDR ; 24 | GET ; 25 | IF_NONE { PUSH int 90 ; FAILWITH } {} ; 26 | CDR ; 27 | SENDER ; 28 | COMPARE ; 29 | EQ ; 30 | IF 31 | { SWAP ; 32 | DUP ; 33 | DUG 2 ; 34 | CAR ; 35 | CAR ; 36 | SWAP ; 37 | DUP ; 38 | DUG 2 ; 39 | CDR ; 40 | GET ; 41 | IF_NONE { PUSH int 90 ; FAILWITH } {} ; 42 | CAR ; 43 | SWAP ; 44 | DUP ; 45 | DUG 2 ; 46 | CAR ; 47 | COMPARE ; 48 | LE } 49 | { PUSH bool False } ; 50 | IF 51 | {} 52 | { PUSH string "WrongCondition: (sp.sender == self.data.curations[params.objkt_id].issuer) & (params.hDAO_amount <= self.data.curations[params.objkt_id].hDAO_balance)" ; 53 | FAILWITH } ; 54 | NIL operation ; 55 | DIG 2 ; 56 | DUP ; 57 | DUG 3 ; 58 | CAR ; 59 | CDR ; 60 | CAR ; 61 | CONTRACT %transfer (list (pair (address %from_) 62 | (list %txs (pair (address %to_) 63 | (pair (nat %token_id) 64 | (nat %amount)))))) ; 65 | IF_NONE { PUSH int 96 ; FAILWITH } {} ; 66 | PUSH mutez 0 ; 67 | NIL (pair (address %from_) 68 | (list %txs (pair (address %to_) (pair (nat %token_id) (nat %amount))))) ; 69 | NIL (pair (address %to_) (pair (nat %token_id) (nat %amount))) ; 70 | DIG 5 ; 71 | DUP ; 72 | DUG 6 ; 73 | CAR ; 74 | PUSH nat 0 ; 75 | PAIR %token_id %amount ; 76 | SENDER ; 77 | PAIR %to_ ; 78 | CONS ; 79 | SELF ; 80 | ADDRESS ; 81 | PAIR %from_ %txs ; 82 | CONS ; 83 | TRANSFER_TOKENS ; 84 | CONS ; 85 | DIG 2 ; 86 | DUP ; 87 | DUG 3 ; 88 | DUP ; 89 | CDR ; 90 | SWAP ; 91 | CAR ; 92 | DUP ; 93 | CDR ; 94 | SWAP ; 95 | CAR ; 96 | DUP ; 97 | DIG 5 ; 98 | DUP ; 99 | DUG 6 ; 100 | CDR ; 101 | DUP ; 102 | DUG 2 ; 103 | GET ; 104 | IF_NONE { PUSH int 93 ; FAILWITH } {} ; 105 | CDR ; 106 | DIG 6 ; 107 | DUP ; 108 | DUG 7 ; 109 | CAR ; 110 | DIG 8 ; 111 | CAR ; 112 | CAR ; 113 | DIG 8 ; 114 | CDR ; 115 | GET ; 116 | IF_NONE { PUSH int 93 ; FAILWITH } {} ; 117 | CAR ; 118 | SUB ; 119 | ABS ; 120 | PAIR ; 121 | SOME ; 122 | SWAP ; 123 | UPDATE ; 124 | PAIR ; 125 | PAIR ; 126 | SWAP } 127 | { IF_LEFT 128 | { SWAP ; 129 | DUP ; 130 | DUG 2 ; 131 | CDR ; 132 | CAR ; 133 | SENDER ; 134 | COMPARE ; 135 | EQ ; 136 | IF { SWAP ; DUP ; DUG 2 ; CAR ; CDR ; CDR ; NOT } { PUSH bool False } ; 137 | IF 138 | {} 139 | { PUSH string "WrongCondition: (sp.sender == self.data.manager) & (~ self.data.locked)" ; 140 | FAILWITH } ; 141 | SWAP ; 142 | DUP ; 143 | CDR ; 144 | SWAP ; 145 | CAR ; 146 | DUP ; 147 | CAR ; 148 | SWAP ; 149 | CDR ; 150 | CDR ; 151 | DIG 3 ; 152 | DUP ; 153 | DUG 4 ; 154 | CAR ; 155 | PAIR ; 156 | SWAP ; 157 | PAIR ; 158 | PAIR ; 159 | DUP ; 160 | CAR ; 161 | SWAP ; 162 | CDR ; 163 | DUP ; 164 | CAR ; 165 | SWAP ; 166 | CDR ; 167 | CAR ; 168 | DIG 3 ; 169 | CDR ; 170 | SWAP ; 171 | PAIR ; 172 | SWAP ; 173 | PAIR ; 174 | SWAP ; 175 | PAIR ; 176 | DUP ; 177 | CDR ; 178 | SWAP ; 179 | CAR ; 180 | DUP ; 181 | CAR ; 182 | SWAP ; 183 | CDR ; 184 | CAR ; 185 | PUSH bool True ; 186 | SWAP ; 187 | PAIR ; 188 | SWAP ; 189 | PAIR ; 190 | PAIR } 191 | { SWAP ; 192 | DUP ; 193 | DUG 2 ; 194 | CDR ; 195 | CDR ; 196 | CDR ; 197 | SENDER ; 198 | COMPARE ; 199 | EQ ; 200 | IF 201 | {} 202 | { PUSH string "WrongCondition: sp.sender == self.data.protocol" ; 203 | FAILWITH } ; 204 | SWAP ; 205 | DUP ; 206 | DUG 2 ; 207 | CAR ; 208 | CAR ; 209 | SWAP ; 210 | DUP ; 211 | DUG 2 ; 212 | CDR ; 213 | CDR ; 214 | MEM ; 215 | IF 216 | { SWAP ; 217 | DUP ; 218 | CDR ; 219 | SWAP ; 220 | CAR ; 221 | DUP ; 222 | CDR ; 223 | SWAP ; 224 | CAR ; 225 | DUP ; 226 | DIG 4 ; 227 | DUP ; 228 | DUG 5 ; 229 | CDR ; 230 | CDR ; 231 | DUP ; 232 | DUG 2 ; 233 | GET ; 234 | IF_NONE { PUSH int 103 ; FAILWITH } {} ; 235 | DUP ; 236 | CDR ; 237 | SWAP ; 238 | CAR ; 239 | DIG 6 ; 240 | DUP ; 241 | DUG 7 ; 242 | CAR ; 243 | ADD ; 244 | PAIR ; 245 | SOME ; 246 | SWAP ; 247 | UPDATE ; 248 | PAIR ; 249 | PAIR ; 250 | DUP ; 251 | CDR ; 252 | SWAP ; 253 | CAR ; 254 | DUP ; 255 | CDR ; 256 | SWAP ; 257 | CAR ; 258 | DUP ; 259 | DIG 4 ; 260 | DUP ; 261 | DUG 5 ; 262 | CDR ; 263 | CDR ; 264 | DUP ; 265 | DUG 2 ; 266 | GET ; 267 | IF_NONE { PUSH int 104 ; FAILWITH } {} ; 268 | CAR ; 269 | DIG 5 ; 270 | CDR ; 271 | CAR ; 272 | SWAP ; 273 | PAIR ; 274 | SOME ; 275 | SWAP ; 276 | UPDATE ; 277 | PAIR ; 278 | PAIR } 279 | { SWAP ; 280 | DUP ; 281 | CDR ; 282 | SWAP ; 283 | CAR ; 284 | DUP ; 285 | CDR ; 286 | SWAP ; 287 | CAR ; 288 | DIG 3 ; 289 | DUP ; 290 | CDR ; 291 | CAR ; 292 | SWAP ; 293 | DUP ; 294 | DUG 5 ; 295 | CAR ; 296 | PAIR %hDAO_balance %issuer ; 297 | SOME ; 298 | DIG 4 ; 299 | CDR ; 300 | CDR ; 301 | UPDATE ; 302 | PAIR ; 303 | PAIR } } ; 304 | NIL operation } ; 305 | PAIR } -------------------------------------------------------------------------------- /pytezos_tests/contracts/fa2_hdao.json: -------------------------------------------------------------------------------- 1 | { 2 | "administrator": "KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9", 3 | "all_tokens": 0, 4 | "ledger": {}, 5 | "metadata": {}, 6 | "operators": {}, 7 | "paused": false, 8 | "token_metadata": {} 9 | } -------------------------------------------------------------------------------- /pytezos_tests/contracts/fa2_objkts.json: -------------------------------------------------------------------------------- 1 | { 2 | "administrator": "KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9", 3 | "all_tokens": 0, 4 | "ledger": {}, 5 | "metadata": {}, 6 | "operators": {}, 7 | "paused": false, 8 | "token_metadata": {} 9 | } -------------------------------------------------------------------------------- /pytezos_tests/contracts/marketplace.json: -------------------------------------------------------------------------------- 1 | { 2 | "counter": 0, 3 | "fee": 25, 4 | "manager": "tz1hdQscorfqMzFqYxnrApuS5i6QSTuoAp3w", 5 | "metadata": {}, 6 | "objkt": "KT1Kqg6A9m2HBxFDsHpHJeoMFAFiwnKELqHo", 7 | "swaps": {} 8 | } 9 | -------------------------------------------------------------------------------- /pytezos_tests/contracts/marketplace.tz: -------------------------------------------------------------------------------- 1 | storage (pair (pair (nat %counter) (pair (nat %fee) (address %manager))) 2 | (pair (big_map %metadata string bytes) 3 | (pair (address %objkt) 4 | (big_map %swaps nat 5 | (pair 6 | (pair (address %creator) 7 | (pair (address %issuer) 8 | (nat %objkt_amount))) 9 | (pair (nat %objkt_id) 10 | (pair (nat %royalties) 11 | (mutez %xtz_per_objkt)))))))); 12 | parameter (or (or (nat %cancel_swap) (nat %collect)) 13 | (or 14 | (pair %swap (pair (address %creator) (nat %objkt_amount)) 15 | (pair (nat %objkt_id) 16 | (pair (nat %royalties) (mutez %xtz_per_objkt)))) 17 | (or (nat %update_fee) (address %update_manager)))); 18 | code { CAST (pair 19 | (or (or nat nat) 20 | (or (pair (pair address nat) (pair nat (pair nat mutez))) 21 | (or nat address))) 22 | (pair (pair nat (pair nat address)) 23 | (pair (big_map string bytes) 24 | (pair address 25 | (big_map nat 26 | (pair (pair address (pair address nat)) 27 | (pair nat (pair nat mutez)))))))) ; 28 | UNPAIR ; 29 | IF_LEFT 30 | { IF_LEFT 31 | { SWAP ; 32 | DUP ; 33 | DUG 2 ; 34 | GET 6 ; 35 | SWAP ; 36 | DUP ; 37 | DUG 2 ; 38 | GET ; 39 | IF_NONE { PUSH int 328 ; FAILWITH } {} ; 40 | CAR ; 41 | GET 3 ; 42 | SENDER ; 43 | COMPARE ; 44 | EQ ; 45 | IF 46 | { PUSH nat 0 ; 47 | DUP 3 ; 48 | GET 6 ; 49 | DUP 3 ; 50 | GET ; 51 | IF_NONE { PUSH int 328 ; FAILWITH } {} ; 52 | CAR ; 53 | GET 4 ; 54 | COMPARE ; 55 | NEQ } 56 | { PUSH bool False } ; 57 | IF 58 | {} 59 | { PUSH string "WrongCondition: (sp.sender == self.data.swaps[params].issuer) & (self.data.swaps[params].objkt_amount != 0)" ; 60 | FAILWITH } ; 61 | NIL operation ; 62 | DUP 3 ; 63 | GET 5 ; 64 | CONTRACT %transfer (list (pair address 65 | (list (pair address (pair nat nat))))) ; 66 | IF_NONE { PUSH int 343 ; FAILWITH } {} ; 67 | PUSH mutez 0 ; 68 | NIL (pair address (list (pair address (pair nat nat)))) ; 69 | NIL (pair address (pair nat nat)) ; 70 | DUP 7 ; 71 | GET 6 ; 72 | DUP 7 ; 73 | GET ; 74 | IF_NONE { PUSH int 329 ; FAILWITH } {} ; 75 | CAR ; 76 | GET 4 ; 77 | DUP 8 ; 78 | GET 6 ; 79 | DUP 8 ; 80 | GET ; 81 | IF_NONE { PUSH int 329 ; FAILWITH } {} ; 82 | GET 3 ; 83 | SENDER ; 84 | PAIR 3 ; 85 | CONS ; 86 | SELF_ADDRESS ; 87 | PAIR ; 88 | CONS ; 89 | TRANSFER_TOKENS ; 90 | CONS ; 91 | DIG 2 ; 92 | DUP ; 93 | GET 6 ; 94 | NONE (pair (pair address (pair address nat)) (pair nat (pair nat mutez))) ; 95 | DIG 4 ; 96 | UPDATE ; 97 | UPDATE 6 ; 98 | SWAP } 99 | { PUSH mutez 1 ; 100 | DUP ; 101 | DUP 4 ; 102 | GET 6 ; 103 | DUP 4 ; 104 | GET ; 105 | IF_NONE { PUSH int 301 ; FAILWITH } {} ; 106 | GET 6 ; 107 | EDIV ; 108 | IF_NONE { PUSH int 301 ; FAILWITH } {} ; 109 | CAR ; 110 | MUL ; 111 | AMOUNT ; 112 | COMPARE ; 113 | EQ ; 114 | IF 115 | { PUSH nat 0 ; 116 | DUP 3 ; 117 | GET 6 ; 118 | DUP 3 ; 119 | GET ; 120 | IF_NONE { PUSH int 301 ; FAILWITH } {} ; 121 | CAR ; 122 | GET 4 ; 123 | COMPARE ; 124 | NEQ } 125 | { PUSH bool False } ; 126 | IF 127 | {} 128 | { PUSH string "WrongCondition: (sp.amount == sp.mul(sp.fst(sp.ediv(self.data.swaps[params.swap_id].xtz_per_objkt, sp.mutez(1)).open_some()), sp.mutez(1))) & (self.data.swaps[params.swap_id].objkt_amount != 0)" ; 129 | FAILWITH } ; 130 | PUSH mutez 0 ; 131 | DUP 3 ; 132 | GET 6 ; 133 | DUP 3 ; 134 | GET ; 135 | IF_NONE { PUSH int 305 ; FAILWITH } {} ; 136 | GET 6 ; 137 | COMPARE ; 138 | NEQ ; 139 | IF 140 | { NIL operation ; 141 | DUP 3 ; 142 | GET 6 ; 143 | DUP 3 ; 144 | GET ; 145 | IF_NONE { PUSH int 314 ; FAILWITH } {} ; 146 | CAR ; 147 | CAR ; 148 | CONTRACT unit ; 149 | IF_NONE { PUSH int 314 ; FAILWITH } {} ; 150 | PUSH mutez 1 ; 151 | DIG 4 ; 152 | DUP ; 153 | CAR ; 154 | GET 3 ; 155 | SWAP ; 156 | DUP ; 157 | DUG 6 ; 158 | GET 6 ; 159 | DUP 6 ; 160 | GET ; 161 | IF_NONE { PUSH int 311 ; FAILWITH } {} ; 162 | GET 5 ; 163 | ADD ; 164 | PUSH nat 1000 ; 165 | DIG 6 ; 166 | DUP ; 167 | CAR ; 168 | GET 3 ; 169 | SWAP ; 170 | DUP ; 171 | DUG 8 ; 172 | GET 6 ; 173 | DUP 8 ; 174 | GET ; 175 | IF_NONE { PUSH int 310 ; FAILWITH } {} ; 176 | GET 5 ; 177 | ADD ; 178 | PUSH mutez 1 ; 179 | DUP 9 ; 180 | GET 6 ; 181 | DUP 9 ; 182 | GET ; 183 | IF_NONE { PUSH int 307 ; FAILWITH } {} ; 184 | GET 6 ; 185 | EDIV ; 186 | IF_NONE { PUSH int 307 ; FAILWITH } {} ; 187 | CAR ; 188 | MUL ; 189 | EDIV ; 190 | IF_NONE { PUSH int 310 ; FAILWITH } { CAR } ; 191 | DUP 7 ; 192 | GET 6 ; 193 | DUP 7 ; 194 | GET ; 195 | IF_NONE { PUSH int 311 ; FAILWITH } {} ; 196 | GET 5 ; 197 | MUL ; 198 | EDIV ; 199 | IF_NONE { PUSH int 311 ; FAILWITH } { CAR } ; 200 | MUL ; 201 | UNIT ; 202 | TRANSFER_TOKENS ; 203 | CONS ; 204 | DUP 3 ; 205 | CAR ; 206 | GET 4 ; 207 | CONTRACT unit ; 208 | IF_NONE { PUSH int 317 ; FAILWITH } {} ; 209 | PUSH mutez 1 ; 210 | DIG 4 ; 211 | DUP ; 212 | CAR ; 213 | GET 3 ; 214 | SWAP ; 215 | DUP ; 216 | DUG 6 ; 217 | GET 6 ; 218 | DUP 6 ; 219 | GET ; 220 | IF_NONE { PUSH int 311 ; FAILWITH } {} ; 221 | GET 5 ; 222 | ADD ; 223 | PUSH nat 1000 ; 224 | DIG 6 ; 225 | DUP ; 226 | CAR ; 227 | GET 3 ; 228 | SWAP ; 229 | DUP ; 230 | DUG 8 ; 231 | GET 6 ; 232 | DUP 8 ; 233 | GET ; 234 | IF_NONE { PUSH int 310 ; FAILWITH } {} ; 235 | GET 5 ; 236 | ADD ; 237 | PUSH mutez 1 ; 238 | DUP 9 ; 239 | GET 6 ; 240 | DUP 9 ; 241 | GET ; 242 | IF_NONE { PUSH int 307 ; FAILWITH } {} ; 243 | GET 6 ; 244 | EDIV ; 245 | IF_NONE { PUSH int 307 ; FAILWITH } {} ; 246 | CAR ; 247 | MUL ; 248 | EDIV ; 249 | IF_NONE { PUSH int 310 ; FAILWITH } { CAR } ; 250 | DUP 7 ; 251 | GET 6 ; 252 | DUP 7 ; 253 | GET ; 254 | IF_NONE { PUSH int 311 ; FAILWITH } {} ; 255 | GET 5 ; 256 | MUL ; 257 | EDIV ; 258 | IF_NONE { PUSH int 311 ; FAILWITH } { CAR } ; 259 | PUSH nat 1000 ; 260 | DIG 6 ; 261 | DUP ; 262 | CAR ; 263 | GET 3 ; 264 | SWAP ; 265 | DUP ; 266 | DUG 8 ; 267 | GET 6 ; 268 | DUP 8 ; 269 | GET ; 270 | IF_NONE { PUSH int 310 ; FAILWITH } {} ; 271 | GET 5 ; 272 | ADD ; 273 | PUSH mutez 1 ; 274 | DUP 9 ; 275 | GET 6 ; 276 | DUP 9 ; 277 | GET ; 278 | IF_NONE { PUSH int 307 ; FAILWITH } {} ; 279 | GET 6 ; 280 | EDIV ; 281 | IF_NONE { PUSH int 307 ; FAILWITH } {} ; 282 | CAR ; 283 | MUL ; 284 | EDIV ; 285 | IF_NONE { PUSH int 310 ; FAILWITH } { CAR } ; 286 | SUB ; 287 | ABS ; 288 | MUL ; 289 | UNIT ; 290 | TRANSFER_TOKENS ; 291 | CONS ; 292 | DUP 3 ; 293 | GET 6 ; 294 | DUP 3 ; 295 | GET ; 296 | IF_NONE { PUSH int 320 ; FAILWITH } {} ; 297 | CAR ; 298 | GET 3 ; 299 | CONTRACT unit ; 300 | IF_NONE { PUSH int 320 ; FAILWITH } {} ; 301 | PUSH mutez 1 ; 302 | PUSH nat 1000 ; 303 | DIG 5 ; 304 | DUP ; 305 | CAR ; 306 | GET 3 ; 307 | SWAP ; 308 | DUP ; 309 | DUG 7 ; 310 | GET 6 ; 311 | DUP 7 ; 312 | GET ; 313 | IF_NONE { PUSH int 310 ; FAILWITH } {} ; 314 | GET 5 ; 315 | ADD ; 316 | PUSH mutez 1 ; 317 | DUP 8 ; 318 | GET 6 ; 319 | DUP 8 ; 320 | GET ; 321 | IF_NONE { PUSH int 307 ; FAILWITH } {} ; 322 | GET 6 ; 323 | EDIV ; 324 | IF_NONE { PUSH int 307 ; FAILWITH } {} ; 325 | CAR ; 326 | MUL ; 327 | EDIV ; 328 | IF_NONE { PUSH int 310 ; FAILWITH } { CAR } ; 329 | MUL ; 330 | AMOUNT ; 331 | SUB_MUTEZ; ASSERT_SOME; 332 | UNIT ; 333 | TRANSFER_TOKENS ; 334 | CONS } 335 | { NIL operation } ; 336 | DUP 3 ; 337 | DUP ; 338 | GET 6 ; 339 | DUP ; 340 | DUP 5 ; 341 | DUP ; 342 | DUG 2 ; 343 | GET ; 344 | IF_NONE { PUSH int 322 ; FAILWITH } {} ; 345 | UNPAIR ; 346 | UNPAIR ; 347 | SWAP ; 348 | CAR ; 349 | PUSH nat 1 ; 350 | DIG 9 ; 351 | GET 6 ; 352 | DUP 10 ; 353 | GET ; 354 | IF_NONE { PUSH int 322 ; FAILWITH } {} ; 355 | CAR ; 356 | GET 4 ; 357 | SUB ; 358 | ISNAT ; 359 | IF_NONE { PUSH int 322 ; FAILWITH } {} ; 360 | SWAP ; 361 | PAIR ; 362 | SWAP ; 363 | PAIR ; 364 | PAIR ; 365 | SOME ; 366 | SWAP ; 367 | UPDATE ; 368 | UPDATE 6 ; 369 | DUP ; 370 | DUG 3 ; 371 | GET 5 ; 372 | CONTRACT %transfer (list (pair address 373 | (list (pair address (pair nat nat))))) ; 374 | IF_NONE { PUSH int 343 ; FAILWITH } {} ; 375 | PUSH mutez 0 ; 376 | NIL (pair address (list (pair address (pair nat nat)))) ; 377 | NIL (pair address (pair nat nat)) ; 378 | PUSH nat 1 ; 379 | DUP 8 ; 380 | GET 6 ; 381 | DIG 7 ; 382 | GET ; 383 | IF_NONE { PUSH int 324 ; FAILWITH } {} ; 384 | GET 3 ; 385 | SENDER ; 386 | PAIR 3 ; 387 | CONS ; 388 | SELF_ADDRESS ; 389 | PAIR ; 390 | CONS ; 391 | TRANSFER_TOKENS ; 392 | CONS } } 393 | { IF_LEFT 394 | { DUP ; 395 | CAR ; 396 | CDR ; 397 | PUSH nat 0 ; 398 | COMPARE ; 399 | LT ; 400 | IF 401 | { DUP ; 402 | GET 5 ; 403 | PUSH nat 0 ; 404 | SWAP ; 405 | COMPARE ; 406 | GE ; 407 | IF 408 | { DUP ; GET 5 ; PUSH nat 250 ; SWAP ; COMPARE ; LE } 409 | { PUSH bool False } } 410 | { PUSH bool False } ; 411 | IF 412 | {} 413 | { PUSH string "WrongCondition: (params.objkt_amount > 0) & ((params.royalties >= 0) & (params.royalties <= 250))" ; 414 | FAILWITH } ; 415 | NIL operation ; 416 | DUP 3 ; 417 | GET 5 ; 418 | CONTRACT %transfer (list (pair address 419 | (list (pair address (pair nat nat))))) ; 420 | IF_NONE { PUSH int 343 ; FAILWITH } {} ; 421 | PUSH mutez 0 ; 422 | NIL (pair address (list (pair address (pair nat nat)))) ; 423 | NIL (pair address (pair nat nat)) ; 424 | DIG 5 ; 425 | DUP ; 426 | CAR ; 427 | CDR ; 428 | SWAP ; 429 | DUP ; 430 | DUG 7 ; 431 | GET 3 ; 432 | SELF_ADDRESS ; 433 | PAIR 3 ; 434 | CONS ; 435 | SENDER ; 436 | PAIR ; 437 | CONS ; 438 | TRANSFER_TOKENS ; 439 | CONS ; 440 | DUP 3 ; 441 | DUP ; 442 | GET 6 ; 443 | DIG 3 ; 444 | DUP ; 445 | GET 6 ; 446 | SWAP ; 447 | DUP ; 448 | DUG 5 ; 449 | GET 5 ; 450 | PAIR ; 451 | DUP 5 ; 452 | GET 3 ; 453 | PAIR ; 454 | DUP 5 ; 455 | CAR ; 456 | CDR ; 457 | SENDER ; 458 | PAIR ; 459 | DIG 5 ; 460 | CAR ; 461 | CAR ; 462 | PAIR ; 463 | PAIR ; 464 | SOME ; 465 | DIG 4 ; 466 | CAR ; 467 | CAR ; 468 | UPDATE ; 469 | UPDATE 6 ; 470 | UNPAIR ; 471 | UNPAIR ; 472 | PUSH nat 1 ; 473 | ADD ; 474 | PAIR ; 475 | PAIR ; 476 | SWAP } 477 | { IF_LEFT 478 | { SWAP ; 479 | DUP ; 480 | DUG 2 ; 481 | CAR ; 482 | GET 4 ; 483 | SENDER ; 484 | COMPARE ; 485 | EQ ; 486 | IF 487 | {} 488 | { PUSH string "WrongCondition: sp.sender == self.data.manager" ; 489 | FAILWITH } ; 490 | SWAP ; 491 | UNPAIR ; 492 | UNPAIR ; 493 | SWAP ; 494 | CDR ; 495 | DIG 3 ; 496 | PAIR ; 497 | SWAP ; 498 | PAIR ; 499 | PAIR } 500 | { SWAP ; 501 | DUP ; 502 | DUG 2 ; 503 | CAR ; 504 | GET 4 ; 505 | SENDER ; 506 | COMPARE ; 507 | EQ ; 508 | IF 509 | {} 510 | { PUSH string "WrongCondition: sp.sender == self.data.manager" ; 511 | FAILWITH } ; 512 | SWAP ; 513 | UNPAIR ; 514 | UNPAIR ; 515 | SWAP ; 516 | CAR ; 517 | DIG 3 ; 518 | SWAP ; 519 | PAIR ; 520 | SWAP ; 521 | PAIR ; 522 | PAIR } ; 523 | NIL operation } } ; 524 | NIL operation ; 525 | SWAP ; 526 | ITER { CONS } ; 527 | PAIR } 528 | -------------------------------------------------------------------------------- /pytezos_tests/contracts/marketplace_v3.json: -------------------------------------------------------------------------------- 1 | { 2 | "manager": "tz1hdQscorfqMzFqYxnrApuS5i6QSTuoAp3w", 3 | "metadata": {}, 4 | "allowed_fa2s": {}, 5 | "swaps": {}, 6 | "fee": 25, 7 | "fee_recipient": "tz1hdQscorfqMzFqYxnrApuS5i6QSTuoAp3w", 8 | "counter": 0, 9 | "proposed_manager": null, 10 | "swaps_paused": false, 11 | "collects_paused": false 12 | } 13 | 14 | -------------------------------------------------------------------------------- /pytezos_tests/contracts/objkt_swap.json: -------------------------------------------------------------------------------- 1 | { 2 | "curate": "KT1TybhR7XraG75JFYKSrh7KnxukMBT5dor6", 3 | "genesis": "2021-04-15T02:09:41Z", 4 | "hdao": "KT1AFA2mwNUMNd4SsujE1YYp29vd8BZejyKW", 5 | "locked": false, 6 | "manager": "tz1UBZUkXpKGhYsP5KtzDNqLLchwF4uHrGjw", 7 | "metadata": {}, 8 | "objkt": "KT1RJ6PbjHpwc3M5rw5s2Nbmefwbuwbdxton", 9 | "objkt_id": 0, 10 | "royalties": {}, 11 | "size": 0, 12 | "swap_id": 0, 13 | "swaps": {} 14 | } -------------------------------------------------------------------------------- /pytezos_tests/contracts/subjkt.json: -------------------------------------------------------------------------------- 1 | { 2 | "entries": {}, 3 | "invoices": {}, 4 | "manager": "tz1Y1j7FK1X9Rrv2VdPz5bXoU7SszF8W1RnK", 5 | "metadata": {}, 6 | "registries": {}, 7 | "subjkts": {}, 8 | "subjkts_metadata": {} 9 | } -------------------------------------------------------------------------------- /pytezos_tests/contracts/subjkt.tz: -------------------------------------------------------------------------------- 1 | storage (pair 2 | (pair (big_map %entries address bool) 3 | (pair 4 | (big_map %invoices address (pair (bytes %invoice) (bytes %subjkt))) 5 | (address %manager))) 6 | (pair 7 | (pair (big_map %metadata string bytes) (big_map %registries address bytes)) 8 | (pair (big_map %subjkts bytes bool) (big_map %subjkts_metadata bytes bytes)))); 9 | parameter (or 10 | (or (pair %ban (address %address) (bytes %invoice)) 11 | (pair %registry (bytes %metadata) (bytes %subjkt))) 12 | (or (unit %unregistry) (address %update_manager))); 13 | code { UNPAIR ; 14 | IF_LEFT 15 | { IF_LEFT 16 | { SWAP ; 17 | DUP ; 18 | DUG 2 ; 19 | CAR ; 20 | GET 4 ; 21 | SENDER ; 22 | COMPARE ; 23 | EQ ; 24 | IF 25 | {} 26 | { PUSH string "WrongCondition: sp.sender == self.data.manager" ; 27 | FAILWITH } ; 28 | SWAP ; 29 | DUP ; 30 | DUG 2 ; 31 | UNPAIR ; 32 | UNPAIR ; 33 | SWAP ; 34 | UNPAIR ; 35 | DIG 5 ; 36 | GET 3 ; 37 | CDR ; 38 | DUP 6 ; 39 | CAR ; 40 | GET ; 41 | IF_NONE { PUSH int 90 ; FAILWITH } {} ; 42 | DUP 6 ; 43 | CDR ; 44 | PAIR ; 45 | SOME ; 46 | DUP 6 ; 47 | CAR ; 48 | UPDATE ; 49 | PAIR ; 50 | SWAP ; 51 | NONE bool ; 52 | DUP 5 ; 53 | CAR ; 54 | UPDATE ; 55 | PAIR ; 56 | PAIR ; 57 | DUP ; 58 | DUG 2 ; 59 | DUP ; 60 | GET 5 ; 61 | NONE bool ; 62 | DIG 4 ; 63 | GET 3 ; 64 | CDR ; 65 | DUP 5 ; 66 | CAR ; 67 | GET ; 68 | IF_NONE { PUSH int 95 ; FAILWITH } {} ; 69 | UPDATE ; 70 | UPDATE 5 ; 71 | DUP ; 72 | DUG 2 ; 73 | DUP ; 74 | GET 6 ; 75 | NONE bytes ; 76 | DIG 4 ; 77 | GET 3 ; 78 | CDR ; 79 | DUP 5 ; 80 | CAR ; 81 | GET ; 82 | IF_NONE { PUSH int 96 ; FAILWITH } {} ; 83 | UPDATE ; 84 | UPDATE 6 ; 85 | UNPAIR ; 86 | SWAP ; 87 | UNPAIR ; 88 | UNPAIR ; 89 | SWAP ; 90 | NONE bytes ; 91 | DIG 5 ; 92 | CAR ; 93 | UPDATE ; 94 | SWAP ; 95 | PAIR ; 96 | PAIR ; 97 | SWAP ; 98 | PAIR } 99 | { SWAP ; 100 | DUP ; 101 | DUG 2 ; 102 | GET 5 ; 103 | SWAP ; 104 | DUP ; 105 | DUG 2 ; 106 | CDR ; 107 | GET ; 108 | IF_NONE { PUSH bool False } {} ; 109 | IF 110 | { DUP ; 111 | CDR ; 112 | DUP 3 ; 113 | GET 3 ; 114 | CDR ; 115 | SENDER ; 116 | GET ; 117 | IF_NONE { PUSH int 72 ; FAILWITH } {} ; 118 | COMPARE ; 119 | EQ ; 120 | IF 121 | {} 122 | { PUSH string "WrongCondition: self.data.registries[sp.sender] == params.subjkt" ; 123 | FAILWITH } } 124 | {} ; 125 | SWAP ; 126 | DUP ; 127 | DUG 2 ; 128 | CAR ; 129 | CAR ; 130 | SENDER ; 131 | GET ; 132 | IF_NONE { PUSH bool False } {} ; 133 | IF 134 | { SWAP ; 135 | UNPAIR ; 136 | UNPAIR ; 137 | NONE bool ; 138 | SENDER ; 139 | UPDATE ; 140 | PAIR ; 141 | PAIR ; 142 | DUP ; 143 | DUG 2 ; 144 | DUP ; 145 | GET 5 ; 146 | NONE bool ; 147 | DIG 4 ; 148 | GET 3 ; 149 | CDR ; 150 | SENDER ; 151 | GET ; 152 | IF_NONE { PUSH int 95 ; FAILWITH } {} ; 153 | UPDATE ; 154 | UPDATE 5 ; 155 | DUP ; 156 | DUG 2 ; 157 | DUP ; 158 | GET 6 ; 159 | NONE bytes ; 160 | DIG 4 ; 161 | GET 3 ; 162 | CDR ; 163 | SENDER ; 164 | GET ; 165 | IF_NONE { PUSH int 96 ; FAILWITH } {} ; 166 | UPDATE ; 167 | UPDATE 6 ; 168 | UNPAIR ; 169 | SWAP ; 170 | UNPAIR ; 171 | UNPAIR ; 172 | SWAP ; 173 | NONE bytes ; 174 | SENDER ; 175 | UPDATE ; 176 | SWAP ; 177 | PAIR ; 178 | PAIR ; 179 | SWAP ; 180 | PAIR ; 181 | SWAP } 182 | {} ; 183 | SWAP ; 184 | UNPAIR ; 185 | UNPAIR ; 186 | PUSH (option bool) (Some True) ; 187 | SENDER ; 188 | UPDATE ; 189 | PAIR ; 190 | PAIR ; 191 | DUP ; 192 | GET 5 ; 193 | PUSH (option bool) (Some True) ; 194 | DUP 4 ; 195 | CDR ; 196 | UPDATE ; 197 | UPDATE 5 ; 198 | DUP ; 199 | GET 6 ; 200 | DUP 3 ; 201 | CAR ; 202 | SOME ; 203 | DUP 4 ; 204 | CDR ; 205 | UPDATE ; 206 | UPDATE 6 ; 207 | UNPAIR ; 208 | SWAP ; 209 | UNPAIR ; 210 | UNPAIR ; 211 | SWAP ; 212 | DIG 4 ; 213 | CDR ; 214 | SOME ; 215 | SENDER ; 216 | UPDATE ; 217 | SWAP ; 218 | PAIR ; 219 | PAIR ; 220 | SWAP ; 221 | PAIR } } 222 | { IF_LEFT 223 | { DROP ; 224 | UNPAIR ; 225 | UNPAIR ; 226 | NONE bool ; 227 | SENDER ; 228 | UPDATE ; 229 | PAIR ; 230 | PAIR ; 231 | DUP ; 232 | DUP ; 233 | GET 5 ; 234 | NONE bool ; 235 | DIG 3 ; 236 | GET 3 ; 237 | CDR ; 238 | SENDER ; 239 | GET ; 240 | IF_NONE { PUSH int 95 ; FAILWITH } {} ; 241 | UPDATE ; 242 | UPDATE 5 ; 243 | DUP ; 244 | DUP ; 245 | GET 6 ; 246 | NONE bytes ; 247 | DIG 3 ; 248 | GET 3 ; 249 | CDR ; 250 | SENDER ; 251 | GET ; 252 | IF_NONE { PUSH int 96 ; FAILWITH } {} ; 253 | UPDATE ; 254 | UPDATE 6 ; 255 | UNPAIR ; 256 | SWAP ; 257 | UNPAIR ; 258 | UNPAIR ; 259 | SWAP ; 260 | NONE bytes ; 261 | SENDER ; 262 | UPDATE ; 263 | SWAP ; 264 | PAIR ; 265 | PAIR ; 266 | SWAP ; 267 | PAIR } 268 | { SWAP ; 269 | DUP ; 270 | DUG 2 ; 271 | CAR ; 272 | GET 4 ; 273 | SENDER ; 274 | COMPARE ; 275 | EQ ; 276 | IF 277 | {} 278 | { PUSH string "WrongCondition: sp.sender == self.data.manager" ; 279 | FAILWITH } ; 280 | SWAP ; 281 | UNPAIR ; 282 | UNPAIR ; 283 | SWAP ; 284 | CAR ; 285 | DIG 3 ; 286 | SWAP ; 287 | PAIR ; 288 | SWAP ; 289 | PAIR ; 290 | PAIR } } ; 291 | NIL operation ; 292 | PAIR } -------------------------------------------------------------------------------- /pytezos_tests/factory_test.py: -------------------------------------------------------------------------------- 1 | from pytezos import MichelsonRuntimeError 2 | from hic_base import HicBaseCase 3 | 4 | 5 | class FactoryTest(HicBaseCase): 6 | 7 | def _test_admin_change(self): 8 | 9 | # Trying to update admin from not admin address: 10 | with self.assertRaises(MichelsonRuntimeError) as cm: 11 | self._factory_update_admin(self.p2, self.tips) 12 | self.assertTrue('NOT_ADM' in str(cm.exception)) 13 | 14 | # Trying to accept admin rights before transfer: 15 | with self.assertRaises(MichelsonRuntimeError) as cm: 16 | self._factory_accept_ownership(self.tips) 17 | self.assertTrue('NOT_PROPOSED' in str(cm.exception)) 18 | 19 | # Trying to update admin admin address: 20 | self._factory_update_admin(self.admin, self.tips) 21 | 22 | # Checking that another address can't accept: 23 | with self.assertRaises(MichelsonRuntimeError) as cm: 24 | self._factory_accept_ownership(self.p2) 25 | self.assertTrue('NOT_PROPOSED' in str(cm.exception)) 26 | 27 | # Checking that proposed can accept: 28 | self._factory_accept_ownership(self.tips) 29 | 30 | 31 | def _test_no_tez_entrypoints(self): 32 | 33 | # Checking that entrypoints is not allow to send any tez: 34 | calls = [ 35 | lambda: self._factory_create_proxy(self.p1, self.originate_params, amount=100), 36 | lambda: self._factory_add_template(self.tips, amount=100), 37 | lambda: self._factory_remove_template(self.tips, amount=100), 38 | lambda: self._factory_is_originated_contract(amount=100), 39 | lambda: self._factory_update_admin(self.tips, self.p2, amount=100), 40 | lambda: self._factory_accept_ownership(self.p1, amount=100), 41 | lambda: self._factory_add_record(self.p1, amount=100), 42 | lambda: self._factory_remove_record(self.p1, amount=100) 43 | ] 44 | 45 | for call in calls: 46 | with self.assertRaises(MichelsonRuntimeError) as cm: 47 | call() 48 | self.assertTrue('AMNT_FRBD' in str(cm.exception)) 49 | 50 | 51 | def _test_no_admin_rights(self): 52 | """ Tests that call to all admin entrypoints failed for not admin user """ 53 | 54 | not_admin = self.p2 55 | admin_calls = [ 56 | lambda: self._factory_add_template(not_admin), 57 | lambda: self._factory_remove_template(not_admin), 58 | lambda: self._factory_update_admin(not_admin, self.p2), 59 | lambda: self._factory_add_record(not_admin), 60 | lambda: self._factory_remove_record(not_admin) 61 | ] 62 | 63 | for call in admin_calls: 64 | with self.assertRaises(MichelsonRuntimeError) as cm: 65 | call() 66 | self.assertTrue('NOT_ADM' in str(cm.exception)) 67 | 68 | 69 | def _test_records(self): 70 | # removing all records from factory: 71 | self.factory_storage['records'] = {} 72 | 73 | # no records added, trying to originate proxy 74 | # and failwith "Record is not found": 75 | with self.assertRaises(MichelsonRuntimeError) as cm: 76 | self._factory_create_proxy(self.p1, self.originate_params) 77 | self.assertTrue('RECORD_NF' in str(cm.exception)) 78 | 79 | # adding all records is succeeded: 80 | for name, address in self.addresses.items(): 81 | self._factory_add_record( 82 | self.admin, name, self._pack_address(address)) 83 | 84 | # contract origination is succeeded: 85 | self._factory_create_proxy(self.p1, self.originate_params) 86 | 87 | # checking contract addresses: 88 | collab_addresses = { 89 | 'hicMinterAddress': self.collab_storage['minterAddress'], 90 | 'hicMarketplaceAddress': self.collab_storage['marketplaceAddress'], 91 | 'hicTokenAddress': self.collab_storage['tokenAddress'], 92 | 'hicRegistryAddress': self.collab_storage['registryAddress'] 93 | } 94 | self.assertEqual(collab_addresses, self.addresses) 95 | 96 | # removing one of the records succeeded: 97 | self._factory_remove_record(self.admin, 'hicMinterAddress') 98 | 99 | # adding record with nat type instead of address is succeed: 100 | self._factory_add_record( 101 | self.admin, 'hicMinterAddress', self._pack_nat(42)) 102 | 103 | # contract origination failed: "UNPK_FAIL" 104 | with self.assertRaises(MichelsonRuntimeError) as cm: 105 | self._factory_create_proxy(self.p1, self.originate_params) 106 | self.assertTrue('UNPK_FAIL' in str(cm.exception)) 107 | 108 | # replacing record with this name with correct address: 109 | self._factory_add_record( 110 | self.admin, 111 | 'hicMinterAddress', 112 | self._pack_address(self.addresses['hicMinterAddress'])) 113 | 114 | # contract origination succeeded: 115 | self._factory_create_proxy(self.p1, self.originate_params) 116 | 117 | 118 | def test_factory(self): 119 | random_address = 'KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9' 120 | entrypoint = 'not_existed_entrypoint' 121 | 122 | # Testing records before any interactions: 123 | self._test_records() 124 | 125 | # Anyone can create collab 126 | self._factory_create_proxy(self.p2, self.originate_params) 127 | contract_address = list( 128 | self.factory_storage['originatedContracts'].keys())[0] 129 | 130 | # Checking view is contract originated, result should be True: 131 | result = self._factory_is_originated_contract( 132 | contract_address, callback=random_address, entrypoint=entrypoint) 133 | self.assertTrue(result) 134 | 135 | # Checking view for random contract, should be False: 136 | result = self._factory_is_originated_contract( 137 | random_address, callback=random_address, entrypoint=entrypoint) 138 | self.assertFalse(result) 139 | 140 | # Trying to add template from not admin address: 141 | with self.assertRaises(MichelsonRuntimeError) as cm: 142 | self._factory_add_template(self.p2) 143 | self.assertTrue('NOT_ADM' in str(cm.exception)) 144 | 145 | # Trying to add template from admin address: 146 | self._factory_add_template(self.admin) 147 | 148 | # Trying to remove template from not admin address: 149 | with self.assertRaises(MichelsonRuntimeError) as cm: 150 | self._factory_remove_template(self.p2) 151 | self.assertTrue('NOT_ADM' in str(cm.exception)) 152 | 153 | # Trying to remove template from admin address: 154 | self._factory_remove_template(self.admin) 155 | 156 | # Checking that removed template is not possible to use: 157 | with self.assertRaises(MichelsonRuntimeError) as cm: 158 | self._factory_create_proxy(self.p2, self.originate_params, contract='added_template') 159 | self.assertTrue('TEMPLATE_NF' in str(cm.exception)) 160 | 161 | # Running tests that in result changes admin to self.tips: 162 | self._test_admin_change() 163 | 164 | # Trying to add template new admin address: 165 | self._factory_add_template(self.tips) 166 | 167 | self._test_no_tez_entrypoints() 168 | self._test_no_admin_rights() 169 | -------------------------------------------------------------------------------- /pytezos_tests/hic_base.py: -------------------------------------------------------------------------------- 1 | from pytezos import ContractInterface, pytezos, MichelsonRuntimeError 2 | from unittest import TestCase 3 | from os.path import dirname, join 4 | import codecs 5 | from test_data import ( 6 | COLLAB_FN, FACTORY_FN, SIGN_FN, PACKER_FN, HIC_PROXY_CODE, 7 | LAMBDA_CALLS, load_lambdas, DEFAULT_PARAMS 8 | ) 9 | 10 | 11 | def str_to_hex_bytes(string): 12 | return codecs.encode(string.encode("ascii"), "hex") 13 | 14 | 15 | def filter_zero(dct): 16 | return {key: value for key, value in dct.items() if value > 0} 17 | 18 | 19 | def sum_dicts(dct_a, dct_b): 20 | keys = set([*dct_a, *dct_b]) 21 | return {key: dct_a.get(key, 0) + dct_b.get(key, 0) for key in keys} 22 | 23 | 24 | def split_amount(amount, shares): 25 | """ Splits amount according to the shares dict """ 26 | 27 | total_shares = sum(shares.values()) 28 | 29 | # calculating amounts, int always rounds down: 30 | amounts = { 31 | address: share * amount // total_shares 32 | for address, share in shares.items() 33 | } 34 | 35 | accumulated_amount = sum(amounts.values()) 36 | residuals = amount - accumulated_amount 37 | 38 | return amounts, residuals 39 | 40 | 41 | # TODO: consider split this one big base case into separate: 42 | # - one for collab 43 | # - one for factory 44 | # - one for signer 45 | # ? It would be good to make them independent, but I want to support all 46 | # this self.assert* methods, so I don't know how to make it good 47 | 48 | # MAYBE: all this _factory / _sign should go to the BaseCase, so it would 49 | # be possible to have different base cases for different collabs: 50 | # - HicBaseCase for HicProxy & Bigmap one, 51 | # - BasicBaseCase for BasicProxy 52 | # etc 53 | 54 | class HicBaseCase(TestCase): 55 | 56 | def setUp(self): 57 | self.collab = ContractInterface.from_file(join(dirname(__file__), COLLAB_FN)) 58 | self.factory = ContractInterface.from_file(join(dirname(__file__), FACTORY_FN)) 59 | self.sign = ContractInterface.from_file(join(dirname(__file__), SIGN_FN)) 60 | self.packer = ContractInterface.from_file(join(dirname(__file__), PACKER_FN)) 61 | 62 | self.hic_proxy_code = open(join(dirname(__file__), HIC_PROXY_CODE)).read() 63 | 64 | # Two core participants: 65 | self.p1 = 'tz1iQE8ijR5xVPffBUPFubwB9XQJuyD9qsoJ' 66 | self.p2 = 'tz1MdaJfWzP5pPx3gwPxfdLZTHW6js9havos' 67 | 68 | # One just benefactor: 69 | self.tips = 'tz1RS9GoEXakf9iyBmSaheLMcakFRtzBXpWE' 70 | 71 | self.admin = self.p1 72 | 73 | self.default_params = DEFAULT_PARAMS 74 | 75 | self.originate_params = { 76 | self.p1: {'share': 330, 'isCore': True}, 77 | self.p2: {'share': 500, 'isCore': True}, 78 | self.tips: {'share': 170, 'isCore': False} 79 | } 80 | 81 | self.swap_params = { 82 | 'objkt_amount': 1, 83 | 'objkt_id': 30000, 84 | 'xtz_per_objkt': 10_000_000, 85 | 'creator': self.p1, 86 | 'royalties': 100 87 | } 88 | 89 | self.addresses = { 90 | 'hicMinterAddress': 'KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9', 91 | 'hicMarketplaceAddress': 'KT1HbQepzV1nVGg8QVznG7z4RcHseD5kwqBn', 92 | 'hicRegistryAddress': 'KT1My1wDZHDGweCrJnQJi3wcFaS67iksirvj', 93 | 'hicTokenAddress': 'KT1RJ6PbjHpwc3M5rw5s2Nbmefwbuwbdxton' 94 | } 95 | 96 | self.factory_storage = { 97 | 'records': { 98 | name: self._pack_address(address) 99 | for name, address in self.addresses.items() 100 | }, 101 | 'templates': { 102 | 'hic_contract': self.hic_proxy_code 103 | }, 104 | 'originatedContracts': {}, 105 | 'administrator': self.admin, 106 | 'proposedAdministrator': None 107 | } 108 | 109 | self.sign_storage = {} 110 | 111 | self.result = None 112 | self.total_incomings = 0 113 | 114 | self.random_contract_address = 'KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9' 115 | self.lambdas = load_lambdas(LAMBDA_CALLS) 116 | 117 | # default execute_lambda call params: 118 | self.execute_params = self._prepare_lambda_params('mint_OBJKT') 119 | 120 | self.balances = {'proxy': 0} 121 | 122 | 123 | def _pack_address(self, address): 124 | return self.packer.pack_address(address).interpret().storage.hex() 125 | 126 | 127 | def _pack_nat(self, nat): 128 | return self.packer.pack_nat(nat).interpret().storage.hex() 129 | 130 | 131 | def _prepare_lambda_params(self, entrypoint_name): 132 | """ Returns dict with some default lambda params for given entrypoint 133 | """ 134 | 135 | packer_call = getattr(self.packer, f'pack_{entrypoint_name}') 136 | packed_bytes = packer_call( 137 | self.default_params[entrypoint_name]).interpret().storage.hex() 138 | 139 | execute_params = { 140 | 'lambda': self.lambdas[f'hic_{entrypoint_name}'], 141 | 'packedParams': packed_bytes 142 | } 143 | 144 | return execute_params 145 | 146 | 147 | def _factory_create_proxy(self, sender, params, contract='hic_contract', amount=0): 148 | 149 | # converting params to bytes: 150 | params_bytes = self.packer.pack_originate_hic_proxy( 151 | params).interpret().storage.hex() 152 | 153 | create_params = { 154 | 'templateName': contract, 155 | 'params': params_bytes 156 | } 157 | 158 | result = self.factory.create_proxy(create_params).interpret( 159 | storage=self.factory_storage, sender=sender, amount=amount) 160 | self.factory_storage = result.storage 161 | 162 | self.assertEqual(len(result.operations), 1) 163 | 164 | operation = result.operations[0] 165 | self.assertTrue(operation['kind'] == 'origination') 166 | self.collab.storage_from_micheline(operation['script']['storage']) 167 | self.collab_storage = self.collab.storage() 168 | self.assertTrue(self.collab.storage()['administrator'] == sender) 169 | 170 | 171 | def _factory_add_template(self, sender, template_name='added_template', amount=0): 172 | 173 | add_template_params = { 174 | 'name': template_name, 175 | 'originateFunc': self.hic_proxy_code 176 | } 177 | 178 | result = self.factory.add_template(add_template_params).interpret( 179 | storage=self.factory_storage, sender=sender, amount=amount) 180 | self.factory_storage = result.storage 181 | 182 | 183 | def _factory_remove_template(self, sender, template_name='added_template', amount=0): 184 | 185 | result = self.factory.remove_template(template_name).interpret( 186 | storage=self.factory_storage, sender=sender, amount=amount) 187 | self.factory_storage = result.storage 188 | 189 | 190 | def _factory_is_originated_contract( 191 | self, contract_address=None, callback=None, 192 | entrypoint='random_entry', amount=0): 193 | 194 | contract_address = contract_address or self.random_contract_address 195 | callback = callback or self.random_contract_address 196 | 197 | params = { 198 | 'contractAddress': contract_address, 199 | 'callback': callback + '%' + entrypoint 200 | } 201 | 202 | result = self._call_view_entrypoint( 203 | self.factory.is_originated_contract, 204 | params, 205 | self.factory_storage, 206 | callback, 207 | entrypoint, 208 | amount=amount) 209 | 210 | return result.operations[0]['parameters']['value']['prim'] == 'True' 211 | 212 | 213 | def _factory_update_admin(self, sender, proposed_admin, amount=0): 214 | 215 | result = self.factory.update_admin(proposed_admin).interpret( 216 | storage=self.factory_storage, sender=sender, amount=amount) 217 | self.factory_storage = result.storage 218 | 219 | self.assertEqual( 220 | self.factory_storage['proposedAdministrator'], 221 | proposed_admin) 222 | 223 | 224 | def _factory_accept_ownership(self, sender, amount=0): 225 | 226 | result = self.factory.accept_ownership().interpret( 227 | storage=self.factory_storage, sender=sender, amount=amount) 228 | self.factory_storage = result.storage 229 | 230 | self.assertEqual( 231 | self.factory_storage['administrator'], 232 | sender) 233 | 234 | 235 | def _factory_add_record( 236 | self, sender, name='without name', value=None, amount=0): 237 | 238 | value = self._pack_nat(42) if value is None else value 239 | params = dict(name=name, value=value) 240 | 241 | result = self.factory.add_record(params).interpret( 242 | storage=self.factory_storage, sender=sender, amount=amount) 243 | self.factory_storage = result.storage 244 | 245 | recorded_value = self.factory_storage['records'][name] 246 | self.assertEqual(recorded_value, bytes.fromhex(value)) 247 | 248 | 249 | def _factory_remove_record(self, sender, name='without name', amount=0): 250 | 251 | result = self.factory.remove_record(name).interpret( 252 | storage=self.factory_storage, sender=sender, amount=amount) 253 | 254 | self.assertEqual(result.storage['records'][name], None) 255 | result.storage['records'].pop(name) 256 | 257 | self.factory_storage = result.storage 258 | 259 | 260 | def _collab_mint(self, sender, amount=0): 261 | """ Testing that minting doesn't fail with default params """ 262 | 263 | self.result = self.collab.mint_OBJKT( 264 | self.default_params['mint_OBJKT']).interpret( 265 | storage=self.collab_storage, sender=sender, amount=amount) 266 | 267 | self.assertTrue(len(self.result.operations) == 1) 268 | operation = self.result.operations[0] 269 | 270 | self.assertEqual(operation['parameters']['entrypoint'], 'mint_OBJKT') 271 | op_bytes = operation['parameters']['value']['args'][1]['bytes'] 272 | 273 | metadata = self.default_params['mint_OBJKT']['metadata'] 274 | self.assertEqual(op_bytes, metadata) 275 | 276 | self.assertEqual(operation['destination'], 277 | self.addresses['hicMinterAddress']) 278 | self.assertEqual(operation['amount'], '0') 279 | 280 | 281 | def _collab_swap(self, sender, amount=0): 282 | """ Testing that swapping doesn't fail with default params """ 283 | 284 | self.result = self.collab.swap(self.swap_params).interpret( 285 | storage=self.collab_storage, sender=sender, amount=amount) 286 | 287 | assert len(self.result.operations) == 1 288 | assert self.result.operations[0]['parameters']['entrypoint'] == 'swap' 289 | self.assertEqual( 290 | self.result.operations[0]['destination'], 291 | self.collab_storage['marketplaceAddress'] 292 | ) 293 | 294 | 295 | def _collab_cancel_swap(self, sender, amount=0): 296 | """ Testing that cancel swap doesn't fail with default params """ 297 | 298 | some_swap_id = 42 299 | self.result = self.collab.cancel_swap(some_swap_id).interpret( 300 | storage=self.collab_storage, sender=sender, amount=amount) 301 | 302 | assert len(self.result.operations) == 1 303 | assert self.result.operations[0]['parameters']['entrypoint'] == 'cancel_swap' 304 | assert self.result.operations[0]['parameters']['value'] == {'int': '42'} 305 | self.assertEqual( 306 | self.result.operations[0]['destination'], 307 | self.collab_storage['marketplaceAddress'] 308 | ) 309 | 310 | 311 | def _collab_collect(self, sender, amount=0): 312 | """ Testing that collect doesn't fail with default params """ 313 | 314 | some_swap_id = 42 315 | self.result = self.collab.collect(some_swap_id).interpret( 316 | storage=self.collab_storage, sender=sender, amount=amount) 317 | 318 | assert len(self.result.operations) == 1 319 | assert self.result.operations[0]['parameters']['entrypoint'] == 'collect' 320 | assert self.result.operations[0]['parameters']['value']['int'] == '42' 321 | self.assertEqual( 322 | self.result.operations[0]['destination'], 323 | self.collab_storage['marketplaceAddress'] 324 | ) 325 | 326 | 327 | def _collab_registry(self, sender, amount=0): 328 | 329 | metadata = str_to_hex_bytes( 330 | 'ipfs://QmVJzbVtq1sc8Cj2ZJFJmSBZVWfLDvsL3asuimUBvMARiB') 331 | subjkt = str_to_hex_bytes('MEGA COLLABA') 332 | 333 | registry_params = { 334 | 'metadata': metadata, 335 | 'subjkt': subjkt 336 | } 337 | 338 | self.result = self.collab.registry(registry_params).interpret( 339 | storage=self.collab_storage, sender=sender, amount=amount) 340 | 341 | assert len(self.result.operations) == 1 342 | assert self.result.operations[0]['parameters']['entrypoint'] == 'registry' 343 | self.assertEqual( 344 | self.result.operations[0]['destination'], 345 | self.collab_storage['registryAddress'] 346 | ) 347 | 348 | 349 | def _collab_unregistry(self, sender, amount=0): 350 | 351 | self.result = self.collab.unregistry().interpret( 352 | storage=self.collab_storage, sender=sender, amount=amount) 353 | 354 | assert len(self.result.operations) == 1 355 | assert self.result.operations[0]['parameters']['entrypoint'] == 'unregistry' 356 | self.assertEqual( 357 | self.result.operations[0]['destination'], 358 | self.collab_storage['registryAddress'] 359 | ) 360 | 361 | 362 | def _collab_default(self, sender, amount): 363 | """ Testing that distribution in default call works properly """ 364 | 365 | self.result = self.collab.default().with_amount(amount).interpret( 366 | storage=self.collab_storage, sender=sender) 367 | 368 | ops = self.result.operations 369 | # Operations is collected in reversed order 370 | # Order means because one (last) operation takes dust: 371 | ops = list(reversed(ops)) 372 | 373 | shares = self.collab_storage['shares'] 374 | dist_amount = amount + self.collab_storage['residuals'] 375 | calc_amounts, residuals = split_amount(dist_amount, shares) 376 | self.assertEqual(residuals, self.result.storage['residuals']) 377 | 378 | undistributed = self.collab_storage['undistributed'] 379 | threshold = self.collab_storage['threshold'] 380 | calc_amounts = sum_dicts(calc_amounts, undistributed) 381 | 382 | new_undistributed = { 383 | address: 0 if amount >= threshold else amount 384 | for address, amount in calc_amounts.items() 385 | } 386 | 387 | calc_amounts = filter_zero({ 388 | address: 0 if amount < threshold else amount 389 | for address, amount in calc_amounts.items() 390 | }) 391 | 392 | # Checking that sum of the operations is correct and no dust is left 393 | ops_sum = sum(int(op['amount']) for op in ops) 394 | exp_sum = sum(calc_amounts.values()) 395 | self.assertEqual(ops_sum, exp_sum) 396 | self.assertEqual(len(ops), len(calc_amounts)) 397 | 398 | # Checking that each participant get amount he should receive: 399 | amounts = {op['destination']: int(op['amount']) for op in ops} 400 | 401 | self.assertEqual(calc_amounts, amounts) 402 | self.assertEqual(new_undistributed, self.result.storage['undistributed']) 403 | 404 | self.collab_storage = self.result.storage 405 | for address, amount in calc_amounts.items(): 406 | self.balances[address] = self.balances.get(address, 0) + amount 407 | self.balances['proxy'] -= amount 408 | 409 | 410 | def _call_view_entrypoint( 411 | self, entrypoint, params, storage, callback, 412 | entrypoint_name, amount=0): 413 | """ The returned operations from contract very similar for different 414 | entrypoints, so it require similar checks: """ 415 | 416 | # Works for factory & collabs both, this is why storage parameter 417 | # transfered. TODO: Maybe it would be better to use attrib name? 418 | 419 | self.result = entrypoint(params).interpret( 420 | storage=storage, sender=self.p1, amount=amount) 421 | 422 | assert len(self.result.operations) == 1 423 | op = self.result.operations[0] 424 | self.assertEqual(op['parameters']['entrypoint'], entrypoint_name) 425 | self.assertEqual(op['destination'], callback) 426 | 427 | return self.result 428 | 429 | 430 | def _collab_update_operators( 431 | self, sender, operator=None, token_id=0, amount=0): 432 | 433 | operator = operator or self.collab_storage['marketplaceAddress'] 434 | 435 | # self.collab.address is empty, so using random contract address instead: 436 | owner = self.random_contract_address 437 | 438 | update_operatiors_params = [{ 439 | 'add_operator': { 440 | 'owner': owner, 441 | 'operator': operator, 442 | 'token_id': token_id} 443 | }] 444 | 445 | result = (self.collab 446 | .update_operators(update_operatiors_params) 447 | .with_amount(amount) 448 | .interpret(storage=self.collab_storage, sender=sender)) 449 | 450 | self.collab_storage = result.storage 451 | 452 | self.assertEqual(len(result.operations), 1) 453 | op = result.operations[0] 454 | self.assertEqual( 455 | op['destination'], 456 | self.collab_storage['tokenAddress']) 457 | 458 | self.assertEqual(op['parameters']['entrypoint'], 'update_operators') 459 | 460 | 461 | def _collab_update_admin(self, sender, new_admin, amount=0): 462 | 463 | result = self.collab.update_admin(new_admin).interpret( 464 | storage=self.collab_storage, sender=sender, amount=amount) 465 | self.collab_storage = result.storage 466 | 467 | self.assertEqual( 468 | self.collab_storage['administrator'], 469 | new_admin) 470 | 471 | 472 | def _collab_execute(self, sender, params=None, amount=0): 473 | 474 | params = params or self.execute_params 475 | 476 | result = self.collab.execute(params).interpret( 477 | storage=self.collab_storage, sender=sender, amount=amount) 478 | self.collab_storage = result.storage 479 | 480 | return result 481 | 482 | 483 | def _sign_sign(self, sender, objkt_id=0, amount=0): 484 | 485 | result = self.sign.sign(objkt_id).interpret( 486 | storage=self.sign_storage, sender=sender, amount=amount) 487 | 488 | self.assertTrue((sender, objkt_id) in result.storage) 489 | self.sign_storage = result.storage 490 | 491 | 492 | def _sign_unsign(self, sender, objkt_id=0, amount=0): 493 | 494 | result = self.sign.unsign(objkt_id).interpret( 495 | storage=self.sign_storage, sender=sender, amount=amount) 496 | 497 | key = (sender, objkt_id) 498 | self.assertTrue(result.storage[key] is None) 499 | result.storage.pop(key) 500 | self.sign_storage = result.storage 501 | 502 | 503 | def _sign_is_signed( 504 | self, participant=None, objkt_id=0, callback=None, 505 | entrypoint='random_entry', amount=0): 506 | 507 | callback = callback or self.random_contract_address 508 | participant = participant or self.p1 509 | 510 | params = { 511 | 'participant': participant, 512 | 'id': objkt_id, 513 | 'callback': callback + '%' + entrypoint 514 | } 515 | 516 | result = self._call_view_entrypoint( 517 | self.sign.is_signed, 518 | params, 519 | self.sign_storage, 520 | callback, 521 | entrypoint, 522 | amount=amount) 523 | 524 | return result.operations[0]['parameters']['value']['prim'] == 'True' 525 | 526 | 527 | def _collab_get_administrator(self): 528 | call = self.collab.get_administrator() 529 | return call.onchain_view(storage=self.collab_storage) 530 | 531 | 532 | def _collab_get_shares(self): 533 | call = self.collab.get_shares() 534 | return call.onchain_view(storage=self.collab_storage) 535 | 536 | 537 | def _collab_get_total_shares(self): 538 | call = self.collab.get_total_shares() 539 | return call.onchain_view(storage=self.collab_storage) 540 | 541 | 542 | def _collab_get_core_participants(self): 543 | call = self.collab.get_core_participants() 544 | return call.onchain_view(storage=self.collab_storage) 545 | 546 | 547 | def _collab_get_total_received(self): 548 | call = self.collab.get_total_received() 549 | return call.onchain_view(storage=self.collab_storage) 550 | 551 | 552 | def _collab_set_threshold(self, sender=None, threshold=0, amount=0): 553 | sender = sender or self.admin 554 | result = self.collab.set_threshold(threshold).interpret( 555 | storage=self.collab_storage, 556 | sender=sender, 557 | amount=amount 558 | ) 559 | 560 | self.assertEqual(result.storage['threshold'], threshold) 561 | self.collab_storage = result.storage 562 | 563 | 564 | def _collab_withdraw(self, sender=None, recipient=None, amount=0): 565 | sender = sender or self.admin 566 | recipient = recipient or self.admin 567 | 568 | result = self.collab.withdraw(recipient).interpret( 569 | storage=self.collab_storage, 570 | sender=sender, 571 | amount=amount 572 | ) 573 | 574 | self.assertEqual(result.storage['undistributed'][recipient], 0) 575 | self.assertEqual(len(result.operations), 1) 576 | op = result.operations[0] 577 | origin_undistributed = self.collab_storage['undistributed'][recipient] 578 | withdrawn_amount = int(op['amount']) 579 | self.assertEqual(withdrawn_amount, origin_undistributed) 580 | self.assertEqual(op['destination'], recipient) 581 | 582 | self.collab_storage = result.storage 583 | 584 | self.balances['proxy'] -= origin_undistributed 585 | self.balances[recipient] = self.balances.get(recipient, 0) + origin_undistributed 586 | 587 | return withdrawn_amount 588 | 589 | -------------------------------------------------------------------------------- /pytezos_tests/hic_proxy_test.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | from pytezos import MichelsonRuntimeError, pytezos 3 | from hic_base import HicBaseCase 4 | from pytezos.crypto.key import Key 5 | 6 | 7 | def generate_key(): 8 | return pytezos.key.generate(export=False).public_key_hash() 9 | 10 | 11 | # TODO: should I split this test into separate ones? 12 | class MapInteractionTest(HicBaseCase): 13 | 14 | def _test_admin_change(self): 15 | """ This is copy of the tests that made for factory, I just changed 16 | entrypoints. Decided that it is better to copy than 17 | make it abstract """ 18 | 19 | # Trying to update admin from not admin address: 20 | with self.assertRaises(MichelsonRuntimeError) as cm: 21 | self._collab_update_admin(self.p2, self.tips) 22 | self.assertTrue('NOT_ADMIN' in str(cm.exception)) 23 | 24 | # Trying to update admin admin address: 25 | self._collab_update_admin(self.admin, self.tips) 26 | 27 | 28 | def _test_no_tez_entrypoints(self): 29 | # Checking that entrypoints is not allow to send any tez: 30 | calls = [ 31 | lambda: self._collab_mint(self.admin, amount=100), 32 | lambda: self._collab_swap(self.admin, amount=100), 33 | lambda: self._collab_cancel_swap(self.admin, amount=100), 34 | lambda: self._collab_collect(self.admin, amount=100), 35 | lambda: self._collab_registry(self.admin, amount=100), 36 | lambda: self._collab_unregistry(self.admin, amount=100), 37 | lambda: self._collab_update_operators(self.admin, amount=100), 38 | lambda: self._collab_update_admin(self.admin, self.p2, amount=100), 39 | lambda: self._collab_set_threshold(self.admin, amount=100), 40 | lambda: self._collab_withdraw(self.admin, amount=100), 41 | ] 42 | 43 | for call in calls: 44 | with self.assertRaises(MichelsonRuntimeError) as cm: 45 | call() 46 | self.assertTrue('AMNT_FRBD' in str(cm.exception)) 47 | 48 | 49 | def _test_no_admin_rights(self): 50 | """ Tests that call to all admin entrypoints failed for not admin user """ 51 | 52 | not_admin = self.p2 53 | admin_calls = [ 54 | lambda: self._collab_mint(not_admin), 55 | lambda: self._collab_swap(not_admin), 56 | lambda: self._collab_cancel_swap(not_admin), 57 | lambda: self._collab_collect(not_admin), 58 | lambda: self._collab_registry(not_admin), 59 | lambda: self._collab_unregistry(not_admin), 60 | lambda: self._collab_update_operators(not_admin), 61 | lambda: self._collab_update_admin(not_admin, self.tips), 62 | lambda: self._collab_execute(not_admin), 63 | lambda: self._collab_set_threshold(not_admin), 64 | ] 65 | 66 | for call in admin_calls: 67 | with self.assertRaises(MichelsonRuntimeError) as cm: 68 | call() 69 | self.assertTrue('NOT_ADMIN' in str(cm.exception)) 70 | 71 | 72 | def _test_lambdas(self): 73 | """ Testing that lambda calls emits transactions that goes to right 74 | direction 75 | """ 76 | 77 | destinations = { 78 | 'mint_OBJKT': self.collab.storage['minterAddress']() 79 | } 80 | 81 | for entrypoint_name, destination in destinations.items(): 82 | execute_params = self._prepare_lambda_params(entrypoint_name) 83 | result = self._collab_execute(self.admin, params=execute_params) 84 | self.assertTrue(len(result.operations) == 1) 85 | op = result.operations[0] 86 | self.assertTrue(op['destination'] == destination) 87 | self.assertTrue(op['parameters']['entrypoint'] == entrypoint_name) 88 | 89 | 90 | def test_interactions(self): 91 | 92 | # Factory test: 93 | self._factory_create_proxy(self.admin, self.originate_params) 94 | 95 | # Checking that not admin fails to run admin entrypoints: 96 | self._test_no_admin_rights() 97 | 98 | # Test mint call from admin succeed: 99 | self._collab_mint(self.admin) 100 | 101 | # Test mint call without admin role failed: 102 | with self.assertRaises(MichelsonRuntimeError) as cm: 103 | self._collab_mint(self.p2) 104 | self.assertTrue('NOT_ADMIN' in str(cm.exception)) 105 | 106 | # Test swap call from admin succeed: 107 | self._collab_swap(self.admin) 108 | 109 | # TODO: remove duplicated tests: 110 | # Testing that calling swap from non-administrator address is not possible: 111 | with self.assertRaises(MichelsonRuntimeError) as cm: 112 | self._collab_swap(self.p2) 113 | self.assertTrue('NOT_ADMIN' in str(cm.exception)) 114 | 115 | # Test cancel swap call from admin succeed: 116 | self._collab_cancel_swap(self.admin) 117 | 118 | # Testing that calling cancel swap from non-administrator address is not possible: 119 | with self.assertRaises(MichelsonRuntimeError) as cm: 120 | self._collab_cancel_swap(self.tips) 121 | self.assertTrue('NOT_ADMIN' in str(cm.exception)) 122 | 123 | # Test collect call from admin succeed: 124 | self._collab_collect(self.admin) 125 | 126 | # Testing that calling collect from non-administrator address is not possible: 127 | with self.assertRaises(MichelsonRuntimeError) as cm: 128 | self._collab_collect(self.tips) 129 | self.assertTrue('NOT_ADMIN' in str(cm.exception)) 130 | 131 | # Default entrypoint tests with value that can be easy splitted: 132 | self._collab_default(self.tips, 1000) 133 | 134 | # Default entrypoint tests with value that hard to split equally: 135 | self._collab_default(self.tips, 337) 136 | 137 | # Default entrypoint tests with value that very hard to split: 138 | self._collab_default(self.tips, 1) 139 | 140 | # Default entrypoint tests with very big value (100 bln tez): 141 | self._collab_default(self.tips, 10**17) 142 | 143 | # Collab with very big share sums (10**12 is hard limit): 144 | crazy_params = { 145 | self.p1: {'share': 10**11, 'isCore': True}, 146 | self.p2: {'share': 10**11, 'isCore': True}, 147 | self.tips: {'share': 10**11, 'isCore': True} 148 | } 149 | 150 | self._factory_create_proxy(self.p2, crazy_params) 151 | self._collab_default(self.tips, 10**17) 152 | 153 | # Mint from admin address (now admin is p2): 154 | self._collab_mint(self.p2) 155 | with self.assertRaises(MichelsonRuntimeError) as cm: 156 | self._collab_mint(self.admin) 157 | self.assertTrue('NOT_ADMIN' in str(cm.exception)) 158 | 159 | # Collab with 1 participant can be created with only 1 share, 160 | # and we allow to have participants with 0 share (why not?): 161 | single = { 162 | self.p1: {'share': 1, 'isCore': True}, 163 | self.p2: {'share': 0, 'isCore': True}, 164 | } 165 | self._factory_create_proxy(self.admin, single) 166 | self._collab_default(self.tips, 1000) 167 | 168 | # Collab can't be created with only 0 shares: 169 | with self.assertRaises(MichelsonRuntimeError) as cm: 170 | twin = { 171 | self.p1: {'share': 0, 'isCore': True}, 172 | self.p2: {'share': 0, 'isCore': True}, 173 | } 174 | self._factory_create_proxy(self.admin, twin) 175 | msg = 'Sum of the shares should be more than 0n' 176 | self.assertTrue(msg in str(cm.exception)) 177 | 178 | self._test_no_tez_entrypoints() 179 | 180 | # Running update operatiors from admin check: 181 | self._collab_update_operators(self.admin) 182 | 183 | # Running tests that in result changes admin to self.tips: 184 | self._test_admin_change() 185 | 186 | # checking that self.tips can mint now: 187 | self._collab_mint(self.tips) 188 | 189 | # returning admin back: 190 | self._collab_update_admin(self.tips, self.admin) 191 | 192 | # running lambda testing: 193 | self._test_lambdas() 194 | 195 | 196 | def test_zero_core_participants_one(self): 197 | """ Zero core participants should be possible """ 198 | 199 | originate_params = { 200 | self.p1: {'share': 330, 'isCore': False}, 201 | self.p2: {'share': 500, 'isCore': False}, 202 | self.tips: {'share': 170, 'isCore': False} 203 | } 204 | 205 | self._factory_create_proxy(self.p2, originate_params) 206 | 207 | 208 | def test_zero_core_participants_two(self): 209 | """ Zero core participants should be possible """ 210 | 211 | originate_params = { 212 | self.tips: {'share': 170, 'isCore': False} 213 | } 214 | 215 | self._factory_create_proxy(self.p2, originate_params) 216 | 217 | 218 | def test_no_participants(self): 219 | """ No participants should not be allowed """ 220 | 221 | originate_params = {} 222 | 223 | with self.assertRaises(MichelsonRuntimeError) as cm: 224 | self._factory_create_proxy(self.p2, originate_params) 225 | # There are no special msg, 0 shares should be failwithed 226 | 227 | 228 | def test_too_many_participants(self): 229 | """ More than 108 participants is not allowed """ 230 | 231 | COUNT = 109 232 | 233 | originate_params = { 234 | Key.generate(export=False).public_key_hash(): { 235 | 'share': 170, 236 | 'isCore': False 237 | } for _ in range(COUNT) 238 | } 239 | 240 | with self.assertRaises(MichelsonRuntimeError) as cm: 241 | self._factory_create_proxy(self.p2, originate_params) 242 | msg = 'EXCEED_MAX_PARTICIPANTS' 243 | self.assertTrue(msg in str(cm.exception)) 244 | 245 | 246 | def test_too_many_shares(self): 247 | """ More than 10**12 shares is not allowed """ 248 | 249 | originate_params = { 250 | self.p1: {'share': 10**12, 'isCore': False}, 251 | self.p2: {'share': 10**12, 'isCore': False}, 252 | } 253 | 254 | with self.assertRaises(MichelsonRuntimeError) as cm: 255 | self._factory_create_proxy(self.p2, originate_params) 256 | msg = 'EXCEED_MAX_SHARES' 257 | self.assertTrue(msg in str(cm.exception)) 258 | 259 | 260 | def test_hangzhou_views(self): 261 | shares = {self.p1: 420, self.p2: 69} 262 | core_participants = [self.p1] 263 | administrator = self.p2 264 | 265 | originate_params = { 266 | address: {'share': share, 'isCore': address in core_participants} 267 | for address, share in shares.items() 268 | } 269 | 270 | self._factory_create_proxy(administrator, originate_params) 271 | 272 | self.assertEqual(administrator, self._collab_get_administrator()) 273 | self.assertEqual(shares, self._collab_get_shares()) 274 | self.assertEqual(core_participants, self._collab_get_core_participants()) 275 | self.assertEqual(sum(shares.values()), self._collab_get_total_shares()) 276 | 277 | self.assertEqual(0, self._collab_get_total_received()) 278 | self._collab_default(self.p1, 108) 279 | self.assertEqual(108, self._collab_get_total_received()) 280 | self._collab_default(self.p1, 42) 281 | self.assertEqual(150, self._collab_get_total_received()) 282 | 283 | 284 | def test_should_redistribute_with_given_threshold(self): 285 | def calc_undistributed_sum(): 286 | return sum(self.collab_storage['undistributed'].values()) 287 | 288 | originate_params = { 289 | self.p1: {'share': 1, 'isCore': True}, 290 | self.p2: {'share': 1, 'isCore': True}, 291 | } 292 | 293 | self._factory_create_proxy(self.admin, originate_params) 294 | 295 | # setting threshold for 1xtz: 296 | self._collab_set_threshold(self.admin, threshold=1_000_000) 297 | 298 | # first time it should not be distributed because 0.5xtz < 1xtz 299 | self._collab_default(self.p1, amount=1_000_000) 300 | self.assertEqual(calc_undistributed_sum(), 1_000_000) 301 | 302 | # second time it should redistribute all funds 303 | self._collab_default(self.p1, amount=1_000_000) 304 | self.assertEqual(calc_undistributed_sum(), 0) 305 | 306 | # third time with the same threshold should be enough too: 307 | self._collab_default(self.p1, amount=2_000_000) 308 | self.assertEqual(calc_undistributed_sum(), 0) 309 | 310 | # the same amount with new threshold shold not be distributed: 311 | self._collab_set_threshold(self.admin, threshold=3_000_000) 312 | self._collab_default(self.p1, amount=2_000_000) 313 | self.assertEqual(calc_undistributed_sum(), 2_000_000) 314 | 315 | self._collab_default(self.p1, amount=2_000_000) 316 | self.assertEqual(calc_undistributed_sum(), 4_000_000) 317 | 318 | # checking that withdraw works: 319 | amount = self._collab_withdraw(self.p1, recipient=self.p2) 320 | self.assertEqual(amount, 2_000_000) 321 | self.assertEqual(calc_undistributed_sum(), 2_000_000) 322 | 323 | # withdraw second time should fail: 324 | amount = self._collab_withdraw(self.p1, recipient=self.p2) 325 | self.assertEqual(amount, 0) 326 | 327 | # and then auto-distribution should work: 328 | self._collab_default(self.p1, amount=10_000_000) 329 | self.assertEqual(calc_undistributed_sum(), 0) 330 | 331 | 332 | def test_should_aggregate_residuals_and_then_redistribute_them(self): 333 | 334 | originate_params = { 335 | generate_key(): {'share': 1, 'isCore': True} 336 | for _ in range(42) 337 | } 338 | 339 | self._factory_create_proxy(self.admin, originate_params) 340 | 341 | # in the following case 42 should be distributed and 41 will kept as 342 | # residuals: 343 | self._collab_default(self.p1, amount=83) 344 | self.assertEqual(self.collab_storage['residuals'], 41) 345 | 346 | # then only 1 mutez required to redistribute all: 347 | self._collab_default(self.p1, amount=1) 348 | self.assertEqual(self.collab_storage['residuals'], 0) 349 | 350 | # checking scenario with accumulation: 351 | self._collab_default(self.p1, amount=12) 352 | self.assertEqual(self.collab_storage['residuals'], 12) 353 | self._collab_default(self.p1, amount=12) 354 | self.assertEqual(self.collab_storage['residuals'], 24) 355 | self._collab_default(self.p1, amount=12) 356 | self.assertEqual(self.collab_storage['residuals'], 36) 357 | 358 | # checking overflow: 359 | self._collab_default(self.p1, amount=12) 360 | self.assertEqual(self.collab_storage['residuals'], 48-42) 361 | 362 | # increasing threshold: 363 | self._collab_set_threshold(self.admin, 100) 364 | self._collab_default(self.p1, amount=50) 365 | self.assertEqual(self.collab_storage['residuals'], 6+8) 366 | amounts = [a for a in self.collab_storage['undistributed'].values()] 367 | self.assertTrue(all(amount == 1 for amount in amounts)) 368 | 369 | self._collab_default(self.p1, amount=50) 370 | self.assertEqual(self.collab_storage['residuals'], 6+8+8) 371 | amounts = [a for a in self.collab_storage['undistributed'].values()] 372 | self.assertTrue(all(amount == 2 for amount in amounts)) 373 | 374 | 375 | def test_wrong_share_configuration_lead_to_default_failwith(self): 376 | # making wrong collab just to test this case: 377 | originate_params = { 378 | self.p1: {'share': 10, 'isCore': True}, 379 | self.p2: {'share': 10, 'isCore': True}, 380 | } 381 | 382 | self._factory_create_proxy(self.admin, originate_params) 383 | self.collab_storage['totalShares'] = 18 384 | 385 | with self.assertRaises(MichelsonRuntimeError) as cm: 386 | self._collab_default(self.p2, amount=100) 387 | msg = 'WR_SHARES' 388 | self.assertTrue(msg in str(cm.exception)) 389 | 390 | 391 | def test_random_interactions(self): 392 | ITERATIONS = 3 393 | 394 | for iteration in range(ITERATIONS): 395 | total_sum = 0 396 | 397 | shares = { 398 | generate_key(): {'share': randint(1, 100), 'isCore': True} 399 | for _ in range(randint(2, 5)) 400 | } 401 | 402 | self._factory_create_proxy(self.admin, shares) 403 | self._collab_set_threshold(self.admin, randint(0, 100)) 404 | 405 | for call in range(randint(2, 5)): 406 | new_sum = randint(0, 100) 407 | self._collab_default(self.p1, amount=new_sum) 408 | total_sum += new_sum 409 | self.assertTrue(self.collab_storage['residuals'] < len(shares)) 410 | 411 | balances = sum(self.balances.get(addr, 0) for addr in shares) 412 | undistributed = sum( 413 | self.collab_storage['undistributed'][addr] for addr in shares) 414 | residuals = self.collab_storage['residuals'] 415 | self.assertEqual(total_sum, balances+undistributed+residuals) 416 | 417 | 418 | def test_should_not_allow_to_withdraw_for_not_shareholder(self): 419 | """ This withdrawals can be used to spam attack contract and increase 420 | undistributed map size """ 421 | 422 | originate_params = { 423 | self.p1: {'share': 1, 'isCore': True}, 424 | self.p2: {'share': 1, 'isCore': True} 425 | } 426 | 427 | self._factory_create_proxy(self.admin, originate_params) 428 | self.collab_storage['undistributed'] = {} 429 | 430 | with self.assertRaises(MichelsonRuntimeError) as cm: 431 | self._collab_withdraw(recipient=self.tips) 432 | msg = 'WR_ADDR' 433 | self.assertTrue(msg in str(cm.exception)) 434 | 435 | -------------------------------------------------------------------------------- /pytezos_tests/requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==20.3.0 2 | base58==1.0.3 3 | bravado==11.0.3 4 | bravado-core==5.17.0 5 | bson==0.5.10 6 | cached-property==1.5.2 7 | cattrs==1.4.0 8 | cattrs-extras==0.1.1 9 | certifi==2020.12.5 10 | cffi==1.14.5 11 | chardet==4.0.0 12 | click==7.1.2 13 | cytoolz==0.11.0 14 | dateutils==0.6.12 15 | deprecation==2.1.0 16 | docker==5.0.0 17 | eth-hash==0.3.1 18 | eth-typing==2.2.2 19 | eth-utils==1.10.0 20 | fastecdsa==1.7.5 21 | idna==2.10 22 | iniconfig==1.1.1 23 | jsonpointer==2.1 24 | jsonref==0.2 25 | jsonschema==3.2.0 26 | loguru==0.5.3 27 | mnemonic==0.19 28 | monotonic==1.6 29 | msgpack==1.0.2 30 | mypy-extensions==0.4.3 31 | netstruct==1.1.2 32 | packaging==20.9 33 | pendulum==2.1.2 34 | pluggy==0.13.1 35 | ply==3.11 36 | py==1.10.0 37 | py-ecc==5.2.0 38 | pyblake2==1.1.2 39 | pycparser==2.20 40 | pyparsing==2.4.7 41 | pyrsistent==0.17.3 42 | pysha3==1.0.2 43 | pysodium==0.7.7 44 | pytest==6.2.3 45 | pytezos==3.1.0 46 | python-dateutil==2.8.1 47 | pytimeparse==1.1.8 48 | pytz==2021.1 49 | pytzdata==2020.1 50 | PyYAML==5.4.1 51 | requests==2.25.1 52 | rfc3987==1.3.8 53 | secp256k1==0.13.2 54 | simplejson==3.17.2 55 | six==1.15.0 56 | strict-rfc3339==0.7 57 | swagger-spec-validator==2.7.3 58 | testcontainers==3.4.0 59 | toml==0.10.2 60 | toolz==0.11.1 61 | tqdm==4.60.0 62 | typing-extensions==3.7.4.3 63 | typing-inspect==0.6.0 64 | urllib3==1.26.4 65 | webcolors==1.11.1 66 | websocket-client==0.58.0 67 | wrapt==1.12.1 68 | -------------------------------------------------------------------------------- /pytezos_tests/sandbox_base.py: -------------------------------------------------------------------------------- 1 | from pytezos.sandbox.node import SandboxedNodeTestCase 2 | from pytezos.sandbox.parameters import sandbox_addresses, sandbox_commitment 3 | from pytezos import ContractInterface, pytezos, MichelsonRuntimeError 4 | from pytezos.contract.result import ContractCallResult 5 | from pytezos.rpc.errors import MichelsonError 6 | import unittest 7 | from os.path import dirname, join 8 | import json 9 | from test_data import CONTRACTS_DIR, FACTORY_FN, SWAP_ADMIN_FN 10 | 11 | 12 | def pkh(client): 13 | return client.key.public_key_hash() 14 | 15 | 16 | def read_contract(name): 17 | """ Loads contract from CONTRACTS_DIR with name {name}.tz """ 18 | 19 | filename = join(dirname(__file__), CONTRACTS_DIR, f'{name}.tz') 20 | return ContractInterface.from_file(filename) 21 | 22 | 23 | def read_storage(name): 24 | """ Loads storage from CONTRACTS_DIR with name {name}.json """ 25 | 26 | filename = join(dirname(__file__), CONTRACTS_DIR, f'{name}.json') 27 | with open(filename, 'r') as f: 28 | return json.loads(f.read()) 29 | 30 | 31 | class ContractInteractionsTestCase(SandboxedNodeTestCase): 32 | 33 | 34 | def _deploy_contract(self, client, contract, storage): 35 | """ Deploys contract with given storage """ 36 | 37 | opg = contract.using(shell=client.shell, key=client.key) 38 | opg = opg.originate(initial_storage=storage) 39 | 40 | return opg.send() 41 | 42 | 43 | def _deploy_factory(self, client): 44 | 45 | factory = ContractInterface.from_file(join(dirname(__file__), FACTORY_FN)) 46 | factory_init = { 47 | 'records': {}, 48 | 'templates': {}, 49 | 'originatedContracts': {}, 50 | 'administrator': pkh(client), 51 | 'proposedAdministrator': None 52 | } 53 | 54 | return self._deploy_contract(client, factory, factory_init) 55 | 56 | 57 | def _deploy_swap_admin( 58 | self, client, gallery_address, token_address, marketplace_address): 59 | 60 | swap_admin = ContractInterface.from_file(join(dirname(__file__), SWAP_ADMIN_FN)) 61 | swap_admin_init = { 62 | 'administrator': pkh(client), 63 | 'galleryAddress': gallery_address, 64 | 'tokenAddress': token_address, 65 | 'marketplaceAddress': marketplace_address 66 | } 67 | 68 | return self._deploy_contract(client, swap_admin, swap_admin_init) 69 | 70 | 71 | def _find_call_result_by_hash(self, client, opg_hash): 72 | 73 | # Get injected operation and convert to ContractCallResult 74 | opg = client.shell.blocks['head':].find_operation(opg_hash) 75 | return ContractCallResult.from_operation_group(opg)[0] 76 | 77 | 78 | def _load_contract(self, client, contract_address): 79 | 80 | # Load originated contract from blockchain 81 | contract = client.contract(contract_address) 82 | contract = contract.using( 83 | shell=self.client, 84 | key='bootstrap1') 85 | return contract 86 | 87 | 88 | def _find_contract_by_hash(self, client, opg_hash): 89 | """ Returns contract that was originated with opg_hash """ 90 | 91 | op = client.shell.blocks['head':].find_operation(opg_hash) 92 | op_result = op['contents'][0]['metadata']['operation_result'] 93 | address = op_result['originated_contracts'][0] 94 | 95 | return self._load_contract(client, address) 96 | 97 | 98 | def _find_contract_internal_by_hash(self, client, opg_hash): 99 | """ Returns collab that was originated with opg_hash """ 100 | 101 | op = client.shell.blocks['head':].find_operation(opg_hash) 102 | int_op = op['contents'][0]['metadata']['internal_operation_results'] 103 | address = int_op[0]['result']['originated_contracts'][0] 104 | 105 | return self._load_contract(client, address) 106 | 107 | 108 | def _activate_accs(self): 109 | self.p1 = self.client.using(key='bootstrap1') 110 | self.p1.reveal() 111 | 112 | self.p2 = self.client.using(key='bootstrap2') 113 | self.p2.reveal() 114 | 115 | self.tips = self.client.using(key='bootstrap3') 116 | self.tips.reveal() 117 | 118 | # Using two names for the same key: 119 | self.hic_admin = self.client.using(key='bootstrap4') 120 | self.admin = self.client.using(key='bootstrap4') 121 | self.hic_admin.reveal() 122 | 123 | self.buyer = self.client.using(key='bootstrap5') 124 | self.buyer.reveal() 125 | 126 | -------------------------------------------------------------------------------- /pytezos_tests/sign_test.py: -------------------------------------------------------------------------------- 1 | from pytezos import MichelsonRuntimeError 2 | from hic_base import HicBaseCase 3 | 4 | 5 | class SignTest(HicBaseCase): 6 | 7 | def _test_no_tez_entrypoints(self): 8 | 9 | # Checking that entrypoints is not allow to send any tez: 10 | calls = [ 11 | lambda: self._sign_sign(self.admin, amount=100), 12 | lambda: self._sign_unsign(self.admin, amount=100), 13 | lambda: self._sign_is_signed(self.admin, amount=100), 14 | ] 15 | 16 | for call in calls: 17 | with self.assertRaises(MichelsonRuntimeError) as cm: 18 | call() 19 | self.assertTrue('AMNT_FRBD' in str(cm.exception)) 20 | 21 | 22 | def test_signing(self): 23 | 24 | # Checking no-tez entrypoints: 25 | self._test_no_tez_entrypoints() 26 | 27 | # Check that sign succeed for any artist for any work: 28 | self.result = self._sign_sign(self.p1, 42) 29 | 30 | # This is very strange, but pytezos failed inside 31 | # _sign_is_signed with MichelsonRuntimeError "keys are unsorted" 32 | # if id very big (32000 for example) 33 | self.result = self._sign_sign(self.tips, 32) 34 | 35 | # Checking is_sign_view for signed works, True case: 36 | is_signed = self._sign_is_signed(participant=self.p1, objkt_id=42) 37 | self.assertTrue(is_signed) 38 | 39 | # False case 40 | is_signed = self._sign_is_signed(participant=self.p1, objkt_id=43) 41 | self.assertFalse(is_signed) 42 | 43 | # False case 2: 44 | is_signed = self._sign_is_signed(participant=self.tips, objkt_id=42) 45 | self.assertFalse(is_signed) 46 | 47 | # Another work from False case is became True case: 48 | self.result = self._sign_sign(self.p1, 43) 49 | is_signed = self._sign_is_signed(participant=self.p1, objkt_id=43) 50 | self.assertTrue(is_signed) 51 | 52 | # Unsign test: 53 | self.result = self._sign_unsign(self.p1, 43) 54 | 55 | # And again False case: 56 | is_signed = self._sign_is_signed(participant=self.p1, objkt_id=43) 57 | self.assertFalse(is_signed) 58 | -------------------------------------------------------------------------------- /pytezos_tests/test_data.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname, join 2 | 3 | 4 | def load_lambda(filename): 5 | return open(join(dirname(__file__), filename)).read() 6 | 7 | def load_lambdas(lambda_calls): 8 | return { 9 | lambda_name: load_lambda(filename) 10 | for lambda_name, filename in lambda_calls.items() 11 | } 12 | 13 | 14 | LAMBDA_CALLS = { 15 | 'hic_mint_OBJKT': '../build/tz/lambdas/call/hic_mint_OBJKT.tz', 16 | 'marketplace_v3_swap': '../build/tz/lambdas/call/teia_marketplace_swap.tz' 17 | } 18 | 19 | LAMBDA_ORIGINATE = { 20 | 'hic_proxy': '../build/tz/lambdas/originate/hic_proxy.tz', 21 | 'basic_proxy': '../build/tz/lambdas/originate/basic_proxy.tz' 22 | } 23 | 24 | COLLAB_FN = '../build/tz/hic_proxy.tz' 25 | FACTORY_FN = '../build/tz/factory.tz' 26 | SIGN_FN = '../build/tz/sign.tz' 27 | PACKER_FN = '../build/tz/packer.tz' 28 | MOCK_FN = '../build/tz/mock_view.tz' 29 | SWAP_ADMIN_FN = '../build/tz/swap_admin.tz' 30 | 31 | HIC_PROXY_CODE = '../build/tz/lambdas/originate/hic_proxy.tz' 32 | 33 | CONTRACTS_DIR = 'contracts' 34 | 35 | 36 | DEFAULT_PARAMS = { 37 | 'mint_OBJKT': { 38 | 'address': 'KT1VRdyXdMb452GRnSz7tPFQVg96bq2XAmSN', 39 | 'amount': 1, 40 | 'metadata': '697066733a2f2f516d5952724264554578587269473470526679746e666d596b664a4564417157793632683746327771346b517775', 41 | 'royalties': 250 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /scripts/deploy_test_collab.py: -------------------------------------------------------------------------------- 1 | from pytezos import pytezos, ContractInterface 2 | from random import randint 3 | 4 | COLLAB_TZ = 'build/tz/hic_proxy.tz' 5 | PARTICIPANTS = 100 6 | 7 | 8 | def generate_key(): 9 | return pytezos.key.generate(export=False).public_key_hash() 10 | 11 | 12 | def deploy(): 13 | """ Deploys collab contract with two participants """ 14 | 15 | client = pytezos.using(key='ithacanet.json') 16 | shares = {generate_key(): randint(1, 1000) for _ in range(PARTICIPANTS)} 17 | 18 | storage = { 19 | 'administrator': client.key.public_key_hash(), 20 | 'shares': shares, 21 | 'totalShares': sum(shares.values()), 22 | 'minterAddress': 'KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9', 23 | 'marketplaceAddress': 'KT1HbQepzV1nVGg8QVznG7z4RcHseD5kwqBn', 24 | 'tokenAddress': 'KT1RJ6PbjHpwc3M5rw5s2Nbmefwbuwbdxton', 25 | 'registryAddress': 'KT1My1wDZHDGweCrJnQJi3wcFaS67iksirvj', 26 | 'coreParticipants': {'tz1PjsbveFyzpTcJ6KkXEjNR1CYr55BGboQv'}, 27 | 'isPaused': False, 28 | 'totalReceived': 0, 29 | 'threshold': 0, 30 | 'undistributed': {address: 0 for address in shares}, 31 | 'residuals': 0 32 | } 33 | 34 | contract = ContractInterface.from_file(COLLAB_TZ).using( 35 | shell=client.shell, 36 | key=client.key 37 | ) 38 | 39 | opg = contract.originate(initial_storage=storage).send() 40 | print(f'success: {opg.hash()}') 41 | client.wait(opg) 42 | 43 | 44 | if __name__ == '__main__': 45 | deploy() 46 | 47 | --------------------------------------------------------------------------------