diff --git a/packages/minter-contracts/bin/bonding_curve.tz b/packages/minter-contracts/bin/bonding_curve.tz new file mode 100644 index 000000000..5bdecfa93 --- /dev/null +++ b/packages/minter-contracts/bin/bonding_curve.tz @@ -0,0 +1,546 @@ +{ parameter + (or (or (or (or %admin (or (unit %confirm_admin) (bool %pause)) (address %set_admin)) + (unit %buy)) + (or (address %buy_offchain) (unit %default))) + (or (or (nat %sell) (pair %sell_offchain nat address)) + (or (option %set_delegate key_hash) (unit %withdraw)))) ; + storage + (pair (pair %admin (pair (address %admin) (bool %paused)) (option %pending_admin address)) + (pair (address %market_contract) + (pair (mutez %auction_price) + (pair (nat %token_index) + (pair (map %token_metadata string bytes) + (pair (nat %basis_points) (pair (lambda %cost_mutez nat mutez) (mutez %unclaimed)))))))) ; + code { LAMBDA + (pair (pair address bool) (option address)) + unit + { CAR ; + CAR ; + SENDER ; + COMPARE ; + NEQ ; + IF { PUSH string "NOT_AN_ADMIN" ; FAILWITH } { UNIT } } ; + LAMBDA + (pair address + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez (pair nat (pair (map string bytes) (pair nat (pair (lambda nat mutez) mutez)))))))) + (pair (list operation) + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez (pair nat (pair (map string bytes) (pair nat (pair (lambda nat mutez) mutez)))))))) + { UNPAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CAR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + SWAP ; + EXEC ; + DUP 3 ; + CDR ; + CDR ; + CAR ; + ADD ; + DUP ; + AMOUNT ; + COMPARE ; + NEQ ; + IF { SWAP ; + DROP ; + SWAP ; + DROP ; + AMOUNT ; + PUSH string "WRONG_TEZ_PRICE" ; + PAIR ; + PAIR ; + FAILWITH } + { DROP ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + CONTRACT %mint + (list (pair (pair %token_metadata (nat %token_id) (map %token_info string bytes)) + (address %owner))) ; + IF_NONE + { DROP ; PUSH string "NO_MINT" ; FAILWITH } + { PUSH mutez 0 ; + NIL (pair (pair nat (map string bytes)) address) ; + DIG 3 ; + DUP 5 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PUSH nat 0 ; + PAIR ; + PAIR ; + CONS ; + TRANSFER_TOKENS } ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + PUSH nat 1 ; + DUP 4 ; + CDR ; + CDR ; + CDR ; + CAR ; + ADD ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CAR ; + PAIR ; + DIG 2 ; + CAR ; + PAIR ; + NIL operation ; + DIG 2 ; + CONS ; + PAIR } } ; + LAMBDA + (pair (pair nat address) + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez (pair nat (pair (map string bytes) (pair nat (pair (lambda nat mutez) mutez)))))))) + (pair (list operation) + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez (pair nat (pair (map string bytes) (pair nat (pair (lambda nat mutez) mutez)))))))) + { UNPAIR ; + UNPAIR ; + PUSH nat 1 ; + DUP 4 ; + CDR ; + CDR ; + CDR ; + CAR ; + SUB ; + ISNAT ; + IF_NONE { PUSH string "NO_TOKENS" ; FAILWITH } {} ; + DUP ; + DUP 5 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + SWAP ; + EXEC ; + DUP 5 ; + CDR ; + CDR ; + CAR ; + ADD ; + PUSH nat 10000 ; + DUP 6 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + DUP 3 ; + MUL ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + PUSH mutez 1 ; + SWAP ; + DUP ; + DUG 2 ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + PUSH mutez 1 ; + DUP 4 ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + SUB ; + ISNAT ; + IF_NONE + { DROP ; PUSH mutez 0 ; SWAP ; PAIR } + { DIG 2 ; DROP ; PUSH mutez 1 ; MUL ; SWAP ; PAIR } ; + UNPAIR ; + DUP 6 ; + CDR ; + CAR ; + CONTRACT %burn (pair nat address) ; + IF_NONE + { DIG 3 ; DROP ; PUSH string "NO_BURN" ; FAILWITH } + { PUSH mutez 0 ; DUP 7 ; DIG 6 ; PAIR ; TRANSFER_TOKENS } ; + DIG 4 ; + CONTRACT unit ; + IF_NONE + { DROP ; SWAP ; DROP ; PUSH string "CANT_RETURN" ; FAILWITH } + { DUP 4 ; + PUSH mutez 0 ; + COMPARE ; + EQ ; + IF { DROP ; DIG 2 ; DROP ; NIL operation } + { NIL operation ; SWAP ; DIG 4 ; UNIT ; TRANSFER_TOKENS ; CONS } ; + SWAP ; + CONS } ; + DUP 4 ; + CDR ; + CDR ; + CDR ; + CDR ; + DIG 3 ; + PAIR ; + DUP 4 ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 4 ; + CDR ; + CAR ; + PAIR ; + DUP 4 ; + CAR ; + PAIR ; + DIG 2 ; + DIG 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + ADD ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + PAIR ; + SWAP ; + CAR ; + PAIR ; + SWAP ; + PAIR } ; + DIG 3 ; + UNPAIR ; + IF_LEFT + { DIG 2 ; + DROP ; + IF_LEFT + { IF_LEFT + { DIG 2 ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + SWAP ; + IF_LEFT + { IF_LEFT + { DROP ; + DIG 2 ; + DROP ; + DUP ; + CDR ; + IF_NONE + { DROP ; PUSH string "NO_PENDING_ADMIN" ; FAILWITH } + { SENDER ; + COMPARE ; + EQ ; + IF { NONE address ; SWAP ; CAR ; CDR ; SENDER ; PAIR ; PAIR } + { DROP ; PUSH string "NOT_A_PENDING_ADMIN" ; FAILWITH } } ; + NIL operation ; + PAIR } + { SWAP ; + DUP ; + DUG 2 ; + DIG 4 ; + SWAP ; + EXEC ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + SWAP ; + DIG 2 ; + CAR ; + CAR ; + PAIR ; + PAIR ; + NIL operation ; + PAIR } } + { SWAP ; + DUP ; + DUG 2 ; + DIG 4 ; + SWAP ; + EXEC ; + DROP ; + SOME ; + SWAP ; + CAR ; + PAIR ; + NIL operation ; + PAIR } ; + UNPAIR ; + DIG 2 ; + CDR ; + DIG 2 ; + PAIR ; + SWAP ; + PAIR } + { DROP ; DIG 2 ; DROP ; SENDER ; PAIR ; EXEC } } + { DIG 3 ; + DROP ; + IF_LEFT + { PAIR ; EXEC } + { DROP ; + SWAP ; + DROP ; + AMOUNT ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + ADD ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + PAIR ; + SWAP ; + CAR ; + PAIR ; + NIL operation ; + PAIR } } } + { DIG 3 ; + DROP ; + IF_LEFT + { IF_LEFT + { DIG 3 ; DROP ; SWAP ; SENDER ; DIG 2 ; PAIR ; PAIR ; EXEC } + { SWAP ; DUP ; DUG 2 ; CAR ; DIG 4 ; SWAP ; EXEC ; DROP ; PAIR ; EXEC } } + { DIG 2 ; + DROP ; + IF_LEFT + { SWAP ; + DUP ; + DUG 2 ; + CAR ; + DIG 3 ; + SWAP ; + EXEC ; + DROP ; + NIL operation ; + SWAP ; + SET_DELEGATE ; + CONS ; + PAIR } + { DROP ; + DUP ; + CAR ; + DIG 2 ; + SWAP ; + EXEC ; + DROP ; + DUP ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + PUSH mutez 0 ; + COMPARE ; + LT ; + IF { DUP ; + CAR ; + CAR ; + CAR ; + CONTRACT unit ; + IF_NONE { PUSH string "ADDRESS_DOES_NOT_RESOLVE" ; FAILWITH } {} ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + PUSH unit Unit ; + TRANSFER_TOKENS ; + PUSH mutez 0 ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CAR ; + PAIR ; + DIG 2 ; + CAR ; + PAIR ; + NIL operation ; + DIG 2 ; + CONS ; + PAIR } + { DROP ; PUSH string "UNCLAIMED=0" ; FAILWITH } } } } } } + diff --git a/packages/minter-contracts/bin/bonding_curve_debug.tz b/packages/minter-contracts/bin/bonding_curve_debug.tz new file mode 100644 index 000000000..251f1a669 --- /dev/null +++ b/packages/minter-contracts/bin/bonding_curve_debug.tz @@ -0,0 +1,653 @@ +{ parameter + (or (or (or (or (or %admin (or (unit %confirm_admin) (bool %pause)) (address %set_admin)) + (unit %buy)) + (or (address %buy_offchain) (nat %cost))) + (or (or (unit %default) (nat %exampleFormula0)) (or (pair %pow nat nat) (nat %sell)))) + (or (or (pair %sell_offchain nat address) (option %set_delegate key_hash)) + (unit %withdraw))) ; + storage + (pair (pair %admin (pair (address %admin) (bool %paused)) (option %pending_admin address)) + (pair (address %market_contract) + (pair (mutez %auction_price) + (pair (nat %token_index) + (pair (map %token_metadata string bytes) + (pair (nat %basis_points) (pair (lambda %cost_mutez nat mutez) (mutez %unclaimed)))))))) ; + code { LAMBDA + (pair (pair address bool) (option address)) + unit + { CAR ; + CAR ; + SENDER ; + COMPARE ; + NEQ ; + IF { PUSH string "NOT_AN_ADMIN" ; FAILWITH } { UNIT } } ; + LAMBDA + (pair nat nat) + nat + { UNPAIR ; + DUP ; + DUG 2 ; + PAIR ; + PUSH nat 1 ; + DIG 2 ; + PAIR ; + PAIR ; + LEFT nat ; + LOOP_LEFT + { UNPAIR ; + UNPAIR ; + DIG 2 ; + UNPAIR ; + PUSH nat 0 ; + DUP 3 ; + COMPARE ; + EQ ; + IF { DROP 3 ; RIGHT (pair (pair nat nat) (pair nat nat)) } + { PUSH nat 0 ; + PUSH nat 1 ; + DUP 4 ; + AND ; + COMPARE ; + EQ ; + IF { DIG 3 } { DUP ; DIG 4 ; MUL } ; + SWAP ; + DUP ; + MUL ; + PUSH nat 1 ; + DIG 3 ; + LSR ; + SWAP ; + PAIR ; + SWAP ; + DIG 2 ; + PAIR ; + PAIR ; + LEFT nat } } } ; + LAMBDA + (pair address + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez (pair nat (pair (map string bytes) (pair nat (pair (lambda nat mutez) mutez)))))))) + (pair (list operation) + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez (pair nat (pair (map string bytes) (pair nat (pair (lambda nat mutez) mutez)))))))) + { UNPAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CAR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + SWAP ; + EXEC ; + DUP 3 ; + CDR ; + CDR ; + CAR ; + ADD ; + DUP ; + AMOUNT ; + COMPARE ; + NEQ ; + IF { SWAP ; + DROP ; + SWAP ; + DROP ; + AMOUNT ; + PUSH string "WRONG_TEZ_PRICE" ; + PAIR ; + PAIR ; + FAILWITH } + { DROP ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + CONTRACT %mint + (list (pair (pair %token_metadata (nat %token_id) (map %token_info string bytes)) + (address %owner))) ; + IF_NONE + { DROP ; PUSH string "NO_MINT" ; FAILWITH } + { PUSH mutez 0 ; + NIL (pair (pair nat (map string bytes)) address) ; + DIG 3 ; + DUP 5 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PUSH nat 0 ; + PAIR ; + PAIR ; + CONS ; + TRANSFER_TOKENS } ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + PUSH nat 1 ; + DUP 4 ; + CDR ; + CDR ; + CDR ; + CAR ; + ADD ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CAR ; + PAIR ; + DIG 2 ; + CAR ; + PAIR ; + NIL operation ; + DIG 2 ; + CONS ; + PAIR } } ; + LAMBDA + (pair (pair nat address) + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez (pair nat (pair (map string bytes) (pair nat (pair (lambda nat mutez) mutez)))))))) + (pair (list operation) + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez (pair nat (pair (map string bytes) (pair nat (pair (lambda nat mutez) mutez)))))))) + { UNPAIR ; + UNPAIR ; + PUSH nat 1 ; + DUP 4 ; + CDR ; + CDR ; + CDR ; + CAR ; + SUB ; + ISNAT ; + IF_NONE { PUSH string "NO_TOKENS" ; FAILWITH } {} ; + DUP ; + DUP 5 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + SWAP ; + EXEC ; + DUP 5 ; + CDR ; + CDR ; + CAR ; + ADD ; + PUSH nat 10000 ; + DUP 6 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + DUP 3 ; + MUL ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + PUSH mutez 1 ; + SWAP ; + DUP ; + DUG 2 ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + PUSH mutez 1 ; + DUP 4 ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + SUB ; + ISNAT ; + IF_NONE + { DROP ; PUSH mutez 0 ; SWAP ; PAIR } + { DIG 2 ; DROP ; PUSH mutez 1 ; MUL ; SWAP ; PAIR } ; + UNPAIR ; + DUP 6 ; + CDR ; + CAR ; + CONTRACT %burn (pair nat address) ; + IF_NONE + { DIG 3 ; DROP ; PUSH string "NO_BURN" ; FAILWITH } + { PUSH mutez 0 ; DUP 7 ; DIG 6 ; PAIR ; TRANSFER_TOKENS } ; + DIG 4 ; + CONTRACT unit ; + IF_NONE + { DROP ; SWAP ; DROP ; PUSH string "CANT_RETURN" ; FAILWITH } + { DUP 4 ; + PUSH mutez 0 ; + COMPARE ; + EQ ; + IF { DROP ; DIG 2 ; DROP ; NIL operation } + { NIL operation ; SWAP ; DIG 4 ; UNIT ; TRANSFER_TOKENS ; CONS } ; + SWAP ; + CONS } ; + DUP 4 ; + CDR ; + CDR ; + CDR ; + CDR ; + DIG 3 ; + PAIR ; + DUP 4 ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 4 ; + CDR ; + CAR ; + PAIR ; + DUP 4 ; + CAR ; + PAIR ; + DIG 2 ; + DIG 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + ADD ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + PAIR ; + SWAP ; + CAR ; + PAIR ; + SWAP ; + PAIR } ; + DIG 4 ; + UNPAIR ; + IF_LEFT + { IF_LEFT + { DIG 2 ; + DROP ; + DIG 3 ; + DROP ; + IF_LEFT + { IF_LEFT + { DIG 2 ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + SWAP ; + IF_LEFT + { IF_LEFT + { DROP ; + DIG 2 ; + DROP ; + DUP ; + CDR ; + IF_NONE + { DROP ; PUSH string "NO_PENDING_ADMIN" ; FAILWITH } + { SENDER ; + COMPARE ; + EQ ; + IF { NONE address ; SWAP ; CAR ; CDR ; SENDER ; PAIR ; PAIR } + { DROP ; PUSH string "NOT_A_PENDING_ADMIN" ; FAILWITH } } ; + NIL operation ; + PAIR } + { SWAP ; + DUP ; + DUG 2 ; + DIG 4 ; + SWAP ; + EXEC ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + SWAP ; + DIG 2 ; + CAR ; + CAR ; + PAIR ; + PAIR ; + NIL operation ; + PAIR } } + { SWAP ; + DUP ; + DUG 2 ; + DIG 4 ; + SWAP ; + EXEC ; + DROP ; + SOME ; + SWAP ; + CAR ; + PAIR ; + NIL operation ; + PAIR } ; + UNPAIR ; + DIG 2 ; + CDR ; + DIG 2 ; + PAIR ; + SWAP ; + PAIR } + { DROP ; DIG 2 ; DROP ; SENDER ; PAIR ; EXEC } } + { DIG 3 ; + DROP ; + IF_LEFT + { PAIR ; EXEC } + { DIG 2 ; + DROP ; + SWAP ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + SWAP ; + EXEC ; + FAILWITH } } } + { DIG 3 ; + DROP ; + DIG 4 ; + DROP ; + IF_LEFT + { DIG 2 ; + DROP ; + IF_LEFT + { DROP ; + SWAP ; + DROP ; + AMOUNT ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + ADD ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + PAIR ; + SWAP ; + CAR ; + PAIR ; + NIL operation ; + PAIR } + { SWAP ; + DROP ; + PUSH mutez 1 ; + PUSH nat 30000 ; + DUP 3 ; + COMPARE ; + LT ; + IF { DIG 2 ; + DROP ; + PUSH nat 3000 ; + DIG 2 ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR } + { SWAP ; + DUP ; + DUG 2 ; + PUSH nat 1000 ; + PAIR ; + DUP 4 ; + SWAP ; + EXEC ; + DIG 2 ; + PUSH nat 1001 ; + PAIR ; + DIG 3 ; + SWAP ; + EXEC ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + PUSH nat 10 ; + MUL } ; + MUL ; + FAILWITH } } + { IF_LEFT + { SWAP ; DROP ; SWAP ; DROP ; EXEC ; FAILWITH } + { DIG 3 ; DROP ; SWAP ; SENDER ; DIG 2 ; PAIR ; PAIR ; EXEC } } } } + { DIG 3 ; + DROP ; + DIG 3 ; + DROP ; + IF_LEFT + { IF_LEFT + { SWAP ; DUP ; DUG 2 ; CAR ; DIG 4 ; SWAP ; EXEC ; DROP ; PAIR ; EXEC } + { DIG 2 ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + DIG 3 ; + SWAP ; + EXEC ; + DROP ; + NIL operation ; + SWAP ; + SET_DELEGATE ; + CONS ; + PAIR } } + { DROP ; + SWAP ; + DROP ; + DUP ; + CAR ; + DIG 2 ; + SWAP ; + EXEC ; + DROP ; + DUP ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + PUSH mutez 0 ; + COMPARE ; + LT ; + IF { DUP ; + CAR ; + CAR ; + CAR ; + CONTRACT unit ; + IF_NONE { PUSH string "ADDRESS_DOES_NOT_RESOLVE" ; FAILWITH } {} ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + PUSH unit Unit ; + TRANSFER_TOKENS ; + PUSH mutez 0 ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CAR ; + PAIR ; + DIG 2 ; + CAR ; + PAIR ; + NIL operation ; + DIG 2 ; + CONS ; + PAIR } + { DROP ; PUSH string "UNCLAIMED=0" ; FAILWITH } } } } } + diff --git a/packages/minter-contracts/bin/bonding_curve_example_formula_0.tz b/packages/minter-contracts/bin/bonding_curve_example_formula_0.tz new file mode 100644 index 000000000..b2868c1eb --- /dev/null +++ b/packages/minter-contracts/bin/bonding_curve_example_formula_0.tz @@ -0,0 +1 @@ +{ PUSH (lambda (pair nat nat) nat) { UNPAIR ; DUP ; DUG 2 ; PAIR ; PUSH nat 1 ; DIG 2 ; PAIR ; PAIR ; LEFT nat ; LOOP_LEFT { UNPAIR ; UNPAIR ; DIG 2 ; UNPAIR ; PUSH nat 0 ; DUP 3 ; COMPARE ; EQ ; IF { DROP 3 ; RIGHT (pair (pair nat nat) (pair nat nat)) } { PUSH nat 0 ; PUSH nat 1 ; DUP 4 ; AND ; COMPARE ; EQ ; IF { DIG 3 } { DUP ; DIG 4 ; MUL } ; SWAP ; DUP ; MUL ; PUSH nat 1 ; DIG 3 ; LSR ; SWAP ; PAIR ; SWAP ; DIG 2 ; PAIR ; PAIR ; LEFT nat } } } ; PAIR ; { UNPAIR ; SWAP ; PUSH mutez 1 ; PUSH nat 30000 ; DUP 3 ; COMPARE ; LT ; IF { DIG 2 ; DROP ; PUSH nat 3000 ; DIG 2 ; EDIV ; IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; CAR } { SWAP ; DUP ; DUG 2 ; PUSH nat 1000 ; PAIR ; DUP 4 ; SWAP ; EXEC ; DIG 2 ; PUSH nat 1001 ; PAIR ; DIG 3 ; SWAP ; EXEC ; EDIV ; IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; CAR ; PUSH nat 10 ; MUL } ; MUL } } \ No newline at end of file diff --git a/packages/minter-contracts/bin/bonding_curve_piecewise.tz b/packages/minter-contracts/bin/bonding_curve_piecewise.tz new file mode 100644 index 000000000..5e3d19e85 --- /dev/null +++ b/packages/minter-contracts/bin/bonding_curve_piecewise.tz @@ -0,0 +1,631 @@ +{ parameter + (or (or (or (or %admin (or (unit %confirm_admin) (bool %pause)) (address %set_admin)) + (unit %buy)) + (or (address %buy_offchain) (unit %default))) + (or (or (nat %sell) (pair %sell_offchain nat address)) + (or (option %set_delegate key_hash) (unit %withdraw)))) ; + storage + (pair (pair %admin (pair (address %admin) (bool %paused)) (option %pending_admin address)) + (pair (address %market_contract) + (pair (mutez %auction_price) + (pair (nat %token_index) + (pair (map %token_metadata string bytes) + (pair (nat %basis_points) + (pair (pair %cost_mutez + (list %segments (pair (nat %length) (list %poly int))) + (list %last_segment int)) + (mutez %unclaimed)))))))) ; + code { LAMBDA + (pair (pair address bool) (option address)) + unit + { CAR ; + CAR ; + SENDER ; + COMPARE ; + NEQ ; + IF { PUSH string "NOT_AN_ADMIN" ; FAILWITH } { UNIT } } ; + LAMBDA + (pair (pair (list (pair nat (list int))) (list int)) nat) + int + { UNPAIR ; + PUSH nat 0 ; + NONE (list int) ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + ITER { SWAP ; + DUP ; + CAR ; + IF_NONE + { SWAP ; + DUP ; + DUG 2 ; + CAR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + ADD ; + DUP ; + DUP 6 ; + COMPARE ; + LE ; + IF { DROP ; CDR ; SWAP ; CDR ; SOME ; PAIR } + { DIG 2 ; DROP ; SWAP ; CAR ; PAIR } } + { DROP ; SWAP ; DROP } } ; + DIG 2 ; + INT ; + SWAP ; + CAR ; + IF_NONE { SWAP ; CDR } { DIG 2 ; DROP } ; + PUSH int 1 ; + PUSH int 0 ; + PAIR ; + SWAP ; + ITER { SWAP ; + DUP ; + CDR ; + DUP ; + DUP 5 ; + MUL ; + SWAP ; + DIG 3 ; + MUL ; + DIG 2 ; + CAR ; + ADD ; + PAIR } ; + SWAP ; + DROP ; + CAR } ; + DUP ; + LAMBDA + (pair (lambda (pair (pair (list (pair nat (list int))) (list int)) nat) int) + (pair address + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez + (pair nat + (pair (map string bytes) (pair nat (pair (pair (list (pair nat (list int))) (list int)) mutez))))))))) + (pair (list operation) + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez + (pair nat + (pair (map string bytes) (pair nat (pair (pair (list (pair nat (list int))) (list int)) mutez)))))))) + { UNPAIR ; + SWAP ; + UNPAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CAR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DIG 3 ; + SWAP ; + EXEC ; + ISNAT ; + IF_NONE { PUSH string "NEGATIVE_COST" ; FAILWITH } { PUSH mutez 1 ; MUL } ; + DUP 3 ; + CDR ; + CDR ; + CAR ; + ADD ; + DUP ; + AMOUNT ; + COMPARE ; + NEQ ; + IF { SWAP ; + DROP ; + SWAP ; + DROP ; + AMOUNT ; + PUSH string "WRONG_TEZ_PRICE" ; + PAIR ; + PAIR ; + FAILWITH } + { DROP ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + CONTRACT %mint + (list (pair (pair %token_metadata (nat %token_id) (map %token_info string bytes)) + (address %owner))) ; + IF_NONE + { DROP ; PUSH string "NO_MINT" ; FAILWITH } + { PUSH mutez 0 ; + NIL (pair (pair nat (map string bytes)) address) ; + DIG 3 ; + DUP 5 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PUSH nat 0 ; + PAIR ; + PAIR ; + CONS ; + TRANSFER_TOKENS } ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + PUSH nat 1 ; + DUP 4 ; + CDR ; + CDR ; + CDR ; + CAR ; + ADD ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CAR ; + PAIR ; + DIG 2 ; + CAR ; + PAIR ; + NIL operation ; + DIG 2 ; + CONS ; + PAIR } } ; + SWAP ; + APPLY ; + SWAP ; + LAMBDA + (pair (lambda (pair (pair (list (pair nat (list int))) (list int)) nat) int) + (pair (pair nat address) + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez + (pair nat + (pair (map string bytes) (pair nat (pair (pair (list (pair nat (list int))) (list int)) mutez))))))))) + (pair (list operation) + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez + (pair nat + (pair (map string bytes) (pair nat (pair (pair (list (pair nat (list int))) (list int)) mutez)))))))) + { UNPAIR ; + SWAP ; + UNPAIR ; + UNPAIR ; + PUSH nat 1 ; + DUP 4 ; + CDR ; + CDR ; + CDR ; + CAR ; + SUB ; + ISNAT ; + IF_NONE { PUSH string "NO_TOKENS" ; FAILWITH } {} ; + DUP ; + DUP 5 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DIG 5 ; + SWAP ; + EXEC ; + ISNAT ; + IF_NONE + { PUSH string "NEGATIVE_COST" ; FAILWITH } + { PUSH mutez 1 ; MUL ; DUP 5 ; CDR ; CDR ; CAR ; ADD } ; + PUSH nat 10000 ; + DUP 6 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + DUP 3 ; + MUL ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + PUSH mutez 1 ; + SWAP ; + DUP ; + DUG 2 ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + PUSH mutez 1 ; + DUP 4 ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + SUB ; + ISNAT ; + IF_NONE + { DROP ; PUSH mutez 0 ; SWAP ; PAIR } + { DIG 2 ; DROP ; PUSH mutez 1 ; MUL ; SWAP ; PAIR } ; + UNPAIR ; + DUP 6 ; + CDR ; + CAR ; + CONTRACT %burn (pair nat address) ; + IF_NONE + { DIG 3 ; DROP ; PUSH string "NO_BURN" ; FAILWITH } + { PUSH mutez 0 ; DUP 7 ; DIG 6 ; PAIR ; TRANSFER_TOKENS } ; + DIG 4 ; + CONTRACT unit ; + IF_NONE + { DROP ; SWAP ; DROP ; PUSH string "CANT_RETURN" ; FAILWITH } + { DUP 4 ; + PUSH mutez 0 ; + COMPARE ; + EQ ; + IF { DROP ; DIG 2 ; DROP ; NIL operation } + { NIL operation ; SWAP ; DIG 4 ; UNIT ; TRANSFER_TOKENS ; CONS } ; + SWAP ; + CONS } ; + DUP 4 ; + CDR ; + CDR ; + CDR ; + CDR ; + DIG 3 ; + PAIR ; + DUP 4 ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 4 ; + CDR ; + CAR ; + PAIR ; + DUP 4 ; + CAR ; + PAIR ; + DIG 2 ; + DIG 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + ADD ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + PAIR ; + SWAP ; + CAR ; + PAIR ; + SWAP ; + PAIR } ; + SWAP ; + APPLY ; + DIG 3 ; + UNPAIR ; + IF_LEFT + { DIG 2 ; + DROP ; + IF_LEFT + { IF_LEFT + { DIG 2 ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + SWAP ; + IF_LEFT + { IF_LEFT + { DROP ; + DIG 2 ; + DROP ; + DUP ; + CDR ; + IF_NONE + { DROP ; PUSH string "NO_PENDING_ADMIN" ; FAILWITH } + { SENDER ; + COMPARE ; + EQ ; + IF { NONE address ; SWAP ; CAR ; CDR ; SENDER ; PAIR ; PAIR } + { DROP ; PUSH string "NOT_A_PENDING_ADMIN" ; FAILWITH } } ; + NIL operation ; + PAIR } + { SWAP ; + DUP ; + DUG 2 ; + DIG 4 ; + SWAP ; + EXEC ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + SWAP ; + DIG 2 ; + CAR ; + CAR ; + PAIR ; + PAIR ; + NIL operation ; + PAIR } } + { SWAP ; + DUP ; + DUG 2 ; + DIG 4 ; + SWAP ; + EXEC ; + DROP ; + SOME ; + SWAP ; + CAR ; + PAIR ; + NIL operation ; + PAIR } ; + UNPAIR ; + DIG 2 ; + CDR ; + DIG 2 ; + PAIR ; + SWAP ; + PAIR } + { DROP ; DIG 2 ; DROP ; SENDER ; PAIR ; EXEC } } + { DIG 3 ; + DROP ; + IF_LEFT + { PAIR ; EXEC } + { DROP ; + SWAP ; + DROP ; + AMOUNT ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + ADD ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + PAIR ; + SWAP ; + CAR ; + PAIR ; + NIL operation ; + PAIR } } } + { DIG 3 ; + DROP ; + IF_LEFT + { IF_LEFT + { DIG 3 ; DROP ; SWAP ; SENDER ; DIG 2 ; PAIR ; PAIR ; EXEC } + { SWAP ; DUP ; DUG 2 ; CAR ; DIG 4 ; SWAP ; EXEC ; DROP ; PAIR ; EXEC } } + { DIG 2 ; + DROP ; + IF_LEFT + { SWAP ; + DUP ; + DUG 2 ; + CAR ; + DIG 3 ; + SWAP ; + EXEC ; + DROP ; + NIL operation ; + SWAP ; + SET_DELEGATE ; + CONS ; + PAIR } + { DROP ; + DUP ; + CAR ; + DIG 2 ; + SWAP ; + EXEC ; + DROP ; + DUP ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + PUSH mutez 0 ; + COMPARE ; + LT ; + IF { DUP ; + CAR ; + CAR ; + CAR ; + CONTRACT unit ; + IF_NONE { PUSH string "ADDRESS_DOES_NOT_RESOLVE" ; FAILWITH } {} ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + PUSH unit Unit ; + TRANSFER_TOKENS ; + PUSH mutez 0 ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CAR ; + PAIR ; + DIG 2 ; + CAR ; + PAIR ; + NIL operation ; + DIG 2 ; + CONS ; + PAIR } + { DROP ; PUSH string "UNCLAIMED=0" ; FAILWITH } } } } } } + diff --git a/packages/minter-contracts/bin/bonding_curve_piecewise_debug.tz b/packages/minter-contracts/bin/bonding_curve_piecewise_debug.tz new file mode 100644 index 000000000..1f2402156 --- /dev/null +++ b/packages/minter-contracts/bin/bonding_curve_piecewise_debug.tz @@ -0,0 +1,746 @@ +{ parameter + (or (or (or (or (or %admin (or (unit %confirm_admin) (bool %pause)) (address %set_admin)) + (unit %buy)) + (or (address %buy_offchain) (nat %cost))) + (or (or (unit %default) (nat %exampleFormula0)) (or (pair %pow nat nat) (nat %sell)))) + (or (or (pair %sell_offchain nat address) (option %set_delegate key_hash)) + (unit %withdraw))) ; + storage + (pair (pair %admin (pair (address %admin) (bool %paused)) (option %pending_admin address)) + (pair (address %market_contract) + (pair (mutez %auction_price) + (pair (nat %token_index) + (pair (map %token_metadata string bytes) + (pair (nat %basis_points) + (pair (pair %cost_mutez + (list %segments (pair (nat %length) (list %poly int))) + (list %last_segment int)) + (mutez %unclaimed)))))))) ; + code { LAMBDA + (pair (pair address bool) (option address)) + unit + { CAR ; + CAR ; + SENDER ; + COMPARE ; + NEQ ; + IF { PUSH string "NOT_AN_ADMIN" ; FAILWITH } { UNIT } } ; + LAMBDA + (pair (pair (list (pair nat (list int))) (list int)) nat) + int + { UNPAIR ; + PUSH nat 0 ; + NONE (list int) ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + ITER { SWAP ; + DUP ; + CAR ; + IF_NONE + { SWAP ; + DUP ; + DUG 2 ; + CAR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + ADD ; + DUP ; + DUP 6 ; + COMPARE ; + LE ; + IF { DROP ; CDR ; SWAP ; CDR ; SOME ; PAIR } + { DIG 2 ; DROP ; SWAP ; CAR ; PAIR } } + { DROP ; SWAP ; DROP } } ; + DIG 2 ; + INT ; + SWAP ; + CAR ; + IF_NONE { SWAP ; CDR } { DIG 2 ; DROP } ; + PUSH int 1 ; + PUSH int 0 ; + PAIR ; + SWAP ; + ITER { SWAP ; + DUP ; + CDR ; + DUP ; + DUP 5 ; + MUL ; + SWAP ; + DIG 3 ; + MUL ; + DIG 2 ; + CAR ; + ADD ; + PAIR } ; + SWAP ; + DROP ; + CAR } ; + LAMBDA + (pair nat nat) + nat + { UNPAIR ; + DUP ; + DUG 2 ; + PAIR ; + PUSH nat 1 ; + DIG 2 ; + PAIR ; + PAIR ; + LEFT nat ; + LOOP_LEFT + { UNPAIR ; + UNPAIR ; + DIG 2 ; + UNPAIR ; + PUSH nat 0 ; + DUP 3 ; + COMPARE ; + EQ ; + IF { DROP 3 ; RIGHT (pair (pair nat nat) (pair nat nat)) } + { PUSH nat 0 ; + PUSH nat 1 ; + DUP 4 ; + AND ; + COMPARE ; + EQ ; + IF { DIG 3 } { DUP ; DIG 4 ; MUL } ; + SWAP ; + DUP ; + MUL ; + PUSH nat 1 ; + DIG 3 ; + LSR ; + SWAP ; + PAIR ; + SWAP ; + DIG 2 ; + PAIR ; + PAIR ; + LEFT nat } } } ; + SWAP ; + DUP ; + DUG 2 ; + LAMBDA + (pair (lambda (pair (pair (list (pair nat (list int))) (list int)) nat) int) + (pair address + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez + (pair nat + (pair (map string bytes) (pair nat (pair (pair (list (pair nat (list int))) (list int)) mutez))))))))) + (pair (list operation) + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez + (pair nat + (pair (map string bytes) (pair nat (pair (pair (list (pair nat (list int))) (list int)) mutez)))))))) + { UNPAIR ; + SWAP ; + UNPAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CAR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DIG 3 ; + SWAP ; + EXEC ; + ISNAT ; + IF_NONE { PUSH string "NEGATIVE_COST" ; FAILWITH } { PUSH mutez 1 ; MUL } ; + DUP 3 ; + CDR ; + CDR ; + CAR ; + ADD ; + DUP ; + AMOUNT ; + COMPARE ; + NEQ ; + IF { SWAP ; + DROP ; + SWAP ; + DROP ; + AMOUNT ; + PUSH string "WRONG_TEZ_PRICE" ; + PAIR ; + PAIR ; + FAILWITH } + { DROP ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + CONTRACT %mint + (list (pair (pair %token_metadata (nat %token_id) (map %token_info string bytes)) + (address %owner))) ; + IF_NONE + { DROP ; PUSH string "NO_MINT" ; FAILWITH } + { PUSH mutez 0 ; + NIL (pair (pair nat (map string bytes)) address) ; + DIG 3 ; + DUP 5 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PUSH nat 0 ; + PAIR ; + PAIR ; + CONS ; + TRANSFER_TOKENS } ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + PUSH nat 1 ; + DUP 4 ; + CDR ; + CDR ; + CDR ; + CAR ; + ADD ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CAR ; + PAIR ; + DIG 2 ; + CAR ; + PAIR ; + NIL operation ; + DIG 2 ; + CONS ; + PAIR } } ; + SWAP ; + APPLY ; + DUP 3 ; + LAMBDA + (pair (lambda (pair (pair (list (pair nat (list int))) (list int)) nat) int) + (pair (pair nat address) + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez + (pair nat + (pair (map string bytes) (pair nat (pair (pair (list (pair nat (list int))) (list int)) mutez))))))))) + (pair (list operation) + (pair (pair (pair address bool) (option address)) + (pair address + (pair mutez + (pair nat + (pair (map string bytes) (pair nat (pair (pair (list (pair nat (list int))) (list int)) mutez)))))))) + { UNPAIR ; + SWAP ; + UNPAIR ; + UNPAIR ; + PUSH nat 1 ; + DUP 4 ; + CDR ; + CDR ; + CDR ; + CAR ; + SUB ; + ISNAT ; + IF_NONE { PUSH string "NO_TOKENS" ; FAILWITH } {} ; + DUP ; + DUP 5 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DIG 5 ; + SWAP ; + EXEC ; + ISNAT ; + IF_NONE + { PUSH string "NEGATIVE_COST" ; FAILWITH } + { PUSH mutez 1 ; MUL ; DUP 5 ; CDR ; CDR ; CAR ; ADD } ; + PUSH nat 10000 ; + DUP 6 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + DUP 3 ; + MUL ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + PUSH mutez 1 ; + SWAP ; + DUP ; + DUG 2 ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + PUSH mutez 1 ; + DUP 4 ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + SUB ; + ISNAT ; + IF_NONE + { DROP ; PUSH mutez 0 ; SWAP ; PAIR } + { DIG 2 ; DROP ; PUSH mutez 1 ; MUL ; SWAP ; PAIR } ; + UNPAIR ; + DUP 6 ; + CDR ; + CAR ; + CONTRACT %burn (pair nat address) ; + IF_NONE + { DIG 3 ; DROP ; PUSH string "NO_BURN" ; FAILWITH } + { PUSH mutez 0 ; DUP 7 ; DIG 6 ; PAIR ; TRANSFER_TOKENS } ; + DIG 4 ; + CONTRACT unit ; + IF_NONE + { DROP ; SWAP ; DROP ; PUSH string "CANT_RETURN" ; FAILWITH } + { DUP 4 ; + PUSH mutez 0 ; + COMPARE ; + EQ ; + IF { DROP ; DIG 2 ; DROP ; NIL operation } + { NIL operation ; SWAP ; DIG 4 ; UNIT ; TRANSFER_TOKENS ; CONS } ; + SWAP ; + CONS } ; + DUP 4 ; + CDR ; + CDR ; + CDR ; + CDR ; + DIG 3 ; + PAIR ; + DUP 4 ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 4 ; + CDR ; + CAR ; + PAIR ; + DUP 4 ; + CAR ; + PAIR ; + DIG 2 ; + DIG 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + ADD ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + PAIR ; + SWAP ; + CAR ; + PAIR ; + SWAP ; + PAIR } ; + SWAP ; + APPLY ; + DIG 5 ; + UNPAIR ; + IF_LEFT + { IF_LEFT + { DIG 2 ; + DROP ; + DIG 3 ; + DROP ; + IF_LEFT + { DIG 3 ; + DROP ; + IF_LEFT + { DIG 2 ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + SWAP ; + IF_LEFT + { IF_LEFT + { DROP ; + DIG 2 ; + DROP ; + DUP ; + CDR ; + IF_NONE + { DROP ; PUSH string "NO_PENDING_ADMIN" ; FAILWITH } + { SENDER ; + COMPARE ; + EQ ; + IF { NONE address ; SWAP ; CAR ; CDR ; SENDER ; PAIR ; PAIR } + { DROP ; PUSH string "NOT_A_PENDING_ADMIN" ; FAILWITH } } ; + NIL operation ; + PAIR } + { SWAP ; + DUP ; + DUG 2 ; + DIG 4 ; + SWAP ; + EXEC ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + SWAP ; + DIG 2 ; + CAR ; + CAR ; + PAIR ; + PAIR ; + NIL operation ; + PAIR } } + { SWAP ; + DUP ; + DUG 2 ; + DIG 4 ; + SWAP ; + EXEC ; + DROP ; + SOME ; + SWAP ; + CAR ; + PAIR ; + NIL operation ; + PAIR } ; + UNPAIR ; + DIG 2 ; + CDR ; + DIG 2 ; + PAIR ; + SWAP ; + PAIR } + { DROP ; DIG 2 ; DROP ; SENDER ; PAIR ; EXEC } } + { DIG 4 ; + DROP ; + IF_LEFT + { DIG 3 ; DROP ; PAIR ; EXEC } + { DIG 2 ; + DROP ; + SWAP ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + EXEC ; + FAILWITH } } } + { DIG 3 ; + DROP ; + DIG 4 ; + DROP ; + DIG 4 ; + DROP ; + IF_LEFT + { DIG 2 ; + DROP ; + IF_LEFT + { DROP ; + SWAP ; + DROP ; + AMOUNT ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + ADD ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CAR ; + PAIR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + PAIR ; + SWAP ; + CAR ; + PAIR ; + NIL operation ; + PAIR } + { SWAP ; + DROP ; + PUSH mutez 1 ; + PUSH nat 30000 ; + DUP 3 ; + COMPARE ; + LT ; + IF { DIG 2 ; + DROP ; + PUSH nat 3000 ; + DIG 2 ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR } + { SWAP ; + DUP ; + DUG 2 ; + PUSH nat 1000 ; + PAIR ; + DUP 4 ; + SWAP ; + EXEC ; + DIG 2 ; + PUSH nat 1001 ; + PAIR ; + DIG 3 ; + SWAP ; + EXEC ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + PUSH nat 10 ; + MUL } ; + MUL ; + FAILWITH } } + { IF_LEFT + { SWAP ; DROP ; SWAP ; DROP ; EXEC ; FAILWITH } + { DIG 3 ; DROP ; SWAP ; SENDER ; DIG 2 ; PAIR ; PAIR ; EXEC } } } } + { DIG 3 ; + DROP ; + DIG 3 ; + DROP ; + DIG 3 ; + DROP ; + IF_LEFT + { IF_LEFT + { SWAP ; DUP ; DUG 2 ; CAR ; DIG 4 ; SWAP ; EXEC ; DROP ; PAIR ; EXEC } + { DIG 2 ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + DIG 3 ; + SWAP ; + EXEC ; + DROP ; + NIL operation ; + SWAP ; + SET_DELEGATE ; + CONS ; + PAIR } } + { DROP ; + SWAP ; + DROP ; + DUP ; + CAR ; + DIG 2 ; + SWAP ; + EXEC ; + DROP ; + DUP ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + PUSH mutez 0 ; + COMPARE ; + LT ; + IF { DUP ; + CAR ; + CAR ; + CAR ; + CONTRACT unit ; + IF_NONE { PUSH string "ADDRESS_DOES_NOT_RESOLVE" ; FAILWITH } {} ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + PUSH unit Unit ; + TRANSFER_TOKENS ; + PUSH mutez 0 ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CDR ; + CAR ; + PAIR ; + DUP 3 ; + CDR ; + CAR ; + PAIR ; + DIG 2 ; + CAR ; + PAIR ; + NIL operation ; + DIG 2 ; + CONS ; + PAIR } + { DROP ; PUSH string "UNCLAIMED=0" ; FAILWITH } } } } } + diff --git a/packages/minter-contracts/bin/fa2_multi_nft_asset.tz b/packages/minter-contracts/bin/fa2_multi_nft_asset.tz index fd5ecc1a8..eb216b77c 100644 --- a/packages/minter-contracts/bin/fa2_multi_nft_asset.tz +++ b/packages/minter-contracts/bin/fa2_multi_nft_asset.tz @@ -1,19 +1,21 @@ { parameter - (or (or (or %admin (or (unit %confirm_admin) (bool %pause)) (address %set_admin)) - (or %assets - (or (pair %balance_of - (list %requests (pair (address %owner) (nat %token_id))) - (contract %callback - (list (pair (pair %request (address %owner) (nat %token_id)) (nat %balance))))) - (list %transfer - (pair (address %from_) - (list %txs (pair (address %to_) (pair (nat %token_id) (nat %amount))))))) - (list %update_operators - (or (pair %add_operator (address %owner) (pair (address %operator) (nat %token_id))) - (pair %remove_operator (address %owner) (pair (address %operator) (nat %token_id))))))) - (list %mint - (pair (pair %token_metadata (nat %token_id) (map %token_info string bytes)) - (address %owner)))) ; + (or (or (or (or %admin (or (unit %confirm_admin) (bool %pause)) (address %set_admin)) + (or %assets + (or (pair %balance_of + (list %requests (pair (address %owner) (nat %token_id))) + (contract %callback + (list (pair (pair %request (address %owner) (nat %token_id)) (nat %balance))))) + (list %transfer + (pair (address %from_) + (list %txs (pair (address %to_) (pair (nat %token_id) (nat %amount))))))) + (list %update_operators + (or (pair %add_operator (address %owner) (pair (address %operator) (nat %token_id))) + (pair %remove_operator (address %owner) (pair (address %operator) (nat %token_id))))))) + (or (pair %burn nat address) + (list %mint + (pair (pair %token_metadata (nat %token_id) (map %token_info string bytes)) + (address %owner))))) + (list %update_metadata (pair (nat %token_id) (map %token_info string bytes)))) ; storage (pair (pair (pair %admin (pair (address %admin) (bool %paused)) (option %pending_admin address)) (pair %assets @@ -162,32 +164,52 @@ UNPAIR ; IF_LEFT { IF_LEFT - { DIG 2 ; - DROP ; - DIG 2 ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CAR ; - SWAP ; - IF_LEFT - { IF_LEFT - { DROP ; - DIG 2 ; - DROP ; - DUP ; - CDR ; - IF_NONE - { DROP ; PUSH string "NO_PENDING_ADMIN" ; FAILWITH } - { SENDER ; - COMPARE ; - EQ ; - IF { NONE address ; SWAP ; CAR ; CDR ; SENDER ; PAIR ; PAIR } - { DROP ; PUSH string "NOT_A_PENDING_ADMIN" ; FAILWITH } } ; - NIL operation ; - PAIR } + { IF_LEFT + { DIG 2 ; + DROP ; + DIG 2 ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + CAR ; + SWAP ; + IF_LEFT + { IF_LEFT + { DROP ; + DIG 2 ; + DROP ; + DUP ; + CDR ; + IF_NONE + { DROP ; PUSH string "NO_PENDING_ADMIN" ; FAILWITH } + { SENDER ; + COMPARE ; + EQ ; + IF { NONE address ; SWAP ; CAR ; CDR ; SENDER ; PAIR ; PAIR } + { DROP ; PUSH string "NOT_A_PENDING_ADMIN" ; FAILWITH } } ; + NIL operation ; + PAIR } + { SWAP ; + DUP ; + DUG 2 ; + DIG 4 ; + SWAP ; + EXEC ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + SWAP ; + DIG 2 ; + CAR ; + CAR ; + PAIR ; + PAIR ; + NIL operation ; + PAIR } } { SWAP ; DUP ; DUG 2 ; @@ -195,231 +217,404 @@ SWAP ; EXEC ; DROP ; + SOME ; SWAP ; - DUP ; - DUG 2 ; - CDR ; - SWAP ; - DIG 2 ; - CAR ; CAR ; PAIR ; - PAIR ; NIL operation ; - PAIR } } - { SWAP ; - DUP ; - DUG 2 ; - DIG 4 ; + PAIR } ; + UNPAIR ; + DUP 3 ; + CDR ; + DIG 3 ; + CAR ; + CDR ; + DIG 3 ; + PAIR ; + PAIR ; SWAP ; - EXEC ; + PAIR } + { DIG 4 ; DROP ; - SOME ; SWAP ; + DUP ; + DUG 2 ; CAR ; - PAIR ; - NIL operation ; - PAIR } ; - UNPAIR ; - DUP 3 ; - CDR ; - DIG 3 ; - CAR ; - CDR ; - DIG 3 ; - PAIR ; - PAIR ; - SWAP ; - PAIR } - { DIG 4 ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CAR ; - CAR ; - CDR ; - IF { PUSH string "PAUSED" ; FAILWITH } {} ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CDR ; - SWAP ; - IF_LEFT - { IF_LEFT + CAR ; + CAR ; + CDR ; + IF { PUSH string "PAUSED" ; FAILWITH } {} ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + CDR ; + SWAP ; + IF_LEFT + { IF_LEFT + { DIG 3 ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + CAR ; + SWAP ; + DUP ; + CAR ; + MAP { DUP 3 ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + GET ; + IF_NONE + { DROP ; DUP 5 ; FAILWITH } + { SWAP ; + DUP ; + DUG 2 ; + CAR ; + SWAP ; + COMPARE ; + EQ ; + IF { PUSH nat 1 } { PUSH nat 0 } ; + SWAP ; + PAIR } } ; + DIG 2 ; + DROP ; + DIG 4 ; + DROP ; + SWAP ; + CDR ; + PUSH mutez 0 ; + DIG 2 ; + TRANSFER_TOKENS ; + SWAP ; + NIL operation ; + DIG 2 ; + CONS ; + PAIR } + { DIG 4 ; + DROP ; + MAP { DUP ; + CDR ; + MAP { DUP ; + CDR ; + CDR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + PAIR ; + SWAP ; + CAR ; + SOME ; + PAIR } ; + SWAP ; + CAR ; + SOME ; + PAIR } ; + SWAP ; + LAMBDA + (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit))) + unit + { UNPAIR ; + UNPAIR ; + DIG 2 ; + UNPAIR ; + DUP 4 ; + DUP 4 ; + COMPARE ; + EQ ; + IF { DROP 4 ; UNIT } + { DIG 3 ; + PAIR ; + DIG 2 ; + PAIR ; + MEM ; + IF { UNIT } { PUSH string "FA2_NOT_OPERATOR" ; FAILWITH } } } ; + DIG 2 ; + PAIR ; + PAIR ; + DIG 2 ; + SWAP ; + EXEC } } { DIG 3 ; + DROP ; + DIG 3 ; DROP ; SWAP ; DUP ; DUG 2 ; - CAR ; + CDR ; CAR ; SWAP ; - DUP ; - CAR ; - MAP { DUP 3 ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - GET ; - IF_NONE - { DROP ; DUP 5 ; FAILWITH } - { SWAP ; - DUP ; - DUG 2 ; - CAR ; - SWAP ; - COMPARE ; - EQ ; - IF { PUSH nat 1 } { PUSH nat 0 } ; - SWAP ; - PAIR } } ; - DIG 2 ; - DROP ; - DIG 4 ; + SENDER ; + DUG 2 ; + ITER { SWAP ; + DUP 3 ; + DUP 3 ; + IF_LEFT {} {} ; + CAR ; + COMPARE ; + EQ ; + IF {} { PUSH string "FA2_NOT_OWNER" ; FAILWITH } ; + SWAP ; + IF_LEFT + { SWAP ; + UNIT ; + SOME ; + DUP 3 ; + CDR ; + CDR ; + DUP 4 ; + CDR ; + CAR ; + PAIR ; + DIG 3 ; + CAR ; + PAIR ; + UPDATE } + { DUP ; + DUG 2 ; + CDR ; + CDR ; + DUP 3 ; + CDR ; + CAR ; + PAIR ; + DIG 2 ; + CAR ; + PAIR ; + NONE unit ; + SWAP ; + UPDATE } } ; + SWAP ; DROP ; SWAP ; + DUP ; + DUG 2 ; + CDR ; CDR ; - PUSH mutez 0 ; - DIG 2 ; - TRANSFER_TOKENS ; - SWAP ; - NIL operation ; - DIG 2 ; - CONS ; - PAIR } - { DIG 4 ; - DROP ; - MAP { DUP ; - CDR ; - MAP { DUP ; - CDR ; - CDR ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - PAIR ; - SWAP ; - CAR ; - SOME ; - PAIR } ; - SWAP ; - CAR ; - SOME ; - PAIR } ; SWAP ; - LAMBDA - (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit))) - unit - { UNPAIR ; - UNPAIR ; - DIG 2 ; - UNPAIR ; - DUP 4 ; - DUP 4 ; - COMPARE ; - EQ ; - IF { DROP 4 ; UNIT } - { DIG 3 ; - PAIR ; - DIG 2 ; - PAIR ; - MEM ; - IF { UNIT } { PUSH string "FA2_NOT_OPERATOR" ; FAILWITH } } } ; - DIG 2 ; - PAIR ; PAIR ; - DIG 2 ; SWAP ; - EXEC } } - { DIG 3 ; - DROP ; + CAR ; + PAIR ; + NIL operation ; + PAIR } ; + UNPAIR ; + DUP 3 ; + CDR ; + DIG 2 ; DIG 3 ; + CAR ; + CAR ; + PAIR ; + PAIR ; + SWAP ; + PAIR } } + { DIG 3 ; + DROP ; + DIG 3 ; + DROP ; + IF_LEFT + { DIG 2 ; DROP ; + UNPAIR ; + DUP 3 ; + CAR ; + CDR ; + CAR ; + CAR ; + NONE address ; + DUP 3 ; + GET_AND_UPDATE ; + IF_NONE + { DIG 2 ; DROP ; PUSH string "WRONG_ID" ; FAILWITH } + { DIG 3 ; + COMPARE ; + EQ ; + DUP 4 ; + CAR ; + CDR ; + CDR ; + CAR ; + PUSH nat 0 ; + SENDER ; + PAIR ; + DUP 6 ; + CAR ; + CAR ; + CAR ; + CAR ; + PAIR ; + MEM ; + AND ; + IF { NIL operation } { PUSH string "NOT_BURNER" ; FAILWITH } } ; + DUP 4 ; + CDR ; + DUP 5 ; + CAR ; + CDR ; + UNPAIR ; + CDR ; + DIG 4 ; + PAIR ; + PAIR ; + DUP 5 ; + CAR ; + CDR ; + CDR ; + CDR ; + NONE (pair nat (map string bytes)) ; + DIG 5 ; + UPDATE ; SWAP ; DUP ; DUG 2 ; CDR ; CAR ; + PAIR ; + SWAP ; + CAR ; + PAIR ; + DIG 3 ; + CAR ; + CAR ; + PAIR ; + PAIR ; SWAP ; + PAIR } + { SWAP ; + DUP ; + DUG 2 ; + CAR ; + CDR ; + CDR ; + CAR ; + PUSH nat 0 ; SENDER ; + PAIR ; + DUP 4 ; + CAR ; + CAR ; + CAR ; + CAR ; + PAIR ; + MEM ; + IF {} { PUSH string "NOT_MINTER" ; FAILWITH } ; + SWAP ; + DUP ; DUG 2 ; + CAR ; + CDR ; + NIL (pair (option address) (pair nat nat)) ; + PAIR ; + SWAP ; ITER { SWAP ; - DUP 3 ; - DUP 3 ; - IF_LEFT {} {} ; + DUP ; + CDR ; CAR ; - COMPARE ; - EQ ; - IF {} { PUSH string "FA2_NOT_OWNER" ; FAILWITH } ; + CDR ; SWAP ; - IF_LEFT - { SWAP ; - UNIT ; - SOME ; - DUP 3 ; - CDR ; - CDR ; - DUP 4 ; - CDR ; - CAR ; - PAIR ; - DIG 3 ; - CAR ; - PAIR ; - UPDATE } - { DUP ; - DUG 2 ; - CDR ; - CDR ; - DUP 3 ; - CDR ; - CAR ; - PAIR ; - DIG 2 ; - CAR ; - PAIR ; - NONE unit ; - SWAP ; - UPDATE } } ; - SWAP ; - DROP ; - SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + CAR ; + SWAP ; + DUP ; + DUG 2 ; + MEM ; + IF { DROP 3 ; PUSH string "FA2_INVALID_TOKEN_ID" ; FAILWITH } + { PUSH nat 1 ; + SWAP ; + DUP ; + DUG 2 ; + ADD ; + DUP 3 ; + CDR ; + DUP 4 ; + CDR ; + CDR ; + CDR ; + DUP 6 ; + CAR ; + CDR ; + DUP 5 ; + PAIR ; + DUP 5 ; + SWAP ; + SOME ; + SWAP ; + UPDATE ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + PAIR ; + SWAP ; + CAR ; + PAIR ; + DUP ; + CDR ; + DUG 2 ; + CAR ; + CAR ; + PAIR ; + PAIR ; + DIG 2 ; + CAR ; + PUSH nat 1 ; + DIG 3 ; + PAIR ; + DIG 3 ; + CDR ; + SOME ; + PAIR ; + CONS ; + PAIR } } ; DUP ; - DUG 2 ; - CDR ; CDR ; - SWAP ; + LAMBDA + (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit))) + unit + { DROP ; UNIT } ; + NIL (pair (option address) (list (pair (option address) (pair nat nat)))) ; + DIG 3 ; + CAR ; + NONE address ; + PAIR ; + CONS ; PAIR ; + PAIR ; + DIG 2 ; SWAP ; + EXEC ; + UNPAIR ; + DUP 3 ; + CDR ; + DIG 2 ; + DIG 3 ; + CAR ; CAR ; PAIR ; - NIL operation ; - PAIR } ; - UNPAIR ; - DUP 3 ; - CDR ; - DIG 2 ; - DIG 3 ; - CAR ; - CAR ; - PAIR ; - PAIR ; - SWAP ; - PAIR } } - { DIG 3 ; + PAIR ; + SWAP ; + PAIR } } } + { DIG 2 ; + DROP ; + DIG 2 ; DROP ; SWAP ; DUP ; DUG 2 ; CAR ; CAR ; - DIG 4 ; + DIG 3 ; SWAP ; EXEC ; DROP ; @@ -428,95 +623,31 @@ DUG 2 ; CAR ; CDR ; - NIL (pair (option address) (pair nat nat)) ; - PAIR ; + CDR ; + CDR ; + SWAP ; + ITER { DUP ; DUG 2 ; SOME ; DIG 2 ; CAR ; UPDATE } ; SWAP ; - ITER { DUP ; - DUG 2 ; - CAR ; - CAR ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - CAR ; - SWAP ; - DUP ; - DUG 2 ; - MEM ; - IF { DROP 3 ; PUSH string "FA2_INVALID_TOKEN_ID" ; FAILWITH } - { PUSH nat 1 ; - SWAP ; - DUP ; - DUG 2 ; - ADD ; - DUP 3 ; - CDR ; - DUP 4 ; - CDR ; - CDR ; - CDR ; - DUP 6 ; - CAR ; - DUP 5 ; - SWAP ; - SOME ; - SWAP ; - UPDATE ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - PAIR ; - SWAP ; - CAR ; - PAIR ; - DUP ; - CDR ; - DUG 2 ; - CAR ; - CAR ; - PAIR ; - PAIR ; - DIG 2 ; - CAR ; - PUSH nat 1 ; - DIG 3 ; - PAIR ; - DIG 3 ; - CDR ; - SOME ; - PAIR ; - CONS ; - PAIR } } ; DUP ; + DUG 2 ; + CDR ; + SWAP ; + DUP 3 ; + CAR ; + CDR ; CDR ; - LAMBDA - (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit))) - unit - { DROP ; UNIT } ; - NIL (pair (option address) (list (pair (option address) (pair nat nat)))) ; - DIG 3 ; CAR ; - NONE address ; - PAIR ; - CONS ; - PAIR ; PAIR ; - DIG 2 ; - SWAP ; - EXEC ; - UNPAIR ; DUP 3 ; + CAR ; CDR ; + CAR ; + PAIR ; DIG 2 ; - DIG 3 ; CAR ; CAR ; PAIR ; PAIR ; - SWAP ; + NIL operation ; PAIR } } } diff --git a/packages/minter-contracts/bin/fa2_multi_nft_asset_multi_admin.tz b/packages/minter-contracts/bin/fa2_multi_nft_asset_multi_admin.tz deleted file mode 100644 index a3552fec3..000000000 --- a/packages/minter-contracts/bin/fa2_multi_nft_asset_multi_admin.tz +++ /dev/null @@ -1,588 +0,0 @@ -{ parameter - (or (or (or %admin - (or (unit %confirm_admin) (bool %pause)) - (or (address %remove_admin) (address %set_admin))) - (or %assets - (or (pair %balance_of - (list %requests (pair (address %owner) (nat %token_id))) - (contract %callback - (list (pair (pair %request (address %owner) (nat %token_id)) (nat %balance))))) - (list %transfer - (pair (address %from_) - (list %txs (pair (address %to_) (pair (nat %token_id) (nat %amount))))))) - (list %update_operators - (or (pair %add_operator (address %owner) (pair (address %operator) (nat %token_id))) - (pair %remove_operator (address %owner) (pair (address %operator) (nat %token_id))))))) - (list %mint - (pair (pair %token_metadata (nat %token_id) (map %token_info string bytes)) - (address %owner)))) ; - storage - (pair (pair (pair %admin - (pair (set %admins address) (bool %paused)) - (big_map %pending_admins address unit)) - (pair %assets - (pair (big_map %ledger nat address) (nat %next_token_id)) - (pair (big_map %operators (pair address (pair address nat)) unit) - (big_map %token_metadata nat (pair (nat %token_id) (map %token_info string bytes)))))) - (big_map %metadata string bytes)) ; - code { LAMBDA - (pair (pair (set address) bool) (big_map address unit)) - unit - { CAR ; - CAR ; - SENDER ; - MEM ; - NOT ; - IF { PUSH string "NOT_AN_ADMIN" ; FAILWITH } { UNIT } } ; - PUSH string "FA2_TOKEN_UNDEFINED" ; - PUSH string "FA2_INSUFFICIENT_BALANCE" ; - SWAP ; - DUP ; - DUG 2 ; - SWAP ; - PAIR ; - LAMBDA - (pair (pair string string) - (pair (pair (list (pair (option address) (list (pair (option address) (pair nat nat))))) - (lambda - (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit))) - unit)) - (pair (pair (big_map nat address) nat) - (pair (big_map (pair address (pair address nat)) unit) - (big_map nat (pair nat (map string bytes))))))) - (pair (list operation) - (pair (pair (big_map nat address) nat) - (pair (big_map (pair address (pair address nat)) unit) - (big_map nat (pair nat (map string bytes)))))) - { UNPAIR ; - UNPAIR ; - DIG 2 ; - UNPAIR ; - UNPAIR ; - DUP 3 ; - CAR ; - CAR ; - DUP 4 ; - CDR ; - CAR ; - PAIR ; - DUG 2 ; - DUP ; - DUG 3 ; - DIG 2 ; - UNPAIR ; - SWAP ; - DIG 2 ; - ITER { DUP ; - DUG 2 ; - CDR ; - ITER { SWAP ; - DUP 3 ; - CAR ; - IF_NONE - { UNIT } - { DUP 5 ; - DUP 4 ; - CDR ; - CAR ; - PAIR ; - SENDER ; - DIG 2 ; - PAIR ; - PAIR ; - DUP 6 ; - SWAP ; - EXEC } ; - DROP ; - PUSH nat 1 ; - DUP 3 ; - CDR ; - CDR ; - COMPARE ; - GT ; - IF { DROP 2 ; DUP 6 ; FAILWITH } - { PUSH nat 0 ; - DUP 3 ; - CDR ; - CDR ; - COMPARE ; - EQ ; - IF { DUP ; - DIG 2 ; - CDR ; - CAR ; - GET ; - IF_NONE { DROP ; DUP 7 ; FAILWITH } { DROP } } - { SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - DUP 4 ; - CAR ; - IF_NONE - { DROP } - { DUP 3 ; - DUP 3 ; - GET ; - IF_NONE - { DROP 3 ; DUP 8 ; FAILWITH } - { COMPARE ; - EQ ; - IF { NONE address ; SWAP ; UPDATE } { DROP 2 ; DUP 7 ; FAILWITH } } } ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - DIG 2 ; - CAR ; - IF_NONE { DROP } { DIG 2 ; SWAP ; DIG 2 ; SWAP ; SOME ; SWAP ; UPDATE } } } } ; - SWAP ; - DROP } ; - SWAP ; - DROP ; - SWAP ; - DROP ; - DIG 3 ; - DROP ; - DIG 3 ; - DROP ; - DUP 3 ; - CDR ; - DUP 4 ; - CAR ; - CDR ; - DIG 2 ; - PAIR ; - PAIR ; - DUG 2 ; - DROP 2 ; - NIL operation ; - PAIR } ; - SWAP ; - APPLY ; - DIG 3 ; - UNPAIR ; - IF_LEFT - { IF_LEFT - { DIG 2 ; - DROP ; - DIG 2 ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CAR ; - SWAP ; - IF_LEFT - { IF_LEFT - { DROP ; - DIG 2 ; - DROP ; - DUP ; - CDR ; - SENDER ; - MEM ; - IF { DUP ; - CDR ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CDR ; - DUP 3 ; - CAR ; - CAR ; - PUSH bool True ; - SENDER ; - UPDATE ; - PAIR ; - PAIR ; - SWAP ; - CDR ; - NONE unit ; - SENDER ; - UPDATE ; - SWAP ; - CAR ; - PAIR } - { DROP ; PUSH string "NOT_A_PENDING_ADMIN" ; FAILWITH } ; - NIL operation ; - PAIR } - { SWAP ; - DUP ; - DUG 2 ; - DIG 4 ; - SWAP ; - EXEC ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - SWAP ; - DIG 2 ; - CAR ; - CAR ; - PAIR ; - PAIR ; - NIL operation ; - PAIR } } - { IF_LEFT - { SWAP ; - DUP ; - DUG 2 ; - DIG 4 ; - SWAP ; - EXEC ; - DROP ; - PUSH nat 1 ; - DUP 3 ; - CAR ; - CAR ; - SIZE ; - COMPARE ; - EQ ; - IF { DROP 2 ; PUSH string "LAST_ADMIN" ; FAILWITH } - { SWAP ; - DUP ; - DUG 2 ; - CDR ; - DUP 3 ; - CAR ; - CDR ; - DIG 3 ; - CAR ; - CAR ; - DIG 3 ; - PUSH bool False ; - SWAP ; - UPDATE ; - PAIR ; - PAIR } ; - NIL operation ; - PAIR } - { SWAP ; - DUP ; - DUG 2 ; - DIG 4 ; - SWAP ; - EXEC ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - UNIT ; - DIG 2 ; - SWAP ; - SOME ; - SWAP ; - UPDATE ; - SWAP ; - CAR ; - PAIR ; - NIL operation ; - PAIR } } ; - UNPAIR ; - DUP 3 ; - CDR ; - DIG 3 ; - CAR ; - CDR ; - DIG 3 ; - PAIR ; - PAIR ; - SWAP ; - PAIR } - { DIG 4 ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CAR ; - CAR ; - CDR ; - IF { PUSH string "PAUSED" ; FAILWITH } {} ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CDR ; - SWAP ; - IF_LEFT - { IF_LEFT - { DIG 3 ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CAR ; - SWAP ; - DUP ; - CAR ; - MAP { DUP 3 ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - GET ; - IF_NONE - { DROP ; DUP 5 ; FAILWITH } - { SWAP ; - DUP ; - DUG 2 ; - CAR ; - SWAP ; - COMPARE ; - EQ ; - IF { PUSH nat 1 } { PUSH nat 0 } ; - SWAP ; - PAIR } } ; - DIG 2 ; - DROP ; - DIG 4 ; - DROP ; - SWAP ; - CDR ; - PUSH mutez 0 ; - DIG 2 ; - TRANSFER_TOKENS ; - SWAP ; - NIL operation ; - DIG 2 ; - CONS ; - PAIR } - { DIG 4 ; - DROP ; - MAP { DUP ; - CDR ; - MAP { DUP ; - CDR ; - CDR ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - PAIR ; - SWAP ; - CAR ; - SOME ; - PAIR } ; - SWAP ; - CAR ; - SOME ; - PAIR } ; - SWAP ; - LAMBDA - (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit))) - unit - { UNPAIR ; - UNPAIR ; - DIG 2 ; - UNPAIR ; - DUP 4 ; - DUP 4 ; - COMPARE ; - EQ ; - IF { DROP 4 ; UNIT } - { DIG 3 ; - PAIR ; - DIG 2 ; - PAIR ; - MEM ; - IF { UNIT } { PUSH string "FA2_NOT_OPERATOR" ; FAILWITH } } } ; - DIG 2 ; - PAIR ; - PAIR ; - DIG 2 ; - SWAP ; - EXEC } } - { DIG 3 ; - DROP ; - DIG 3 ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - SWAP ; - SENDER ; - DUG 2 ; - ITER { SWAP ; - DUP 3 ; - DUP 3 ; - IF_LEFT {} {} ; - CAR ; - COMPARE ; - EQ ; - IF {} { PUSH string "FA2_NOT_OWNER" ; FAILWITH } ; - SWAP ; - IF_LEFT - { SWAP ; - UNIT ; - SOME ; - DUP 3 ; - CDR ; - CDR ; - DUP 4 ; - CDR ; - CAR ; - PAIR ; - DIG 3 ; - CAR ; - PAIR ; - UPDATE } - { DUP ; - DUG 2 ; - CDR ; - CDR ; - DUP 3 ; - CDR ; - CAR ; - PAIR ; - DIG 2 ; - CAR ; - PAIR ; - NONE unit ; - SWAP ; - UPDATE } } ; - SWAP ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CDR ; - SWAP ; - PAIR ; - SWAP ; - CAR ; - PAIR ; - NIL operation ; - PAIR } ; - UNPAIR ; - DUP 3 ; - CDR ; - DIG 2 ; - DIG 3 ; - CAR ; - CAR ; - PAIR ; - PAIR ; - SWAP ; - PAIR } } - { DIG 3 ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CAR ; - DIG 4 ; - SWAP ; - EXEC ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CDR ; - NIL (pair (option address) (pair nat nat)) ; - PAIR ; - SWAP ; - ITER { DUP ; - DUG 2 ; - CAR ; - CAR ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - CAR ; - SWAP ; - DUP ; - DUG 2 ; - MEM ; - IF { DROP 3 ; PUSH string "FA2_INVALID_TOKEN_ID" ; FAILWITH } - { PUSH nat 1 ; - SWAP ; - DUP ; - DUG 2 ; - ADD ; - DUP 3 ; - CDR ; - DUP 4 ; - CDR ; - CDR ; - CDR ; - DUP 6 ; - CAR ; - DUP 5 ; - SWAP ; - SOME ; - SWAP ; - UPDATE ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - PAIR ; - SWAP ; - CAR ; - PAIR ; - DUP ; - CDR ; - DUG 2 ; - CAR ; - CAR ; - PAIR ; - PAIR ; - DIG 2 ; - CAR ; - PUSH nat 1 ; - DIG 3 ; - PAIR ; - DIG 3 ; - CDR ; - SOME ; - PAIR ; - CONS ; - PAIR } } ; - DUP ; - CDR ; - LAMBDA - (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit))) - unit - { DROP ; UNIT } ; - NIL (pair (option address) (list (pair (option address) (pair nat nat)))) ; - DIG 3 ; - CAR ; - NONE address ; - PAIR ; - CONS ; - PAIR ; - PAIR ; - DIG 2 ; - SWAP ; - EXEC ; - UNPAIR ; - DUP 3 ; - CDR ; - DIG 2 ; - DIG 3 ; - CAR ; - CAR ; - PAIR ; - PAIR ; - SWAP ; - PAIR } } } - diff --git a/packages/minter-contracts/bin/fa2_multi_nft_asset_no_admin.tz b/packages/minter-contracts/bin/fa2_multi_nft_asset_no_admin.tz deleted file mode 100644 index 458153ec3..000000000 --- a/packages/minter-contracts/bin/fa2_multi_nft_asset_no_admin.tz +++ /dev/null @@ -1,447 +0,0 @@ -{ parameter - (or (or (never %admin) - (or %assets - (or (pair %balance_of - (list %requests (pair (address %owner) (nat %token_id))) - (contract %callback - (list (pair (pair %request (address %owner) (nat %token_id)) (nat %balance))))) - (list %transfer - (pair (address %from_) - (list %txs (pair (address %to_) (pair (nat %token_id) (nat %amount))))))) - (list %update_operators - (or (pair %add_operator (address %owner) (pair (address %operator) (nat %token_id))) - (pair %remove_operator (address %owner) (pair (address %operator) (nat %token_id))))))) - (list %mint - (pair (pair %token_metadata (nat %token_id) (map %token_info string bytes)) - (address %owner)))) ; - storage - (pair (pair (unit %admin) - (pair %assets - (pair (big_map %ledger nat address) (nat %next_token_id)) - (pair (big_map %operators (pair address (pair address nat)) unit) - (big_map %token_metadata nat (pair (nat %token_id) (map %token_info string bytes)))))) - (big_map %metadata string bytes)) ; - code { PUSH string "FA2_TOKEN_UNDEFINED" ; - PUSH string "FA2_INSUFFICIENT_BALANCE" ; - SWAP ; - DUP ; - DUG 2 ; - SWAP ; - PAIR ; - LAMBDA - (pair (pair string string) - (pair (pair (list (pair (option address) (list (pair (option address) (pair nat nat))))) - (lambda - (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit))) - unit)) - (pair (pair (big_map nat address) nat) - (pair (big_map (pair address (pair address nat)) unit) - (big_map nat (pair nat (map string bytes))))))) - (pair (list operation) - (pair (pair (big_map nat address) nat) - (pair (big_map (pair address (pair address nat)) unit) - (big_map nat (pair nat (map string bytes)))))) - { UNPAIR ; - UNPAIR ; - DIG 2 ; - UNPAIR ; - UNPAIR ; - DUP 3 ; - CAR ; - CAR ; - DUP 4 ; - CDR ; - CAR ; - PAIR ; - DUG 2 ; - DUP ; - DUG 3 ; - DIG 2 ; - UNPAIR ; - SWAP ; - DIG 2 ; - ITER { DUP ; - DUG 2 ; - CDR ; - ITER { SWAP ; - DUP 3 ; - CAR ; - IF_NONE - { UNIT } - { DUP 5 ; - DUP 4 ; - CDR ; - CAR ; - PAIR ; - SENDER ; - DIG 2 ; - PAIR ; - PAIR ; - DUP 6 ; - SWAP ; - EXEC } ; - DROP ; - PUSH nat 1 ; - DUP 3 ; - CDR ; - CDR ; - COMPARE ; - GT ; - IF { DROP 2 ; DUP 6 ; FAILWITH } - { PUSH nat 0 ; - DUP 3 ; - CDR ; - CDR ; - COMPARE ; - EQ ; - IF { DUP ; - DIG 2 ; - CDR ; - CAR ; - GET ; - IF_NONE { DROP ; DUP 7 ; FAILWITH } { DROP } } - { SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - DUP 4 ; - CAR ; - IF_NONE - { DROP } - { DUP 3 ; - DUP 3 ; - GET ; - IF_NONE - { DROP 3 ; DUP 8 ; FAILWITH } - { COMPARE ; - EQ ; - IF { NONE address ; SWAP ; UPDATE } { DROP 2 ; DUP 7 ; FAILWITH } } } ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - DIG 2 ; - CAR ; - IF_NONE { DROP } { DIG 2 ; SWAP ; DIG 2 ; SWAP ; SOME ; SWAP ; UPDATE } } } } ; - SWAP ; - DROP } ; - SWAP ; - DROP ; - SWAP ; - DROP ; - DIG 3 ; - DROP ; - DIG 3 ; - DROP ; - DUP 3 ; - CDR ; - DUP 4 ; - CAR ; - CDR ; - DIG 2 ; - PAIR ; - PAIR ; - DUG 2 ; - DROP 2 ; - NIL operation ; - PAIR } ; - SWAP ; - APPLY ; - DIG 2 ; - UNPAIR ; - IF_LEFT - { IF_LEFT - { DIG 2 ; - DROP ; - DIG 2 ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CAR ; - SWAP ; - DROP ; - NIL operation ; - DUP 3 ; - CDR ; - DIG 3 ; - CAR ; - CDR ; - DIG 3 ; - PAIR ; - PAIR ; - SWAP ; - PAIR } - { SWAP ; - DUP ; - DUG 2 ; - CAR ; - CDR ; - SWAP ; - IF_LEFT - { IF_LEFT - { DIG 3 ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CAR ; - SWAP ; - DUP ; - CAR ; - MAP { DUP 3 ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - GET ; - IF_NONE - { DROP ; DUP 5 ; FAILWITH } - { SWAP ; - DUP ; - DUG 2 ; - CAR ; - SWAP ; - COMPARE ; - EQ ; - IF { PUSH nat 1 } { PUSH nat 0 } ; - SWAP ; - PAIR } } ; - DIG 2 ; - DROP ; - DIG 4 ; - DROP ; - SWAP ; - CDR ; - PUSH mutez 0 ; - DIG 2 ; - TRANSFER_TOKENS ; - SWAP ; - NIL operation ; - DIG 2 ; - CONS ; - PAIR } - { DIG 4 ; - DROP ; - MAP { DUP ; - CDR ; - MAP { DUP ; - CDR ; - CDR ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - PAIR ; - SWAP ; - CAR ; - SOME ; - PAIR } ; - SWAP ; - CAR ; - SOME ; - PAIR } ; - SWAP ; - LAMBDA - (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit))) - unit - { UNPAIR ; - UNPAIR ; - DIG 2 ; - UNPAIR ; - DUP 4 ; - DUP 4 ; - COMPARE ; - EQ ; - IF { DROP 4 ; UNIT } - { DIG 3 ; - PAIR ; - DIG 2 ; - PAIR ; - MEM ; - IF { UNIT } { PUSH string "FA2_NOT_OPERATOR" ; FAILWITH } } } ; - DIG 2 ; - PAIR ; - PAIR ; - DIG 2 ; - SWAP ; - EXEC } } - { DIG 3 ; - DROP ; - DIG 3 ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - SWAP ; - SENDER ; - DUG 2 ; - ITER { SWAP ; - DUP 3 ; - DUP 3 ; - IF_LEFT {} {} ; - CAR ; - COMPARE ; - EQ ; - IF {} { PUSH string "FA2_NOT_OWNER" ; FAILWITH } ; - SWAP ; - IF_LEFT - { SWAP ; - UNIT ; - SOME ; - DUP 3 ; - CDR ; - CDR ; - DUP 4 ; - CDR ; - CAR ; - PAIR ; - DIG 3 ; - CAR ; - PAIR ; - UPDATE } - { DUP ; - DUG 2 ; - CDR ; - CDR ; - DUP 3 ; - CDR ; - CAR ; - PAIR ; - DIG 2 ; - CAR ; - PAIR ; - NONE unit ; - SWAP ; - UPDATE } } ; - SWAP ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CDR ; - SWAP ; - PAIR ; - SWAP ; - CAR ; - PAIR ; - NIL operation ; - PAIR } ; - UNPAIR ; - DUP 3 ; - CDR ; - DIG 2 ; - DIG 3 ; - CAR ; - CAR ; - PAIR ; - PAIR ; - SWAP ; - PAIR } } - { DIG 3 ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CDR ; - NIL (pair (option address) (pair nat nat)) ; - PAIR ; - SWAP ; - ITER { DUP ; - DUG 2 ; - CAR ; - CAR ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - CAR ; - SWAP ; - DUP ; - DUG 2 ; - MEM ; - IF { DROP 3 ; PUSH string "FA2_INVALID_TOKEN_ID" ; FAILWITH } - { PUSH nat 1 ; - SWAP ; - DUP ; - DUG 2 ; - ADD ; - DUP 3 ; - CDR ; - DUP 4 ; - CDR ; - CDR ; - CDR ; - DUP 6 ; - CAR ; - DUP 5 ; - SWAP ; - SOME ; - SWAP ; - UPDATE ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - PAIR ; - SWAP ; - CAR ; - PAIR ; - DUP ; - CDR ; - DUG 2 ; - CAR ; - CAR ; - PAIR ; - PAIR ; - DIG 2 ; - CAR ; - PUSH nat 1 ; - DIG 3 ; - PAIR ; - DIG 3 ; - CDR ; - SOME ; - PAIR ; - CONS ; - PAIR } } ; - DUP ; - CDR ; - LAMBDA - (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit))) - unit - { DROP ; UNIT } ; - NIL (pair (option address) (list (pair (option address) (pair nat nat)))) ; - DIG 3 ; - CAR ; - NONE address ; - PAIR ; - CONS ; - PAIR ; - PAIR ; - DIG 2 ; - SWAP ; - EXEC ; - UNPAIR ; - DUP 3 ; - CDR ; - DIG 2 ; - DIG 3 ; - CAR ; - CAR ; - PAIR ; - PAIR ; - SWAP ; - PAIR } } } - diff --git a/packages/minter-contracts/bin/fa2_multi_nft_asset_non_pausable_simple_admin.tz b/packages/minter-contracts/bin/fa2_multi_nft_asset_non_pausable_simple_admin.tz index b2453efcf..f9b47a2fe 100644 --- a/packages/minter-contracts/bin/fa2_multi_nft_asset_non_pausable_simple_admin.tz +++ b/packages/minter-contracts/bin/fa2_multi_nft_asset_non_pausable_simple_admin.tz @@ -1,19 +1,21 @@ { parameter - (or (or (or %admin (unit %confirm_admin) (address %set_admin)) - (or %assets - (or (pair %balance_of - (list %requests (pair (address %owner) (nat %token_id))) - (contract %callback - (list (pair (pair %request (address %owner) (nat %token_id)) (nat %balance))))) - (list %transfer - (pair (address %from_) - (list %txs (pair (address %to_) (pair (nat %token_id) (nat %amount))))))) - (list %update_operators - (or (pair %add_operator (address %owner) (pair (address %operator) (nat %token_id))) - (pair %remove_operator (address %owner) (pair (address %operator) (nat %token_id))))))) - (list %mint - (pair (pair %token_metadata (nat %token_id) (map %token_info string bytes)) - (address %owner)))) ; + (or (or (or (or %admin (unit %confirm_admin) (address %set_admin)) + (or %assets + (or (pair %balance_of + (list %requests (pair (address %owner) (nat %token_id))) + (contract %callback + (list (pair (pair %request (address %owner) (nat %token_id)) (nat %balance))))) + (list %transfer + (pair (address %from_) + (list %txs (pair (address %to_) (pair (nat %token_id) (nat %amount))))))) + (list %update_operators + (or (pair %add_operator (address %owner) (pair (address %operator) (nat %token_id))) + (pair %remove_operator (address %owner) (pair (address %operator) (nat %token_id))))))) + (or (pair %burn nat address) + (list %mint + (pair (pair %token_metadata (nat %token_id) (map %token_info string bytes)) + (address %owner))))) + (list %update_metadata (pair (nat %token_id) (map %token_info string bytes)))) ; storage (pair (pair (pair %admin (address %admin) (option %pending_admin address)) (pair %assets @@ -161,235 +163,426 @@ UNPAIR ; IF_LEFT { IF_LEFT - { DIG 2 ; - DROP ; - DIG 2 ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CAR ; - SWAP ; - IF_LEFT - { DROP ; + { IF_LEFT + { DIG 2 ; + DROP ; DIG 2 ; DROP ; - CDR ; - IF_NONE - { PUSH string "NO_PENDING_ADMIN" ; FAILWITH } - { SENDER ; - COMPARE ; - EQ ; - IF { NONE address ; SENDER ; PAIR } - { PUSH string "NOT_A_PENDING_ADMIN" ; FAILWITH } } ; - NIL operation ; - PAIR } - { SWAP ; + SWAP ; DUP ; DUG 2 ; - DIG 4 ; - SWAP ; - EXEC ; - DROP ; - SOME ; - SWAP ; CAR ; - PAIR ; - NIL operation ; - PAIR } ; - UNPAIR ; - DUP 3 ; - CDR ; - DIG 3 ; - CAR ; - CDR ; - DIG 3 ; - PAIR ; - PAIR ; - SWAP ; - PAIR } - { DIG 4 ; - DROP ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CDR ; - SWAP ; - IF_LEFT - { IF_LEFT - { DIG 3 ; + CAR ; + SWAP ; + IF_LEFT + { DROP ; + DIG 2 ; DROP ; - SWAP ; + CDR ; + IF_NONE + { PUSH string "NO_PENDING_ADMIN" ; FAILWITH } + { SENDER ; + COMPARE ; + EQ ; + IF { NONE address ; SENDER ; PAIR } + { PUSH string "NOT_A_PENDING_ADMIN" ; FAILWITH } } ; + NIL operation ; + PAIR } + { SWAP ; DUP ; DUG 2 ; - CAR ; - CAR ; + DIG 4 ; + SWAP ; + EXEC ; + DROP ; + SOME ; SWAP ; - DUP ; CAR ; - MAP { DUP 3 ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - GET ; - IF_NONE - { DROP ; DUP 5 ; FAILWITH } - { SWAP ; + PAIR ; + NIL operation ; + PAIR } ; + UNPAIR ; + DUP 3 ; + CDR ; + DIG 3 ; + CAR ; + CDR ; + DIG 3 ; + PAIR ; + PAIR ; + SWAP ; + PAIR } + { DIG 4 ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + CDR ; + SWAP ; + IF_LEFT + { IF_LEFT + { DIG 3 ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + CAR ; + SWAP ; + DUP ; + CAR ; + MAP { DUP 3 ; + SWAP ; DUP ; DUG 2 ; - CAR ; - SWAP ; - COMPARE ; - EQ ; - IF { PUSH nat 1 } { PUSH nat 0 } ; + CDR ; + GET ; + IF_NONE + { DROP ; DUP 5 ; FAILWITH } + { SWAP ; + DUP ; + DUG 2 ; + CAR ; + SWAP ; + COMPARE ; + EQ ; + IF { PUSH nat 1 } { PUSH nat 0 } ; + SWAP ; + PAIR } } ; + DIG 2 ; + DROP ; + DIG 4 ; + DROP ; + SWAP ; + CDR ; + PUSH mutez 0 ; + DIG 2 ; + TRANSFER_TOKENS ; + SWAP ; + NIL operation ; + DIG 2 ; + CONS ; + PAIR } + { DIG 4 ; + DROP ; + MAP { DUP ; + CDR ; + MAP { DUP ; + CDR ; + CDR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + PAIR ; + SWAP ; + CAR ; + SOME ; + PAIR } ; SWAP ; - PAIR } } ; - DIG 2 ; + CAR ; + SOME ; + PAIR } ; + SWAP ; + LAMBDA + (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit))) + unit + { UNPAIR ; + UNPAIR ; + DIG 2 ; + UNPAIR ; + DUP 4 ; + DUP 4 ; + COMPARE ; + EQ ; + IF { DROP 4 ; UNIT } + { DIG 3 ; + PAIR ; + DIG 2 ; + PAIR ; + MEM ; + IF { UNIT } { PUSH string "FA2_NOT_OPERATOR" ; FAILWITH } } } ; + DIG 2 ; + PAIR ; + PAIR ; + DIG 2 ; + SWAP ; + EXEC } } + { DIG 3 ; DROP ; - DIG 4 ; + DIG 3 ; DROP ; SWAP ; + DUP ; + DUG 2 ; CDR ; - PUSH mutez 0 ; - DIG 2 ; - TRANSFER_TOKENS ; + CAR ; + SWAP ; + SENDER ; + DUG 2 ; + ITER { SWAP ; + DUP 3 ; + DUP 3 ; + IF_LEFT {} {} ; + CAR ; + COMPARE ; + EQ ; + IF {} { PUSH string "FA2_NOT_OWNER" ; FAILWITH } ; + SWAP ; + IF_LEFT + { SWAP ; + UNIT ; + SOME ; + DUP 3 ; + CDR ; + CDR ; + DUP 4 ; + CDR ; + CAR ; + PAIR ; + DIG 3 ; + CAR ; + PAIR ; + UPDATE } + { DUP ; + DUG 2 ; + CDR ; + CDR ; + DUP 3 ; + CDR ; + CAR ; + PAIR ; + DIG 2 ; + CAR ; + PAIR ; + NONE unit ; + SWAP ; + UPDATE } } ; SWAP ; - NIL operation ; - DIG 2 ; - CONS ; - PAIR } - { DIG 4 ; DROP ; - MAP { DUP ; - CDR ; - MAP { DUP ; - CDR ; - CDR ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - PAIR ; - SWAP ; - CAR ; - SOME ; - PAIR } ; - SWAP ; - CAR ; - SOME ; - PAIR } ; SWAP ; - LAMBDA - (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit))) - unit - { UNPAIR ; - UNPAIR ; - DIG 2 ; - UNPAIR ; - DUP 4 ; - DUP 4 ; - COMPARE ; - EQ ; - IF { DROP 4 ; UNIT } - { DIG 3 ; - PAIR ; - DIG 2 ; - PAIR ; - MEM ; - IF { UNIT } { PUSH string "FA2_NOT_OPERATOR" ; FAILWITH } } } ; - DIG 2 ; - PAIR ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + SWAP ; PAIR ; - DIG 2 ; SWAP ; - EXEC } } - { DIG 3 ; - DROP ; + CAR ; + PAIR ; + NIL operation ; + PAIR } ; + UNPAIR ; + DUP 3 ; + CDR ; + DIG 2 ; DIG 3 ; + CAR ; + CAR ; + PAIR ; + PAIR ; + SWAP ; + PAIR } } + { DIG 3 ; + DROP ; + DIG 3 ; + DROP ; + IF_LEFT + { DIG 2 ; DROP ; + UNPAIR ; + DUP 3 ; + CAR ; + CDR ; + CAR ; + CAR ; + NONE address ; + DUP 3 ; + GET_AND_UPDATE ; + IF_NONE + { DIG 2 ; DROP ; PUSH string "WRONG_ID" ; FAILWITH } + { DIG 3 ; + COMPARE ; + EQ ; + DUP 4 ; + CAR ; + CDR ; + CDR ; + CAR ; + PUSH nat 0 ; + SENDER ; + PAIR ; + DUP 6 ; + CAR ; + CAR ; + CAR ; + PAIR ; + MEM ; + AND ; + IF { NIL operation } { PUSH string "NOT_BURNER" ; FAILWITH } } ; + DUP 4 ; + CDR ; + DUP 5 ; + CAR ; + CDR ; + UNPAIR ; + CDR ; + DIG 4 ; + PAIR ; + PAIR ; + DUP 5 ; + CAR ; + CDR ; + CDR ; + CDR ; + NONE (pair nat (map string bytes)) ; + DIG 5 ; + UPDATE ; SWAP ; DUP ; DUG 2 ; CDR ; CAR ; + PAIR ; + SWAP ; + CAR ; + PAIR ; + DIG 3 ; + CAR ; + CAR ; + PAIR ; + PAIR ; SWAP ; + PAIR } + { SWAP ; + DUP ; + DUG 2 ; + CAR ; + CDR ; + CDR ; + CAR ; + PUSH nat 0 ; SENDER ; + PAIR ; + DUP 4 ; + CAR ; + CAR ; + CAR ; + PAIR ; + MEM ; + IF {} { PUSH string "NOT_MINTER" ; FAILWITH } ; + SWAP ; + DUP ; DUG 2 ; + CAR ; + CDR ; + NIL (pair (option address) (pair nat nat)) ; + PAIR ; + SWAP ; ITER { SWAP ; - DUP 3 ; - DUP 3 ; - IF_LEFT {} {} ; + DUP ; + CDR ; CAR ; - COMPARE ; - EQ ; - IF {} { PUSH string "FA2_NOT_OWNER" ; FAILWITH } ; + CDR ; SWAP ; - IF_LEFT - { SWAP ; - UNIT ; - SOME ; - DUP 3 ; - CDR ; - CDR ; - DUP 4 ; - CDR ; - CAR ; - PAIR ; - DIG 3 ; - CAR ; - PAIR ; - UPDATE } - { DUP ; - DUG 2 ; - CDR ; - CDR ; - DUP 3 ; - CDR ; - CAR ; - PAIR ; - DIG 2 ; - CAR ; - PAIR ; - NONE unit ; - SWAP ; - UPDATE } } ; - SWAP ; - DROP ; - SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + CAR ; + SWAP ; + DUP ; + DUG 2 ; + MEM ; + IF { DROP 3 ; PUSH string "FA2_INVALID_TOKEN_ID" ; FAILWITH } + { PUSH nat 1 ; + SWAP ; + DUP ; + DUG 2 ; + ADD ; + DUP 3 ; + CDR ; + DUP 4 ; + CDR ; + CDR ; + CDR ; + DUP 6 ; + CAR ; + CDR ; + DUP 5 ; + PAIR ; + DUP 5 ; + SWAP ; + SOME ; + SWAP ; + UPDATE ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + PAIR ; + SWAP ; + CAR ; + PAIR ; + DUP ; + CDR ; + DUG 2 ; + CAR ; + CAR ; + PAIR ; + PAIR ; + DIG 2 ; + CAR ; + PUSH nat 1 ; + DIG 3 ; + PAIR ; + DIG 3 ; + CDR ; + SOME ; + PAIR ; + CONS ; + PAIR } } ; DUP ; - DUG 2 ; - CDR ; CDR ; - SWAP ; + LAMBDA + (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit))) + unit + { DROP ; UNIT } ; + NIL (pair (option address) (list (pair (option address) (pair nat nat)))) ; + DIG 3 ; + CAR ; + NONE address ; + PAIR ; + CONS ; PAIR ; + PAIR ; + DIG 2 ; SWAP ; + EXEC ; + UNPAIR ; + DUP 3 ; + CDR ; + DIG 2 ; + DIG 3 ; + CAR ; CAR ; PAIR ; - NIL operation ; - PAIR } ; - UNPAIR ; - DUP 3 ; - CDR ; - DIG 2 ; - DIG 3 ; - CAR ; - CAR ; - PAIR ; - PAIR ; - SWAP ; - PAIR } } - { DIG 3 ; + PAIR ; + SWAP ; + PAIR } } } + { DIG 2 ; + DROP ; + DIG 2 ; DROP ; SWAP ; DUP ; DUG 2 ; CAR ; CAR ; - DIG 4 ; + DIG 3 ; SWAP ; EXEC ; DROP ; @@ -398,95 +591,31 @@ DUG 2 ; CAR ; CDR ; - NIL (pair (option address) (pair nat nat)) ; - PAIR ; + CDR ; + CDR ; + SWAP ; + ITER { DUP ; DUG 2 ; SOME ; DIG 2 ; CAR ; UPDATE } ; SWAP ; - ITER { DUP ; - DUG 2 ; - CAR ; - CAR ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - CAR ; - SWAP ; - DUP ; - DUG 2 ; - MEM ; - IF { DROP 3 ; PUSH string "FA2_INVALID_TOKEN_ID" ; FAILWITH } - { PUSH nat 1 ; - SWAP ; - DUP ; - DUG 2 ; - ADD ; - DUP 3 ; - CDR ; - DUP 4 ; - CDR ; - CDR ; - CDR ; - DUP 6 ; - CAR ; - DUP 5 ; - SWAP ; - SOME ; - SWAP ; - UPDATE ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - PAIR ; - SWAP ; - CAR ; - PAIR ; - DUP ; - CDR ; - DUG 2 ; - CAR ; - CAR ; - PAIR ; - PAIR ; - DIG 2 ; - CAR ; - PUSH nat 1 ; - DIG 3 ; - PAIR ; - DIG 3 ; - CDR ; - SOME ; - PAIR ; - CONS ; - PAIR } } ; DUP ; + DUG 2 ; + CDR ; + SWAP ; + DUP 3 ; + CAR ; + CDR ; CDR ; - LAMBDA - (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit))) - unit - { DROP ; UNIT } ; - NIL (pair (option address) (list (pair (option address) (pair nat nat)))) ; - DIG 3 ; CAR ; - NONE address ; - PAIR ; - CONS ; - PAIR ; PAIR ; - DIG 2 ; - SWAP ; - EXEC ; - UNPAIR ; DUP 3 ; + CAR ; CDR ; + CAR ; + PAIR ; DIG 2 ; - DIG 3 ; CAR ; CAR ; PAIR ; PAIR ; - SWAP ; + NIL operation ; PAIR } } } diff --git a/packages/minter-contracts/bin/fa2_multi_nft_faucet.tz b/packages/minter-contracts/bin/fa2_multi_nft_faucet.tz index 04b77f317..65e833071 100644 --- a/packages/minter-contracts/bin/fa2_multi_nft_faucet.tz +++ b/packages/minter-contracts/bin/fa2_multi_nft_faucet.tz @@ -325,10 +325,11 @@ NIL (pair (option address) (pair nat nat)) ; PAIR ; SWAP ; - ITER { DUP ; - DUG 2 ; - CAR ; + ITER { SWAP ; + DUP ; + CDR ; CAR ; + CDR ; SWAP ; DUP ; DUG 2 ; @@ -353,6 +354,9 @@ CDR ; DUP 6 ; CAR ; + CDR ; + DUP 5 ; + PAIR ; DUP 5 ; SWAP ; SOME ; diff --git a/packages/minter-contracts/buy_sell_test_data_Lambda.txt b/packages/minter-contracts/buy_sell_test_data_Lambda.txt new file mode 100644 index 000000000..6280421c0 --- /dev/null +++ b/packages/minter-contracts/buy_sell_test_data_Lambda.txt @@ -0,0 +1,80 @@ +Buy Sell Test + +(admin, alice, bob, charlie) +("tz1PwgnoAyphW2iFdbnnYgwWRcNTpAaH7gVY","tz1TZpbZZZTKiJVh7ANXCLKFT3TkADzxRZWM","tz1gtKKyvwQ6u54sbVzano58mFZLqERaCgyW","tz1RLYCL2F82JxXtEGZmtLRDMD4Pe9SRYLZo") + +nft storage +Pair { Pair (Pair "tz1PwgnoAyphW2iFdbnnYgwWRcNTpAaH7gVY" False) None; Pair { Elt 0 "tz1PwgnoAyphW2iFdbnnYgwWRcNTpAaH7gVY" } 1; { }; { } } { } + +nft address +KT1GSkFwEJM9GdMPo4HmwVfU4j5fa2MLrZGD + +bonding curve storage +{ Pair (Pair "tz1PwgnoAyphW2iFdbnnYgwWRcNTpAaH7gVY" False) None; "KT1GSkFwEJM9GdMPo4HmwVfU4j5fa2MLrZGD"; 100; 0; { Elt "decimals" 0x3132; Elt "name" 0x546869732069732061207465737421205b6e616d655d; Elt "symbol" 0x746573745f73796d626f6c }; 100; { LEFT mutez; + PUSH + (list mutez) + { 10; 60; 170; 340; 570; 860 }; + SWAP; + LOOP_LEFT { PUSH nat 1; + SWAP; + SUB; + ISNAT; + IF_NONE { IF_CONS { RIGHT nat } + { PUSH string "list too short for index"; + FAILWITH } } + { SWAP; + IF_CONS { DROP; + SWAP; + LEFT mutez } + { PUSH string "list too short for index"; + FAILWITH } } }; + SWAP; + DROP }; 0 } + +bonding curve address +KT1Dzpd58KJ5Jr5bXKeKaZTtYkJJRn8rtdD1 + +admin -> nft: update_operators +{ Left { "tz1PwgnoAyphW2iFdbnnYgwWRcNTpAaH7gVY"; "KT1Dzpd58KJ5Jr5bXKeKaZTtYkJJRn8rtdD1"; 0 } } + +buyer -> bondingCurve: buy +buyer: +tz1TZpbZZZTKiJVh7ANXCLKFT3TkADzxRZWM +amount: +110 + +buyer -> bondingCurve: buy +buyer: +tz1gtKKyvwQ6u54sbVzano58mFZLqERaCgyW +amount: +160 + +buyer -> bondingCurve: buy +buyer: +tz1RLYCL2F82JxXtEGZmtLRDMD4Pe9SRYLZo +amount: +270 + +seller -> bondingCurve: sell +seller: +tz1RLYCL2F82JxXtEGZmtLRDMD4Pe9SRYLZo +parameter: +3 + +seller -> bondingCurve: sell +seller: +tz1gtKKyvwQ6u54sbVzano58mFZLqERaCgyW +parameter: +2 + +seller -> bondingCurve: sell +seller: +tz1TZpbZZZTKiJVh7ANXCLKFT3TkADzxRZWM +parameter: +1 + +admin -> bondingCurve: withdraw +admin: +tz1PwgnoAyphW2iFdbnnYgwWRcNTpAaH7gVY +parameter: +Unit diff --git a/packages/minter-contracts/buy_sell_test_data_Piecewise.txt b/packages/minter-contracts/buy_sell_test_data_Piecewise.txt new file mode 100644 index 000000000..e190ce941 --- /dev/null +++ b/packages/minter-contracts/buy_sell_test_data_Piecewise.txt @@ -0,0 +1,61 @@ +Buy Sell Test + +(admin, alice, bob, charlie) +("tz1PwgnoAyphW2iFdbnnYgwWRcNTpAaH7gVY","tz1TZpbZZZTKiJVh7ANXCLKFT3TkADzxRZWM","tz1gtKKyvwQ6u54sbVzano58mFZLqERaCgyW","tz1RLYCL2F82JxXtEGZmtLRDMD4Pe9SRYLZo") + +nft storage +Pair { Pair (Pair "tz1PwgnoAyphW2iFdbnnYgwWRcNTpAaH7gVY" False) None; Pair { Elt 0 "tz1PwgnoAyphW2iFdbnnYgwWRcNTpAaH7gVY" } 1; { }; { } } { } + +nft address +KT1GSkFwEJM9GdMPo4HmwVfU4j5fa2MLrZGD + +bonding curve storage +{ Pair (Pair "tz1PwgnoAyphW2iFdbnnYgwWRcNTpAaH7gVY" False) None; "KT1GSkFwEJM9GdMPo4HmwVfU4j5fa2MLrZGD"; 100; 0; { Elt "decimals" 0x3132; Elt "name" 0x546869732069732061207465737421205b6e616d655d; Elt "symbol" 0x746573745f73796d626f6c }; 100; Pair { } { 10; 20; 30 }; 0 } + +bonding curve address +KT1PyKosHHNNe1FY5Us66trqbz3smK1AgjHv + +admin -> nft: update_operators +{ Left { "tz1PwgnoAyphW2iFdbnnYgwWRcNTpAaH7gVY"; "KT1PyKosHHNNe1FY5Us66trqbz3smK1AgjHv"; 0 } } + +buyer -> bondingCurve: buy +buyer: +tz1TZpbZZZTKiJVh7ANXCLKFT3TkADzxRZWM +amount: +110 + +buyer -> bondingCurve: buy +buyer: +tz1gtKKyvwQ6u54sbVzano58mFZLqERaCgyW +amount: +160 + +buyer -> bondingCurve: buy +buyer: +tz1RLYCL2F82JxXtEGZmtLRDMD4Pe9SRYLZo +amount: +270 + +seller -> bondingCurve: sell +seller: +tz1RLYCL2F82JxXtEGZmtLRDMD4Pe9SRYLZo +parameter: +3 + +seller -> bondingCurve: sell +seller: +tz1gtKKyvwQ6u54sbVzano58mFZLqERaCgyW +parameter: +2 + +seller -> bondingCurve: sell +seller: +tz1TZpbZZZTKiJVh7ANXCLKFT3TkADzxRZWM +parameter: +1 + +admin -> bondingCurve: withdraw +admin: +tz1PwgnoAyphW2iFdbnnYgwWRcNTpAaH7gVY +parameter: +Unit diff --git a/packages/minter-contracts/ligo/src/bonding_curve/README.md b/packages/minter-contracts/ligo/src/bonding_curve/README.md new file mode 100644 index 000000000..5ac2ebe62 --- /dev/null +++ b/packages/minter-contracts/ligo/src/bonding_curve/README.md @@ -0,0 +1,221 @@ +# Bonding Curve Contract for Non-Fungible Tokens + +The bonding curve contract interfaces with any NFT marketplace contract that +supports minting and burning tokens, allowing users to buy and sell tokens +indefinitely without creating new auctions. + +## Bonding Curve Storage + +- `admin : admin_storage` + + Simple admin storage + +- `market_contract : address`: + + FA2 contract supporting `Mint` and `Burn` + + I.e. "marketplace" contract + +- `auction_price : tez`: + + Final price of the auction + +- `token_index : nat`: + + Number of tokens sold. This number must be positive to sell back tokens. + + I.e. `token_index` must be `20` to sell back up to `20` tokens after an auction where users bought at least `20` tokens + + You may want to add a constant piecewise polynomial segment at the beginning with `tokens_sold_in_auction` length + and value `token_final_cost_in_auction` + +- `token_metadata : token_metadata`: + + Token metadata for minting + + When `Buy` or `Buy_offchain` are called, this `token_metadata` is used to + mint a NFT on the `market_contract` (with a unique token id) + +- `basis_points : nat`: + + The percentage (in basis points) cost of buying and selling a token at the same index + + In other words, the fee in basis points for using this contract + +- `cost_mutez : piecewise_polynomial`: + + The bonding curve formula, as a piecewise polynomial + + See a definition and explanation of the `piecewise_polynomial` type in `Appendix A` + cost_mutez : piecewise_polynomial; + +- `unclaimed : tez`: + + Any tez that's unclaimed as a result of the `basis_points` fee + + +## Bonding Curve Entrypoints + +- Simple Admin entrypoints, i.e. `update_admin`, etc. + +- `Set_deletgate` + + Parameter: `key_hash option` + + Spec: + * Admin-only + * Set the delegate of the contract to the given `key_hash` if present, or unset if `None` + +- `Withdraw` + + Parameter: `unit` + + Spec: + * Admin-only + * The amount of tez in `unclaimed` (in storage) is sent to the admin + +- `Buy` + + Parameter: `unit` + + Spec: + * Requires the bonding curve contract to be a minter + * Requires tez sent equal to the price + * Price is calculated as the sum of + - `auction_price` + - `cost_mutez` applied to `token_index` + - `(auction_price + cost_mutez) * (basis_points / 10,000)` (integer division) + * Mints token using `token_metadata` from storage to buyer + * Increments `token_index` + * Adds the `basis_points` fee to the `unclaimed` tez in storage + +- `Buy_offchain` + + Parameter: `address` + + Spec: + * Anyone can call it (equivalent to calling buy as admin and then transferring to the offchain_address) + * Has all requirements of the `Buy` entrypoint + * `address` is the buyer's address, the minted NFT is sent here + * This entrypoint is the same as `Buy`, except the minted token is sent to + the buyer's address + +- `Sell` + + Parameter: + + Spec: + * Required the bonding curve contract to be a minter + * The sender must be the owner of the token to sell + * `token_id` is token to sell + * Price is calculared as in `Buy`, without the `basis_points` fee, i.e. as the sum of: + - `auction_price` + - `cost_mutez` applied to `token_index` + * The token is burned on the FA2 marketplace + * Tez equal to the price is sent to the seller + * The `token_index` is decremented + +- `Sell_offchain:` + + Parameter: `token_id * address` + + Spec: + * Admin-only + * Has all requirements of the `Sell` entrypoint, except can be called by admin without being a token owner + * `token_id` is token to sell + * `address` is the sellers's address, the NFT is burned from this account and the tez are sent here + * This entrypoint is the same as `Sell`, except the token is burned from the + given seller's address and the tez is sent to that seller's address + + +## NFT Contract + +Updated NFT (marketplace) contract on which NFT's are minted/traded + +Storage: no storage type updates, but an update to the semantics: +The token with `token_id = 0` must be held by the admin of the marketplace to +for any minting or burning and any address which is an operator of `token_id = 0` +for the admin address is allowed to mint or burn tokens. Such a user is called a +"minter". + +Entrypoints: +- `Update_metadata` + + Parameter: `token_metadata list` + + Spec: + * Admin-only + * The given `token_metadata`'s are inserted into the + `token_metadata : big_map token_id token_metadata` `big_map`, + updating any currently-present `token_id`'s metadata. + + Misc: this entrypoint can't be used to delete token metadata + +- `Burn`: + + Parameter: `token_id * (bytes * address)` + + Spec: + * Minter-only + * `bytes` is the `symbol` of the NFT to burn + * `address` is the owner of the NFT to burn + * The token is deleted from the ledger and `token_metadata` `big_map` + + +## Appendix A: Piecewise Polynomial's + +### Polynomials: Coefficient Lists + +The Mathematica function [CoefficientList](https://reference.wolfram.com/language/ref/CoefficientList.html) +is implemented equivalently. + +In short, the following polynomial: + +``` +f(x) = a0 * x^0 + a1 * x^1 + .. + an * x^n +``` + +Is represented as the list: + +``` +[a0, a1, .. , an] +``` + +Where the coefficient of `x^i` is the `ith` element of the list. + +This is exactly the definition of the `polynomial` type in ligo: + +``` +type polynomial = + [@layout:comb] + { + coefficients : int list; + } +``` + +Note that coefficients are `int`'s: floating point numbers are not supported in +Michelson, but their behavior may be simulated to arbitrary precision. + + +### Piecewise Polynomials + +Given our representation of polynomials, because we're only concerned with +inputs over the natural numbers, we can represent a piecewise polynomial in the +following way: + +First, we represent a single finite segment as a pair of a natural number length +and a polynomial: + +``` +(length_0, polynomial_0) => polynomial_0(x) | 0 < x < length_0 +``` + +And glue two or more segments together using their length's + +``` +(length_0, polynomial_0) => polynomial_0(x) | 0 <= x < length_0 +(length_1, polynomial_1) => polynomial_1(x) | length_0 <= x < length_0 + length_1 +(length_2, polynomial_2) => polynomial_2(x) | length_0 + length_1 <= x < length_0 + length_1 + length_2 +.. +``` + +Finally, we can account for the infinite remaining segment with a single +polynomial. + +In other words, when `x >= length_0 + length_1 + .. + length_last`, we apply +a polynomial with no segment length: + +``` +(length_0, polynomial_0) => polynomial_0(x) | 0 <= x < length_0 +(length_1, polynomial_1) => polynomial_1(x) | length_0 <= x < length_0 + length_1 +(length_2, polynomial_2) => polynomial_2(x) | length_0 + length_1 <= x < length_0 + length_1 + length_2 +(length_2, last_segment) => last_segment(x) | length_0 + length_1 + length_2 <= x +.. +``` + +Here's it in one place: + +```ocaml +// A segment of a piecewise function +type piecewise_segment = + { + length : piecewise_length; + poly : polynomial; + } + +type piecewise_polynomial = + { + segments : piecewise_segment list; + last_segment : polynomial; + } +``` + diff --git a/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve.mligo b/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve.mligo new file mode 100644 index 000000000..83ba979dc --- /dev/null +++ b/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve.mligo @@ -0,0 +1,546 @@ +// resolve_address +#include "../common.mligo" + +// admin_storage +// admin_entrypoints +#include "../../fa2_modules/admin/simple_admin.mligo" + +// fa2_entry_points +// token_metadata +#include "../../fa2/fa2_interface.mligo" + +// mint_token_param +// mint_tokens_param +#include "../minter_collection/nft/fa2_multi_nft_manager.mligo" + +// //////////////////////////////////////////////////////////////// +// ERRORS +// //////////////////////////////////////////////////////////////// + +(** + storage.unclaimed == 0 +*) +[@inline] +let error_unclaimed_is_zero = "UNCLAIMED=0" + +(** + Wrong tez price sent when buying +*) +[@inline] +let error_wrong_tez_price = "WRONG_TEZ_PRICE" + +(** + run_piecewise_polynomial gave a negative cost +*) +[@inline] +let error_negative_cost = "NEGATIVE_COST" + +(** + market_contract address does not refer to a contract with a '%mint' + entrypoint with type mint_tokens_param +*) +[@inline] +let error_no_mint_entrypoint = "NO_MINT" + +(** + market_contract address does not refer to a contract with a '%burn' + entrypoint with type (token_id * bytes) +*) +[@inline] +let error_no_burn_entrypoint = "NO_BURN" + +(** + token_index = 0, + i.e. no tokens have been sold to the bonding curve, + i.e. there are no tokens to sell +*) +[@inline] +let error_no_token_to_sell = "NO_TOKENS" + +(** + "symbol" field not found in storage.token_metadata +*) +[@inline] +let error_token_metadata_symbol_missing = "NO_SYMBOL" + +(** + Can't return tez to the given seller address because it doesn't have a default + entrypoint to send tez to +*) +[@inline] +let error_no_default_entrypoint = "CANT_RETURN" + +(** + Entrypoint is unimplemented +*) +[@inline] +let error_unimplemented_entrypoint = "UNIMPLEMENTED" + +// //////////////////////////////////////////////////////////////// + +// length of one of the segments in a piecewise_polynomial +type piecewise_length = nat + +// A list of coefficients for a polynomial over the integers. +// +// See run_polynomial for more info. +type polynomial = + [@layout:comb] + { + coefficients : int list; + } + +// Accumulator for run_polynomial +type polynomial_acc = + { + result : int; + + (** x^i for some i + *) + x_pow : int; + } + +// Run a polynomial [a0; a1; .. ; an] on an input 'x' as +// a0 * x^0 + a1 * x^1 + .. + an * x^n +[@inline] +let run_polynomial (poly, x : polynomial * int) + : int = + let output = List.fold_left + (fun (poly_acc, coefficient : polynomial_acc * int) -> + let x_pow = poly_acc.x_pow in + let x_pow_next = x * x_pow in + let output : polynomial_acc = + { + result = poly_acc.result + coefficient * x_pow; + x_pow = x_pow_next; + } + in output + ) + { + result = 0; + x_pow = 1; + } + poly.coefficients in + output.result + +// A segment of a piecewise function +type piecewise_segment = + { + length : piecewise_length; + poly : polynomial; + } + +// The 'piecewise_length' is the length of each segment +// and the formula for each segment is given by the associated 'polynomial' +// +// [ (length_0, polynomial_0); (length_1, polynomial_1); .. ] +// +// -> +// +// f(x) := +// { polynomial_0(x) | 0 <= x < length_0 +// { polynomial_1(x) | length_0 <= x < length_0 + length_1 +// .. +// { polynomial_i(x) | sum_{0 <= j <= i-1} length_j <= x < sum_{0 <= j <= i} length_j +// .. +// { polynomial_last(x) | sum_{0 <= j < last-1} length_j <= x +type piecewise_polynomial = + [@layout:comb] + { + segments : piecewise_segment list; + last_segment : polynomial; + } + +// Accumulator for run_piecewise_polynomial +type piecewise_polynomial_acc = + { + // Current segment offset, i.e. sum of piecewise_length's up to the current + // location in piecewise_polynomial.segments + offset : nat; + + // The input was found in this polynomial when Some + in_poly : polynomial option + } + +// Run a piecewise polynomial by finding the segment for the current offset and +// calling run_polynomial +// +// Given all of the piecewise_length's as a list piecewise_lengths, the current +// segment can be considered the unique (n) for which the following holds: +// sum (take n piecewise_lengths) <= x < sum (take (n+1) piecewise_lengths) +// Or else the 'last_segment' +let run_piecewise_polynomial (piecewise_poly, x : piecewise_polynomial * nat) + : int = + let output : piecewise_polynomial_acc = List.fold_left + (fun (piecewise_acc, segment : piecewise_polynomial_acc * piecewise_segment) -> + match piecewise_acc.in_poly with + | Some poly -> piecewise_acc + | None -> + let offset_next : nat = piecewise_acc.offset + segment.length in + if x <= offset_next + then {piecewise_acc with in_poly = Some segment.poly} + else {piecewise_acc with offset = offset_next} + ) + { + offset = 0n; + in_poly = (None : polynomial option); + } + piecewise_poly.segments in + + let x_in_poly : polynomial = ( + match output.in_poly with + | Some poly -> poly + | None -> piecewise_poly.last_segment) in + run_polynomial(x_in_poly, int x) + +// //////////////////////////////////////////////////////////////// + +(* res := 0 *) +(* acc := x *) +(* *) +(* current_bit := Bitwise.and n 1n (last bit) *) +(* res *= if current_bit = 1 then acc else 1 *) +(* n_next := Bitwise.shift_right n 1n // (n / 2n) *) +(* acc_next := acc * acc *) +let rec nat_pow_loop(x, res, acc, n : nat * nat * nat * nat) : nat = + if n = 0n + then res + else + let next_res : nat = if Bitwise.and n 1n = 0n then res else res * acc + in let next_acc : nat = acc * acc + in let next_n : nat = Bitwise.shift_right n 1n + in nat_pow_loop(x, next_res, next_acc, next_n) + +(* The n-th power of x *) +let nat_pow(x, n : nat * nat) : nat = + nat_pow_loop(x, 1n, x, n) + + +(* x/3000 when x between 3,000 and 30,000. 10 * 1.001^(x-30000) when x > 30,000 *) +(* x < 3,000 is undefined, so defaults to (x / 3000), i.e. 0 *) +(* Note: pow(1001, x) / pow(1000, x) is an approximation for pow(1.001, x) *) +let example_formula0 (x : nat) : tez = + (if x < 30000n + then (x / 3000n) + else 10n * (nat_pow(1001n, x) / nat_pow(1000n, x))) * 1mutez + +// //////////////////////////////////////////////////////////////// + + +(** Tez used as a price *) +type price_tez = tez + +(** Tez unclaimed that can be withdrawn *) +type unclaimed_tez = tez + +type bonding_curve_storage = + [@layout:comb] + { + // Admin storage (see ligo/fa2_modules/admin/simple_admin.mligo for more info) + admin : admin_storage; + + // fa2_entry_points contract + market_contract : address; + + // Final price of the auction + // Set this price constant based on final price of auction + auction_price : tez; + + // Number of tokens sold _after_ the auction + token_index : nat; + + // Token metadata for minting + token_metadata : (string, bytes) map; + + // The percentage (in basis points) cost of buying and selling a token at the same index + basis_points : nat; + +#if PIECEWISE_BONDING_CURVE + + // bonding curve formula + cost_mutez : piecewise_polynomial; + +#else + + // bonding curve formula + cost_mutez : (nat -> tez); + +#endif // PIECEWISE_BONDING_CURVE + + // unclaimed tez (i.e. the result of the `basis_points` fee) + unclaimed : tez; + } + +// Parameters to buy a single NFT from the bonding curve +type buy_order = + [@layout:comb] + { + buy_order_contents : unit; + } + +// Parameters for selling a single NFT from the bonding curve +type sell_order = token_id + +// alias for user receiving an NFT through a call to the Buy_offchain entrypoint +type offchain_buyer = address + +// alias for user receiving an NFT through a call to the Sell_offchain entrypoint +type offchain_seller = address + +type bonding_curve_entrypoints = + (* A default entrypoint is required to receive tez, e.g. when receiving baking rewards *) + | Default of unit + + | Admin of admin_entrypoints + + // update staking (admin only) + | Set_delegate of key_hash option + + // withdraw profits or fail + | Withdraw of unit + + // buy single token on-chain (requires tez deposit) + | Buy of buy_order + + // buy tokens off-chain (requires tez deposit and sends minted token to the offchain_buyer) + | Buy_offchain of offchain_buyer + + // sell token on-chain (returns tez deposit) + | Sell of sell_order + + // sell single/multi tokens off-chain (returns tez deposit) + | Sell_offchain of (sell_order * offchain_seller) + + +// Debug-only +#if DEBUG_BONDING_CURVE + + // nat -> price in mutez of next token + | Cost of nat + + // nat -> nat -> nat + | Pow of (nat * nat) + + // nat -> tez + | ExampleFormula0 of nat + +#endif // DEBUG_BONDING_CURVE + + +(** 10,000 basis points per 1 *) +[@inline] +let basis_points_per_unit : nat = 10000n + +(** Buy single token on-chain (requires tez deposit) +* calculate current price from index and price constant (run_piecewise_polynomial) +* ensure sent tez = current price +* mint token -> user -> market contract + next token minted same as last? +* increment current token index +* update 'unclaimed' +*) +let buy_offchain_no_admin (buyer_addr, storage : offchain_buyer * bonding_curve_storage) + : (operation list) * bonding_curve_storage = + (* cost = auction_price + cost_mutez(token_index) + basis_point_fee *) + +#if PIECEWISE_BONDING_CURVE + + let cost_tez : price_tez = match is_nat (run_piecewise_polynomial(storage.cost_mutez, storage.token_index)) with + | None -> (failwith error_negative_cost : tez) + | Some nat_cost_tez -> 1mutez * nat_cost_tez + in + +#else + + let cost_tez : price_tez = storage.cost_mutez(storage.token_index) + in + +#endif // PIECEWISE_BONDING_CURVE + + let current_price : price_tez = storage.auction_price + cost_tez + in + + (* assert cost = sent tez *) + if Tezos.amount <> current_price + + // here is a less verbose error, if gas is high + // then (failwith error_wrong_tez_price : (operation list) * bonding_curve_storage) + then ([%Michelson ({| { FAILWITH } |} : string * tez * tez -> (operation list) * bonding_curve_storage)] ("WRONG_TEZ_PRICE", Tezos.amount, current_price) : (operation list) * bonding_curve_storage) + + else + (* mint using storage.token_metadata *) + let mint_entrypoint_opt : (mint_tokens_param contract) option = + Tezos.get_entrypoint_opt "%mint" storage.market_contract in + let mint_op : operation = match mint_entrypoint_opt with + | None -> (failwith error_no_mint_entrypoint : operation) + | Some contract_ref -> + let token_metadata : token_metadata = + { + token_id = 0n; // dummy token_id + token_info = storage.token_metadata; + } in + + let mint_token_params : mint_token_param = { + token_metadata = token_metadata; + owner = buyer_addr; + } + in Tezos.transaction [mint_token_params] 0mutez contract_ref + in [mint_op], { storage with + token_index = storage.token_index + 1n } + + +(** Sell token (returns tez deposit) +- calculate _previous_ price +- burn token -> market contract +- return tez (- basis_points fee) to seller +- decrement current token_index in storage +*) +let sell_offchain_no_admin ((token_to_sell, seller_addr), storage : (token_id * offchain_seller) * bonding_curve_storage) + : (operation list) * bonding_curve_storage = + (* - previous_token_index = storage.token_index - 1n *) + (* - if not is_nat previous_token_index, fail *) + (* - cost_tez = run_piecewise_polynomial(.., previous_token_index) *) + (* - current_price = storage.auction_price + cost_tez *) + let previous_token_index : nat = match is_nat (storage.token_index - 1n) with + | None -> (failwith error_no_token_to_sell : nat) + | Some token_index -> token_index + in + +#if PIECEWISE_BONDING_CURVE + + let previous_price_tez : price_tez = match is_nat (run_piecewise_polynomial(storage.cost_mutez, previous_token_index)) with + | None -> (failwith error_negative_cost : tez) + | Some nat_cost_tez -> storage.auction_price + 1mutez * nat_cost_tez + in + +#else + + let previous_price_tez : price_tez = storage.auction_price + storage.cost_mutez(previous_token_index) + in + +#endif // PIECEWISE_BONDING_CURVE + + (* TODO: avoid converting to and from mutez with previous_price_tez and then previous_cost_tez *) + + (* previous_cost_tez = previous_price_tez - basis_point_fee *) + (* If basis_point_fee >= previous_cost_tez, the entire basis_point_fee is stored in unclaimed *) + let basis_point_fee : tez = + (previous_price_tez * storage.basis_points) / basis_points_per_unit + + (* Note: the arguments to subtraction are converted to nat so that we can *) + (* check for underflow of tez, which otherwise produces an uncatchable *) + (* runtime error *) + in let (basis_point_fee, previous_cost_tez) : tez * price_tez = match is_nat (previous_price_tez / 1mutez - basis_point_fee / 1mutez) with + | None -> (previous_price_tez, 0mutez) + | Some nat_cost_tez -> (basis_point_fee, 1mutez * nat_cost_tez) + in + + (* - burn token -> market contract *) + (* - send -> market contract *) + let burn_entrypoint_opt : ((token_id * address) contract) option = + Tezos.get_entrypoint_opt "%burn" storage.market_contract + in + + let burn_op : operation = match burn_entrypoint_opt with + | None -> (failwith error_no_burn_entrypoint : operation) + | Some contract_ref -> + Tezos.transaction (token_to_sell, seller_addr) 0mutez contract_ref + in let return_tez_entrypoint : (unit contract) option = + Tezos.get_contract_opt seller_addr + + in let operations : operation list = match return_tez_entrypoint with + | None -> (failwith error_no_default_entrypoint : operation list) + | Some seller_contract_ref -> + burn_op :: (if 0mutez = previous_cost_tez + then ([] : operation list) + else [Tezos.transaction unit previous_cost_tez seller_contract_ref]) + + (* update the unclaimed amount *) + in operations, { storage with token_index = previous_token_index; + unclaimed = storage.unclaimed + basis_point_fee } + + +let bonding_curve_main (param, storage : bonding_curve_entrypoints * bonding_curve_storage) + : (operation list) * bonding_curve_storage = + match param with + (* Receive tez, which is added to the storage.unclaimed amount *) + | Default -> + let new_storage = { storage with unclaimed = storage.unclaimed + Tezos.amount } + in ([] : operation list), new_storage + + (** admin entrypoints *) + | Admin admin_param -> + let ops, admin = admin_main (admin_param, storage.admin) in + let new_storage = { storage with admin = admin } in + ops, new_storage + + (** update staking *) + | Set_delegate delegate_opt -> + (* ADMIN ONLY *) + let assert_admin = fail_if_not_admin storage.admin in + let ops = [Tezos.set_delegate delegate_opt] in + ops, storage + + (** withdraw unclaimed profits (tracked in storage as 'unclaimed') or fail + with error_unclaimed_is_zero *) + | Withdraw withdraw_param -> + (* ADMIN ONLY *) + let assert_admin = fail_if_not_admin storage.admin in + if 0mutez < storage.unclaimed + then + let admin : unit contract = resolve_address(storage.admin.admin) in + let send_op : operation = Tezos.transaction () storage.unclaimed admin in + let new_storage = { storage with unclaimed = 0mutez } in + [send_op], new_storage + else (failwith error_unclaimed_is_zero : (operation list) * bonding_curve_storage) + + (** buy single token on-chain (requires tez deposit) + see buy_offchain_no_admin *) + | Buy buy_order_param -> + buy_offchain_no_admin(Tezos.sender, storage) + + (** buy tokens off-chain (requires all tez deposits) + I.e. 3rd party buys, but tokens sent -> given address + see buy_offchain_no_admin *) + | Buy_offchain offchain_buyer_address -> + buy_offchain_no_admin(offchain_buyer_address, storage) + + (** sell token on-chain (returns tez deposit) + see sell_offchain_no_admin *) + | Sell sell_order_param -> + sell_offchain_no_admin((sell_order_param, Tezos.sender), storage) + + (** sell single/multi tokens off-chain (returns all tez deposits) + see sell_offchain_no_admin *) + | Sell_offchain sell_order_param_offchain_seller_address -> + (* ADMIN ONLY *) + let assert_admin = fail_if_not_admin storage.admin in + sell_offchain_no_admin(sell_order_param_offchain_seller_address, storage) + +// Debug-only +#if DEBUG_BONDING_CURVE +#if PIECEWISE_BONDING_CURVE + + // (n : nat) -> failwith (price in mutez of n-th token w/o basis_points) + | Cost n -> + (failwith (run_piecewise_polynomial(storage.cost_mutez, n)) : (operation list) * bonding_curve_storage) + +#else + + // (n : nat) -> failwith (price in tez of n-th token w/o basis_points) + | Cost n -> + ([%Michelson ({| { FAILWITH } |} : tez -> (operation list) * bonding_curve_storage)] (storage.cost_mutez(n)) : (operation list) * bonding_curve_storage) + +#endif // PIECEWISE_BONDING_CURVE + + // (x, n : nat * nat) -> failwith (x ^ n) + | Pow xn -> + let x, n = xn + in (failwith (nat_pow(x, n)) : (operation list) * bonding_curve_storage) + + // (x : nat) -> failwith example_formula0(x) + | ExampleFormula0 x -> + ([%Michelson ({| { FAILWITH } |} : tez -> (operation list) * bonding_curve_storage)] (example_formula0(x)) : (operation list) * bonding_curve_storage) + +#endif // DEBUG_BONDING_CURVE + diff --git a/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve.mligo.ml b/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve.mligo.ml new file mode 100644 index 000000000..83ba979dc --- /dev/null +++ b/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve.mligo.ml @@ -0,0 +1,546 @@ +// resolve_address +#include "../common.mligo" + +// admin_storage +// admin_entrypoints +#include "../../fa2_modules/admin/simple_admin.mligo" + +// fa2_entry_points +// token_metadata +#include "../../fa2/fa2_interface.mligo" + +// mint_token_param +// mint_tokens_param +#include "../minter_collection/nft/fa2_multi_nft_manager.mligo" + +// //////////////////////////////////////////////////////////////// +// ERRORS +// //////////////////////////////////////////////////////////////// + +(** + storage.unclaimed == 0 +*) +[@inline] +let error_unclaimed_is_zero = "UNCLAIMED=0" + +(** + Wrong tez price sent when buying +*) +[@inline] +let error_wrong_tez_price = "WRONG_TEZ_PRICE" + +(** + run_piecewise_polynomial gave a negative cost +*) +[@inline] +let error_negative_cost = "NEGATIVE_COST" + +(** + market_contract address does not refer to a contract with a '%mint' + entrypoint with type mint_tokens_param +*) +[@inline] +let error_no_mint_entrypoint = "NO_MINT" + +(** + market_contract address does not refer to a contract with a '%burn' + entrypoint with type (token_id * bytes) +*) +[@inline] +let error_no_burn_entrypoint = "NO_BURN" + +(** + token_index = 0, + i.e. no tokens have been sold to the bonding curve, + i.e. there are no tokens to sell +*) +[@inline] +let error_no_token_to_sell = "NO_TOKENS" + +(** + "symbol" field not found in storage.token_metadata +*) +[@inline] +let error_token_metadata_symbol_missing = "NO_SYMBOL" + +(** + Can't return tez to the given seller address because it doesn't have a default + entrypoint to send tez to +*) +[@inline] +let error_no_default_entrypoint = "CANT_RETURN" + +(** + Entrypoint is unimplemented +*) +[@inline] +let error_unimplemented_entrypoint = "UNIMPLEMENTED" + +// //////////////////////////////////////////////////////////////// + +// length of one of the segments in a piecewise_polynomial +type piecewise_length = nat + +// A list of coefficients for a polynomial over the integers. +// +// See run_polynomial for more info. +type polynomial = + [@layout:comb] + { + coefficients : int list; + } + +// Accumulator for run_polynomial +type polynomial_acc = + { + result : int; + + (** x^i for some i + *) + x_pow : int; + } + +// Run a polynomial [a0; a1; .. ; an] on an input 'x' as +// a0 * x^0 + a1 * x^1 + .. + an * x^n +[@inline] +let run_polynomial (poly, x : polynomial * int) + : int = + let output = List.fold_left + (fun (poly_acc, coefficient : polynomial_acc * int) -> + let x_pow = poly_acc.x_pow in + let x_pow_next = x * x_pow in + let output : polynomial_acc = + { + result = poly_acc.result + coefficient * x_pow; + x_pow = x_pow_next; + } + in output + ) + { + result = 0; + x_pow = 1; + } + poly.coefficients in + output.result + +// A segment of a piecewise function +type piecewise_segment = + { + length : piecewise_length; + poly : polynomial; + } + +// The 'piecewise_length' is the length of each segment +// and the formula for each segment is given by the associated 'polynomial' +// +// [ (length_0, polynomial_0); (length_1, polynomial_1); .. ] +// +// -> +// +// f(x) := +// { polynomial_0(x) | 0 <= x < length_0 +// { polynomial_1(x) | length_0 <= x < length_0 + length_1 +// .. +// { polynomial_i(x) | sum_{0 <= j <= i-1} length_j <= x < sum_{0 <= j <= i} length_j +// .. +// { polynomial_last(x) | sum_{0 <= j < last-1} length_j <= x +type piecewise_polynomial = + [@layout:comb] + { + segments : piecewise_segment list; + last_segment : polynomial; + } + +// Accumulator for run_piecewise_polynomial +type piecewise_polynomial_acc = + { + // Current segment offset, i.e. sum of piecewise_length's up to the current + // location in piecewise_polynomial.segments + offset : nat; + + // The input was found in this polynomial when Some + in_poly : polynomial option + } + +// Run a piecewise polynomial by finding the segment for the current offset and +// calling run_polynomial +// +// Given all of the piecewise_length's as a list piecewise_lengths, the current +// segment can be considered the unique (n) for which the following holds: +// sum (take n piecewise_lengths) <= x < sum (take (n+1) piecewise_lengths) +// Or else the 'last_segment' +let run_piecewise_polynomial (piecewise_poly, x : piecewise_polynomial * nat) + : int = + let output : piecewise_polynomial_acc = List.fold_left + (fun (piecewise_acc, segment : piecewise_polynomial_acc * piecewise_segment) -> + match piecewise_acc.in_poly with + | Some poly -> piecewise_acc + | None -> + let offset_next : nat = piecewise_acc.offset + segment.length in + if x <= offset_next + then {piecewise_acc with in_poly = Some segment.poly} + else {piecewise_acc with offset = offset_next} + ) + { + offset = 0n; + in_poly = (None : polynomial option); + } + piecewise_poly.segments in + + let x_in_poly : polynomial = ( + match output.in_poly with + | Some poly -> poly + | None -> piecewise_poly.last_segment) in + run_polynomial(x_in_poly, int x) + +// //////////////////////////////////////////////////////////////// + +(* res := 0 *) +(* acc := x *) +(* *) +(* current_bit := Bitwise.and n 1n (last bit) *) +(* res *= if current_bit = 1 then acc else 1 *) +(* n_next := Bitwise.shift_right n 1n // (n / 2n) *) +(* acc_next := acc * acc *) +let rec nat_pow_loop(x, res, acc, n : nat * nat * nat * nat) : nat = + if n = 0n + then res + else + let next_res : nat = if Bitwise.and n 1n = 0n then res else res * acc + in let next_acc : nat = acc * acc + in let next_n : nat = Bitwise.shift_right n 1n + in nat_pow_loop(x, next_res, next_acc, next_n) + +(* The n-th power of x *) +let nat_pow(x, n : nat * nat) : nat = + nat_pow_loop(x, 1n, x, n) + + +(* x/3000 when x between 3,000 and 30,000. 10 * 1.001^(x-30000) when x > 30,000 *) +(* x < 3,000 is undefined, so defaults to (x / 3000), i.e. 0 *) +(* Note: pow(1001, x) / pow(1000, x) is an approximation for pow(1.001, x) *) +let example_formula0 (x : nat) : tez = + (if x < 30000n + then (x / 3000n) + else 10n * (nat_pow(1001n, x) / nat_pow(1000n, x))) * 1mutez + +// //////////////////////////////////////////////////////////////// + + +(** Tez used as a price *) +type price_tez = tez + +(** Tez unclaimed that can be withdrawn *) +type unclaimed_tez = tez + +type bonding_curve_storage = + [@layout:comb] + { + // Admin storage (see ligo/fa2_modules/admin/simple_admin.mligo for more info) + admin : admin_storage; + + // fa2_entry_points contract + market_contract : address; + + // Final price of the auction + // Set this price constant based on final price of auction + auction_price : tez; + + // Number of tokens sold _after_ the auction + token_index : nat; + + // Token metadata for minting + token_metadata : (string, bytes) map; + + // The percentage (in basis points) cost of buying and selling a token at the same index + basis_points : nat; + +#if PIECEWISE_BONDING_CURVE + + // bonding curve formula + cost_mutez : piecewise_polynomial; + +#else + + // bonding curve formula + cost_mutez : (nat -> tez); + +#endif // PIECEWISE_BONDING_CURVE + + // unclaimed tez (i.e. the result of the `basis_points` fee) + unclaimed : tez; + } + +// Parameters to buy a single NFT from the bonding curve +type buy_order = + [@layout:comb] + { + buy_order_contents : unit; + } + +// Parameters for selling a single NFT from the bonding curve +type sell_order = token_id + +// alias for user receiving an NFT through a call to the Buy_offchain entrypoint +type offchain_buyer = address + +// alias for user receiving an NFT through a call to the Sell_offchain entrypoint +type offchain_seller = address + +type bonding_curve_entrypoints = + (* A default entrypoint is required to receive tez, e.g. when receiving baking rewards *) + | Default of unit + + | Admin of admin_entrypoints + + // update staking (admin only) + | Set_delegate of key_hash option + + // withdraw profits or fail + | Withdraw of unit + + // buy single token on-chain (requires tez deposit) + | Buy of buy_order + + // buy tokens off-chain (requires tez deposit and sends minted token to the offchain_buyer) + | Buy_offchain of offchain_buyer + + // sell token on-chain (returns tez deposit) + | Sell of sell_order + + // sell single/multi tokens off-chain (returns tez deposit) + | Sell_offchain of (sell_order * offchain_seller) + + +// Debug-only +#if DEBUG_BONDING_CURVE + + // nat -> price in mutez of next token + | Cost of nat + + // nat -> nat -> nat + | Pow of (nat * nat) + + // nat -> tez + | ExampleFormula0 of nat + +#endif // DEBUG_BONDING_CURVE + + +(** 10,000 basis points per 1 *) +[@inline] +let basis_points_per_unit : nat = 10000n + +(** Buy single token on-chain (requires tez deposit) +* calculate current price from index and price constant (run_piecewise_polynomial) +* ensure sent tez = current price +* mint token -> user -> market contract + next token minted same as last? +* increment current token index +* update 'unclaimed' +*) +let buy_offchain_no_admin (buyer_addr, storage : offchain_buyer * bonding_curve_storage) + : (operation list) * bonding_curve_storage = + (* cost = auction_price + cost_mutez(token_index) + basis_point_fee *) + +#if PIECEWISE_BONDING_CURVE + + let cost_tez : price_tez = match is_nat (run_piecewise_polynomial(storage.cost_mutez, storage.token_index)) with + | None -> (failwith error_negative_cost : tez) + | Some nat_cost_tez -> 1mutez * nat_cost_tez + in + +#else + + let cost_tez : price_tez = storage.cost_mutez(storage.token_index) + in + +#endif // PIECEWISE_BONDING_CURVE + + let current_price : price_tez = storage.auction_price + cost_tez + in + + (* assert cost = sent tez *) + if Tezos.amount <> current_price + + // here is a less verbose error, if gas is high + // then (failwith error_wrong_tez_price : (operation list) * bonding_curve_storage) + then ([%Michelson ({| { FAILWITH } |} : string * tez * tez -> (operation list) * bonding_curve_storage)] ("WRONG_TEZ_PRICE", Tezos.amount, current_price) : (operation list) * bonding_curve_storage) + + else + (* mint using storage.token_metadata *) + let mint_entrypoint_opt : (mint_tokens_param contract) option = + Tezos.get_entrypoint_opt "%mint" storage.market_contract in + let mint_op : operation = match mint_entrypoint_opt with + | None -> (failwith error_no_mint_entrypoint : operation) + | Some contract_ref -> + let token_metadata : token_metadata = + { + token_id = 0n; // dummy token_id + token_info = storage.token_metadata; + } in + + let mint_token_params : mint_token_param = { + token_metadata = token_metadata; + owner = buyer_addr; + } + in Tezos.transaction [mint_token_params] 0mutez contract_ref + in [mint_op], { storage with + token_index = storage.token_index + 1n } + + +(** Sell token (returns tez deposit) +- calculate _previous_ price +- burn token -> market contract +- return tez (- basis_points fee) to seller +- decrement current token_index in storage +*) +let sell_offchain_no_admin ((token_to_sell, seller_addr), storage : (token_id * offchain_seller) * bonding_curve_storage) + : (operation list) * bonding_curve_storage = + (* - previous_token_index = storage.token_index - 1n *) + (* - if not is_nat previous_token_index, fail *) + (* - cost_tez = run_piecewise_polynomial(.., previous_token_index) *) + (* - current_price = storage.auction_price + cost_tez *) + let previous_token_index : nat = match is_nat (storage.token_index - 1n) with + | None -> (failwith error_no_token_to_sell : nat) + | Some token_index -> token_index + in + +#if PIECEWISE_BONDING_CURVE + + let previous_price_tez : price_tez = match is_nat (run_piecewise_polynomial(storage.cost_mutez, previous_token_index)) with + | None -> (failwith error_negative_cost : tez) + | Some nat_cost_tez -> storage.auction_price + 1mutez * nat_cost_tez + in + +#else + + let previous_price_tez : price_tez = storage.auction_price + storage.cost_mutez(previous_token_index) + in + +#endif // PIECEWISE_BONDING_CURVE + + (* TODO: avoid converting to and from mutez with previous_price_tez and then previous_cost_tez *) + + (* previous_cost_tez = previous_price_tez - basis_point_fee *) + (* If basis_point_fee >= previous_cost_tez, the entire basis_point_fee is stored in unclaimed *) + let basis_point_fee : tez = + (previous_price_tez * storage.basis_points) / basis_points_per_unit + + (* Note: the arguments to subtraction are converted to nat so that we can *) + (* check for underflow of tez, which otherwise produces an uncatchable *) + (* runtime error *) + in let (basis_point_fee, previous_cost_tez) : tez * price_tez = match is_nat (previous_price_tez / 1mutez - basis_point_fee / 1mutez) with + | None -> (previous_price_tez, 0mutez) + | Some nat_cost_tez -> (basis_point_fee, 1mutez * nat_cost_tez) + in + + (* - burn token -> market contract *) + (* - send -> market contract *) + let burn_entrypoint_opt : ((token_id * address) contract) option = + Tezos.get_entrypoint_opt "%burn" storage.market_contract + in + + let burn_op : operation = match burn_entrypoint_opt with + | None -> (failwith error_no_burn_entrypoint : operation) + | Some contract_ref -> + Tezos.transaction (token_to_sell, seller_addr) 0mutez contract_ref + in let return_tez_entrypoint : (unit contract) option = + Tezos.get_contract_opt seller_addr + + in let operations : operation list = match return_tez_entrypoint with + | None -> (failwith error_no_default_entrypoint : operation list) + | Some seller_contract_ref -> + burn_op :: (if 0mutez = previous_cost_tez + then ([] : operation list) + else [Tezos.transaction unit previous_cost_tez seller_contract_ref]) + + (* update the unclaimed amount *) + in operations, { storage with token_index = previous_token_index; + unclaimed = storage.unclaimed + basis_point_fee } + + +let bonding_curve_main (param, storage : bonding_curve_entrypoints * bonding_curve_storage) + : (operation list) * bonding_curve_storage = + match param with + (* Receive tez, which is added to the storage.unclaimed amount *) + | Default -> + let new_storage = { storage with unclaimed = storage.unclaimed + Tezos.amount } + in ([] : operation list), new_storage + + (** admin entrypoints *) + | Admin admin_param -> + let ops, admin = admin_main (admin_param, storage.admin) in + let new_storage = { storage with admin = admin } in + ops, new_storage + + (** update staking *) + | Set_delegate delegate_opt -> + (* ADMIN ONLY *) + let assert_admin = fail_if_not_admin storage.admin in + let ops = [Tezos.set_delegate delegate_opt] in + ops, storage + + (** withdraw unclaimed profits (tracked in storage as 'unclaimed') or fail + with error_unclaimed_is_zero *) + | Withdraw withdraw_param -> + (* ADMIN ONLY *) + let assert_admin = fail_if_not_admin storage.admin in + if 0mutez < storage.unclaimed + then + let admin : unit contract = resolve_address(storage.admin.admin) in + let send_op : operation = Tezos.transaction () storage.unclaimed admin in + let new_storage = { storage with unclaimed = 0mutez } in + [send_op], new_storage + else (failwith error_unclaimed_is_zero : (operation list) * bonding_curve_storage) + + (** buy single token on-chain (requires tez deposit) + see buy_offchain_no_admin *) + | Buy buy_order_param -> + buy_offchain_no_admin(Tezos.sender, storage) + + (** buy tokens off-chain (requires all tez deposits) + I.e. 3rd party buys, but tokens sent -> given address + see buy_offchain_no_admin *) + | Buy_offchain offchain_buyer_address -> + buy_offchain_no_admin(offchain_buyer_address, storage) + + (** sell token on-chain (returns tez deposit) + see sell_offchain_no_admin *) + | Sell sell_order_param -> + sell_offchain_no_admin((sell_order_param, Tezos.sender), storage) + + (** sell single/multi tokens off-chain (returns all tez deposits) + see sell_offchain_no_admin *) + | Sell_offchain sell_order_param_offchain_seller_address -> + (* ADMIN ONLY *) + let assert_admin = fail_if_not_admin storage.admin in + sell_offchain_no_admin(sell_order_param_offchain_seller_address, storage) + +// Debug-only +#if DEBUG_BONDING_CURVE +#if PIECEWISE_BONDING_CURVE + + // (n : nat) -> failwith (price in mutez of n-th token w/o basis_points) + | Cost n -> + (failwith (run_piecewise_polynomial(storage.cost_mutez, n)) : (operation list) * bonding_curve_storage) + +#else + + // (n : nat) -> failwith (price in tez of n-th token w/o basis_points) + | Cost n -> + ([%Michelson ({| { FAILWITH } |} : tez -> (operation list) * bonding_curve_storage)] (storage.cost_mutez(n)) : (operation list) * bonding_curve_storage) + +#endif // PIECEWISE_BONDING_CURVE + + // (x, n : nat * nat) -> failwith (x ^ n) + | Pow xn -> + let x, n = xn + in (failwith (nat_pow(x, n)) : (operation list) * bonding_curve_storage) + + // (x : nat) -> failwith example_formula0(x) + | ExampleFormula0 x -> + ([%Michelson ({| { FAILWITH } |} : tez -> (operation list) * bonding_curve_storage)] (example_formula0(x)) : (operation list) * bonding_curve_storage) + +#endif // DEBUG_BONDING_CURVE + diff --git a/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve_debug.mligo b/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve_debug.mligo new file mode 100644 index 000000000..463131aad --- /dev/null +++ b/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve_debug.mligo @@ -0,0 +1,6 @@ +// Bonding curve contract with debugging entrypoints and features enabled +// Similar example here: ../swaps/fa2_allowlisted_swap_with_burn.mligo +#if !DEBUG_BONDING_CURVE +#define DEBUG_BONDING_CURVE +#include "bonding_curve.mligo" +#endif diff --git a/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve_piecewise.mligo b/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve_piecewise.mligo new file mode 100644 index 000000000..a2ebe369c --- /dev/null +++ b/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve_piecewise.mligo @@ -0,0 +1,6 @@ +// Bonding curve contract with piecewise cost_mutez formula +// Similar example here: ../swaps/fa2_allowlisted_swap_with_burn.mligo +#if !PIECEWISE_BONDING_CURVE +#define PIECEWISE_BONDING_CURVE +#include "bonding_curve.mligo" +#endif diff --git a/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve_piecewise_debug.mligo b/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve_piecewise_debug.mligo new file mode 100644 index 000000000..7e454b494 --- /dev/null +++ b/packages/minter-contracts/ligo/src/bonding_curve/bonding_curve_piecewise_debug.mligo @@ -0,0 +1,10 @@ +// Bonding curve contract with debugging entrypoints and features enabled and +// piecewise cost_mutez formula +// Similar example here: ../swaps/fa2_allowlisted_swap_with_burn.mligo +#if !DEBUG_BONDING_CURVE +#define DEBUG_BONDING_CURVE +#if !PIECEWISE_BONDING_CURVE +#define PIECEWISE_BONDING_CURVE +#include "bonding_curve.mligo" +#endif +#endif diff --git a/packages/minter-contracts/ligo/src/minter_collection/nft/fa2_multi_nft_asset.mligo b/packages/minter-contracts/ligo/src/minter_collection/nft/fa2_multi_nft_asset.mligo index c008cbb15..5fd961584 100644 --- a/packages/minter-contracts/ligo/src/minter_collection/nft/fa2_multi_nft_asset.mligo +++ b/packages/minter-contracts/ligo/src/minter_collection/nft/fa2_multi_nft_asset.mligo @@ -1,4 +1,3 @@ - #include "fa2_multi_nft_token.mligo" #include "fa2_multi_nft_manager.mligo" @@ -8,14 +7,16 @@ type nft_asset_storage = { metadata: (string, bytes) big_map; (* contract metadata *) } -#if !EDITIONS +#if !EDITIONS type nft_asset_entrypoints = | Assets of fa2_entry_points | Mint of mint_tokens_param + | Burn of (token_id * address) + | Update_metadata of (token_metadata list) | Admin of admin_entrypoints -#else +#else type nft_asset_entrypoints = | Assets of fa2_entry_points @@ -35,15 +36,62 @@ let nft_asset_main (param, storage : nft_asset_entrypoints * nft_asset_storage) #if !EDITIONS + (* Only owner of token_id=0 can update who can mint *) + (* - note that they can break minting by transferring token_id=0 *) | Mint mp -> - let u = fail_if_not_admin storage.admin in + let assert_minter = if Big_map.mem (storage.admin.admin, (Tezos.sender, 0n)) storage.assets.operators + then unit + else (failwith "NOT_MINTER" : unit) + in let ops, new_assets = mint_tokens (mp, storage.assets) in let new_storage = { storage with assets = new_assets;} in ops, new_storage + + (* Remove token from ledger and token_metadata *) + (* (minter only, forwarded_sender must be token owner) *) + | Burn token_to_burn_and_address -> + let token_to_burn, forwarded_sender : token_id * address = token_to_burn_and_address in + + // delete token from token_metadata + let new_token_metadata : nft_meta = + Big_map.update token_to_burn (None : token_metadata option) storage.assets.token_metadata in + + // delete token from ledger + let token_to_burn_owner_opt, new_ledger : address * ledger = + Big_map.get_and_update token_to_burn (None : address option) storage.assets.ledger in + + // ensure sender is an operator for the owner of the token + let operations : operation list = match token_to_burn_owner_opt with + | None -> (failwith "WRONG_ID" : operation list) + | Some token_to_burn_owner -> + + // Ensure minter is sender and forwarded_sender is owner + if (Big_map.mem (storage.admin.admin, (Tezos.sender, 0n)) storage.assets.operators) && (forwarded_sender = token_to_burn_owner) + then ([] : operation list) + else (failwith "NOT_BURNER" : operation list) + + in let new_assets : nft_token_storage = { storage.assets with + ledger = new_ledger; + token_metadata = new_token_metadata } in + operations, { storage with assets = new_assets} + + + | Update_metadata token_metadatas -> + let u = fail_if_not_admin storage.admin in + let new_nft_meta : nft_meta = List.fold_left + (fun (nft_meta_acc, metadata : nft_meta * token_metadata) -> + Big_map.update metadata.token_id (Some metadata) nft_meta_acc) + storage.assets.token_metadata + token_metadatas in + let new_storage = { storage with assets = { storage.assets with token_metadata = new_nft_meta } } in + ([] : operation list), new_storage + #endif | Admin a -> let ops, admin = admin_main (a, storage.admin) in let new_storage = { storage with admin = admin; } in ops, new_storage + + diff --git a/packages/minter-contracts/ligo/src/minter_collection/nft/fa2_multi_nft_asset.mligo.ml b/packages/minter-contracts/ligo/src/minter_collection/nft/fa2_multi_nft_asset.mligo.ml new file mode 100644 index 000000000..5fd961584 --- /dev/null +++ b/packages/minter-contracts/ligo/src/minter_collection/nft/fa2_multi_nft_asset.mligo.ml @@ -0,0 +1,97 @@ +#include "fa2_multi_nft_token.mligo" +#include "fa2_multi_nft_manager.mligo" + +type nft_asset_storage = { + assets : nft_token_storage; + admin : admin_storage; + metadata: (string, bytes) big_map; (* contract metadata *) +} + +#if !EDITIONS + +type nft_asset_entrypoints = + | Assets of fa2_entry_points + | Mint of mint_tokens_param + | Burn of (token_id * address) + | Update_metadata of (token_metadata list) + | Admin of admin_entrypoints + +#else + +type nft_asset_entrypoints = + | Assets of fa2_entry_points + | Admin of admin_entrypoints + +#endif + + +let nft_asset_main (param, storage : nft_asset_entrypoints * nft_asset_storage) + : operation list * nft_asset_storage = + match param with + | Assets fa2 -> + let u = fail_if_paused(storage.admin) in + let ops, new_assets = fa2_main (fa2, storage.assets) in + let new_storage = { storage with assets = new_assets; } in + ops, new_storage + +#if !EDITIONS + + (* Only owner of token_id=0 can update who can mint *) + (* - note that they can break minting by transferring token_id=0 *) + | Mint mp -> + let assert_minter = if Big_map.mem (storage.admin.admin, (Tezos.sender, 0n)) storage.assets.operators + then unit + else (failwith "NOT_MINTER" : unit) + in + let ops, new_assets = mint_tokens (mp, storage.assets) in + let new_storage = { storage with assets = new_assets;} in + ops, new_storage + + + (* Remove token from ledger and token_metadata *) + (* (minter only, forwarded_sender must be token owner) *) + | Burn token_to_burn_and_address -> + let token_to_burn, forwarded_sender : token_id * address = token_to_burn_and_address in + + // delete token from token_metadata + let new_token_metadata : nft_meta = + Big_map.update token_to_burn (None : token_metadata option) storage.assets.token_metadata in + + // delete token from ledger + let token_to_burn_owner_opt, new_ledger : address * ledger = + Big_map.get_and_update token_to_burn (None : address option) storage.assets.ledger in + + // ensure sender is an operator for the owner of the token + let operations : operation list = match token_to_burn_owner_opt with + | None -> (failwith "WRONG_ID" : operation list) + | Some token_to_burn_owner -> + + // Ensure minter is sender and forwarded_sender is owner + if (Big_map.mem (storage.admin.admin, (Tezos.sender, 0n)) storage.assets.operators) && (forwarded_sender = token_to_burn_owner) + then ([] : operation list) + else (failwith "NOT_BURNER" : operation list) + + in let new_assets : nft_token_storage = { storage.assets with + ledger = new_ledger; + token_metadata = new_token_metadata } in + operations, { storage with assets = new_assets} + + + | Update_metadata token_metadatas -> + let u = fail_if_not_admin storage.admin in + let new_nft_meta : nft_meta = List.fold_left + (fun (nft_meta_acc, metadata : nft_meta * token_metadata) -> + Big_map.update metadata.token_id (Some metadata) nft_meta_acc) + storage.assets.token_metadata + token_metadatas in + let new_storage = { storage with assets = { storage.assets with token_metadata = new_nft_meta } } in + ([] : operation list), new_storage + +#endif + + | Admin a -> + let ops, admin = admin_main (a, storage.admin) in + let new_storage = { storage with admin = admin; } in + ops, new_storage + + diff --git a/packages/minter-contracts/ligo/src/minter_collection/nft/fa2_multi_nft_asset_no_admin.mligo b/packages/minter-contracts/ligo/src/minter_collection/nft/fa2_multi_nft_asset_no_admin.mligo deleted file mode 100644 index 59ef51b49..000000000 --- a/packages/minter-contracts/ligo/src/minter_collection/nft/fa2_multi_nft_asset_no_admin.mligo +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../../fa2_modules/admin/no_admin.mligo" -#include "fa2_multi_nft_asset.mligo" \ No newline at end of file diff --git a/packages/minter-contracts/ligo/src/minter_collection/nft/fa2_multi_nft_manager.mligo b/packages/minter-contracts/ligo/src/minter_collection/nft/fa2_multi_nft_manager.mligo index 15ace84b6..70e318b24 100644 --- a/packages/minter-contracts/ligo/src/minter_collection/nft/fa2_multi_nft_manager.mligo +++ b/packages/minter-contracts/ligo/src/minter_collection/nft/fa2_multi_nft_manager.mligo @@ -28,12 +28,14 @@ let update_meta_and_create_txs (param, storage } in List.fold (fun (acc, t : minted1 * mint_token_param) -> - let new_token_id = t.token_metadata.token_id in + let new_token_id = acc.storage.next_token_id in if (Big_map.mem new_token_id acc.storage.ledger) then (failwith "FA2_INVALID_TOKEN_ID" : minted1) else let new_token_metadata = - Big_map.add new_token_id t.token_metadata acc.storage.token_metadata in + Big_map.add new_token_id { t.token_metadata with token_id = new_token_id } acc.storage.token_metadata in + + let next_token_id : nat = new_token_id + 1n in let new_storage = { acc.storage with token_metadata = new_token_metadata; diff --git a/packages/minter-contracts/package.json b/packages/minter-contracts/package.json index 74d3e2f9a..d40af1874 100644 --- a/packages/minter-contracts/package.json +++ b/packages/minter-contracts/package.json @@ -14,6 +14,7 @@ "lint": "yarn eslint . --ext .js,.ts", "test-contract": "yarn start-sandbox && jest", "test-contracts": "jest --runInBand", + "test-bonding-curve": "yarn start-sandbox && jest --runInBand test/bonding-curve.test.ts; yarn kill-sandbox", "start-sandbox": "../../flextesa/start-sandbox.sh", "kill-sandbox": "../../flextesa/kill-sandbox.sh", "build:watch": "tsc -w -p .", diff --git a/packages/minter-contracts/package.yaml b/packages/minter-contracts/package.yaml index f679073cc..672004cea 100644 --- a/packages/minter-contracts/package.yaml +++ b/packages/minter-contracts/package.yaml @@ -50,9 +50,12 @@ tests: - minter-sdk - sized - tasty + - text - type-natural - hedgehog - tasty-hedgehog + - hedgehog-quickcheck - indexed-traversable - morley-client - integer-gmp + - QuickCheck diff --git a/packages/minter-contracts/src-hs/Lorentz/Contracts/BondingCurve.hs b/packages/minter-contracts/src-hs/Lorentz/Contracts/BondingCurve.hs new file mode 100644 index 000000000..a37331390 --- /dev/null +++ b/packages/minter-contracts/src-hs/Lorentz/Contracts/BondingCurve.hs @@ -0,0 +1,22 @@ +-- | Lorentz bindings for the bonding curve contract +module Lorentz.Contracts.BondingCurve where + +import Lorentz (Contract, Lambda, Mutez) +import Lorentz.Test.Import (embedContractM) + +import Lorentz.Contracts.MinterSdk (inBinFolder) +import Lorentz.Contracts.BondingCurve.Interface (Entrypoints(..), PiecewisePolynomial(..), Storage(..)) +import Lorentz.Contracts.BondingCurve.Interface.Debug (DebugEntrypoints(..)) + +bondingCurveContract :: Contract Entrypoints (Storage (Lambda Natural Mutez)) +bondingCurveContract = $$(embedContractM (inBinFolder "bonding_curve.tz")) + +debugBondingCurveContract :: Contract DebugEntrypoints (Storage (Lambda Natural Mutez)) +debugBondingCurveContract = $$(embedContractM (inBinFolder "bonding_curve_debug.tz")) + +bondingCurvePiecewiseContract :: Contract Entrypoints (Storage PiecewisePolynomial) +bondingCurvePiecewiseContract = $$(embedContractM (inBinFolder "bonding_curve_piecewise.tz")) + +debugBondingCurvePiecewiseContract :: Contract DebugEntrypoints (Storage PiecewisePolynomial) +debugBondingCurvePiecewiseContract = $$(embedContractM (inBinFolder "bonding_curve_piecewise_debug.tz")) + diff --git a/packages/minter-contracts/src-hs/Lorentz/Contracts/BondingCurve/Interface.hs b/packages/minter-contracts/src-hs/Lorentz/Contracts/BondingCurve/Interface.hs new file mode 100644 index 000000000..bcb1c2c56 --- /dev/null +++ b/packages/minter-contracts/src-hs/Lorentz/Contracts/BondingCurve/Interface.hs @@ -0,0 +1,283 @@ +-- | Lorentz interface for the bonding curve contract +module Lorentz.Contracts.BondingCurve.Interface where + +import Fmt (Buildable(..), genericF) +import Tezos.Address (detGenKeyAddress) + +import Lorentz +import Lorentz.Contracts.MinterSdk (inBinFolder) +import Lorentz.Contracts.SimpleAdmin (AdminEntrypoints(..), AdminStorage(..)) +import Lorentz.Contracts.Spec.FA2Interface (TokenId(..), TokenMetadata, mkTokenMetadata) +import qualified Lorentz.Contracts.FA2 as FA2 () -- TokenMetadata(..)) + +import Michelson.Text (unsafeMkMText) +import Michelson.Typed.Value (Value'(..)) +import Michelson.Test.Import (importValue) + + +valueToLambda :: Value (ToT (Lambda a b)) -> Lambda a b +valueToLambda x = + case x of + VLam xs -> LorentzInstr xs + +bondingCurveExampleFormula0Value :: IO (Value (ToT (Lambda Natural Mutez))) +bondingCurveExampleFormula0Value = importValue =<< inBinFolder "bonding_curve_example_formula_0.tz" + +bondingCurveExampleFormula0Lambda :: IO (Lambda Natural Mutez) +bondingCurveExampleFormula0Lambda = valueToLambda <$> bondingCurveExampleFormula0Value + + +-- | "`calculateBasisPointFee` basisPoints amount" gives the expected basis point fee +calculateBasisPointFee :: Natural -> Integer -> Integer +calculateBasisPointFee basisPoints x = + (fromIntegral basisPoints * x) `div` (100 * 100) + +-- | Remove the basis point fee from the input and ensure that the subtraction +-- is safe, i.e. no negative results are returned. +-- +-- If this result is non-negative, it's calculated as: +-- +-- @ +-- removeBasisPointFee basisPoints x = x - calculateBasisPointFee basisPoints x +-- @ +-- +-- otherwise an error is thrown. +removeBasisPointFee :: Natural -> Integer -> Integer +removeBasisPointFee basisPoints x = + if 0 <= unsafeResult + then unsafeResult + else error $ + "removeBasisPointFee: basis point fee too large: (basisPoints, calculateBasisPointFee basisPoints x): " <> + show (basisPoints, calculateBasisPointFee basisPoints x) <> + " while x: " <> + show x + where + unsafeResult = x - calculateBasisPointFee basisPoints x + +-- | A piecewise polynomial is composed of a number of (length, coefficients +-- from x^0..) polynomials, ended by a single (coefficients from x^0..) +-- polynomial +data PiecewisePolynomial = PiecewisePolynomial + { segments :: [(Natural, [Integer])] + , last_segment :: [Integer] + } deriving stock (Eq, Ord, Show) + +customGeneric "PiecewisePolynomial" ligoCombLayout +deriving anyclass instance IsoValue PiecewisePolynomial +deriving anyclass instance HasAnnotation PiecewisePolynomial +instance Buildable PiecewisePolynomial where build = genericF + +-- Run a polynomial [a0, a1, .. , an] on an input 'x' as +-- a0 * x^0 + a1 * x^1 + .. + an * x^n +runPolynomial :: [Integer] -> Integer -> Integer +runPolynomial coefficients x = sum . zipWith (*) coefficients $ iterate (* x) 1 + +-- Run a piecewise polynomial by finding the segment for the current offset and +-- calling runPolynomial +-- +-- Given all of the piecewise_length's as a list piecewise_lengths, the current +-- segment can be considered the unique (n) for which the following holds: +-- sum (take n piecewise_lengths) <= x < sum (take (n+1) piecewise_lengths) +-- Or else the 'last_segment' +runPiecewisePolynomial :: PiecewisePolynomial -> Natural -> Integer +runPiecewisePolynomial PiecewisePolynomial{..} x = aux x segments + where + aux :: Natural -> [(Natural, [Integer])] -> Integer + aux _offset [] = runPolynomial last_segment (toInteger x) + aux offset ((segmentLength, poly):segments') = + -- TODO: remove comment + -- if offset < segmentLength + if offset <= segmentLength + then runPolynomial poly (toInteger x) + else aux (offset - segmentLength) segments' + +polynomialToPiecewisePolynomial :: [Integer] -> PiecewisePolynomial +polynomialToPiecewisePolynomial polynomial = PiecewisePolynomial + { segments = [] + , last_segment = polynomial + } + +-- | PiecewisePolynomial that always outputs constant +constantPiecewisePolynomial :: Integer -> PiecewisePolynomial +constantPiecewisePolynomial = polynomialToPiecewisePolynomial . (: []) + +-- | PiecewisePolynomial that's always a line with formula: y(x) := y0 + slope * x +linearPiecewisePolynomial :: Integer -> Integer -> PiecewisePolynomial +linearPiecewisePolynomial y0 slope = polynomialToPiecewisePolynomial [y0, slope] + +examplePiecewisePolynomial :: PiecewisePolynomial +examplePiecewisePolynomial = PiecewisePolynomial + { segments = [(3, [0, 1])] -- f(x) = x | x < 3 + , last_segment = [0, 2] -- f(x) = 2x + } + +examplePiecewisePolynomial' :: PiecewisePolynomial +examplePiecewisePolynomial' = PiecewisePolynomial + { segments = [(6, [7, 8])] + , last_segment = [4, 5] + } + + +constantLambda :: Mutez -> Lambda Natural Mutez +constantLambda constant = + Lorentz.drop # + push constant + +constantsLambda :: forall a. NiceConstant a => [a] -> Lambda Natural a +constantsLambda constants = + left @Natural # + push @[a] constants # + Lorentz.swap # + loopLeft + ( push @Natural 1 # + Lorentz.swap # + sub # + isNat # + ifNone -- if None then Natural was 0 before subtracting 1 from it + ( ifCons -- return value + right + ( push @MText (unsafeMkMText "list too short for index") # + failWith + ) + ) + ( Lorentz.swap # -- continue uncons-ing through list + ifCons + ( Lorentz.drop # + Lorentz.swap # + left @Natural + ) + ( push @MText (unsafeMkMText "list too short for index") # + failWith + ) + ) + ) # + Lorentz.swap # + Lorentz.drop + + +data Storage c = Storage + { admin :: AdminStorage + , market_contract :: Address + , auction_price :: Mutez + , token_index :: Natural + , token_metadata :: TokenMetadata + , basis_points :: Natural + , cost_mutez :: c + , unclaimed :: Mutez + } deriving stock (Eq, Show) + +customGeneric "Storage" ligoCombLayout +deriving anyclass instance IsoValue c => IsoValue (Storage c) +deriving anyclass instance HasAnnotation c => HasAnnotation (Storage c) +instance Buildable c => Buildable (Storage c) where build = genericF + +exampleAdminStorage :: AdminStorage +exampleAdminStorage = AdminStorage + { admin = detGenKeyAddress "example-admin-key" + , pendingAdmin = Nothing + , paused = False + } + +exampleTokenMetadata :: TokenMetadata +exampleTokenMetadata = mkTokenMetadata symbol name decimals + where + symbol = "test_symbol" + name = "This is a test! [name]" + decimals = "12" + +exampleStoragePiecewise :: Storage PiecewisePolynomial +exampleStoragePiecewise = Storage + { admin = exampleAdminStorage + , market_contract = detGenKeyAddress "dummy-impossible-contract-key" + , auction_price = toMutez 0 + , token_index = 0 + , token_metadata = exampleTokenMetadata + , basis_points = 100 + , cost_mutez = examplePiecewisePolynomial' + , unclaimed = toMutez 0 + } + +-- | exampleStorage with admin set +exampleStoragePiecewiseWithAdmin :: Address -> Storage PiecewisePolynomial +exampleStoragePiecewiseWithAdmin admin = + exampleStoragePiecewise { admin = AdminStorage admin Nothing False } + +exampleStorage :: Storage (Lambda Natural Mutez) +exampleStorage = Storage + { admin = exampleAdminStorage + , market_contract = detGenKeyAddress "dummy-impossible-contract-key" + , auction_price = toMutez 0 + , token_index = 0 + , token_metadata = exampleTokenMetadata + , basis_points = 100 + , cost_mutez = constantLambda (toEnum 0) -- examplePiecewisePolynomial' + , unclaimed = toMutez 0 + } + +-- | exampleStorage with admin set +exampleStorageWithAdmin :: Address -> Storage (Lambda Natural Mutez) +exampleStorageWithAdmin admin = + exampleStorage { admin = AdminStorage admin Nothing False } + +-- | exampleStoragePiecewise w/ distinct values +exampleStoragePiecewise' :: Storage PiecewisePolynomial +exampleStoragePiecewise' = Storage + { admin = exampleAdminStorage + , market_contract = detGenKeyAddress "dummy-impossible-contract-key" + , auction_price = toMutez 0 + , token_index = 2 + , token_metadata = exampleTokenMetadata + , basis_points = 100 + , cost_mutez = examplePiecewisePolynomial' + , unclaimed = toMutez 3 + } + +-- | Print properly-formatted michelson values for exampleStorage +-- +-- ("admin","Pair (Pair \"tz2C97sask3WgSSg27bJhFxuBwW6MMU4uTPK\" False) None") +-- ("market_contract","\"tz2UXHa5WU79MnWF5uKFRM6qUowX13pdgJGy\"") +-- storage for distinguishing fields: +-- "{ Pair (Pair \"tz2C97sask3WgSSg27bJhFxuBwW6MMU4uTPK\" False) None; \"tz2UXHa5WU79MnWF5uKFRM6qUowX13pdgJGy\"; 0; 2; Pair 42 { Elt \"decimals\" 0x3132; Elt \"name\" 0x546869732069732061207465737421205b6e616d655d; Elt \"symbol\" 0x746573745f73796d626f6c }; 100; Pair { Pair 6 { 7; 8 } } { 4; 5 }; 3 }" +-- +-- exampleStorage: +-- "{ Pair (Pair \"tz2C97sask3WgSSg27bJhFxuBwW6MMU4uTPK\" False) None; \"tz2UXHa5WU79MnWF5uKFRM6qUowX13pdgJGy\"; 0; 0; Pair 42 { Elt \"decimals\" 0x3132; Elt \"name\" 0x546869732069732061207465737421205b6e616d655d; Elt \"symbol\" 0x746573745f73796d626f6c }; 100; Pair { Pair 6 { 7; 8 } } { 4; 5 }; 0 }"printExampleStorage' :: IO () +-- +printExampleStoragePiecewise' :: IO () +printExampleStoragePiecewise' = do + print $ ("admin" :: String, printLorentzValue False exampleAdminStorage) + print $ ("market_contract" :: String, printLorentzValue False $ market_contract exampleStoragePiecewise') + putStrLn ("storage for distinguishing fields:" :: Text) + print $ printLorentzValue False exampleStoragePiecewise' + putStrLn ("" :: Text) + putStrLn ("exampleStorage:" :: Text) + print $ printLorentzValue False exampleStoragePiecewise + +storageStr :: String +storageStr = "{ Pair (Pair \"tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb\" False) None; \"tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb\"; 0; 0; { Elt \"decimals\" 0x3132; Elt \"name\" 0x546869732069732061207465737421205b6e616d655d; Elt \"symbol\" 0x746573745f73796d626f6c }; 100; Pair { Pair 6 { 7; 8 } } { 4; 5 }; 3 }" + + + +data Entrypoints + = Default () + | Admin AdminEntrypoints + | Set_delegate (Maybe KeyHash) + | Withdraw () + | Buy () + | Buy_offchain Address + | Sell TokenId + | Sell_offchain (TokenId, Address) + deriving stock (Eq, Show) + +customGeneric "Entrypoints" ligoLayout +deriving anyclass instance IsoValue Entrypoints +deriving anyclass instance HasAnnotation Entrypoints + +instance ParameterHasEntrypoints Entrypoints where + -- EpdRecursive so that AdminEntrypoints are reached + type ParameterEntrypointsDerivation Entrypoints = EpdRecursive + +-- TODO: unused +-- -- | Error resulting from an empty formula +-- errEmptyFormula :: MText +-- errEmptyFormula = [mt|EMPTY_FORMULA|] + diff --git a/packages/minter-contracts/src-hs/Lorentz/Contracts/BondingCurve/Interface/Debug.hs b/packages/minter-contracts/src-hs/Lorentz/Contracts/BondingCurve/Interface/Debug.hs new file mode 100644 index 000000000..46383add9 --- /dev/null +++ b/packages/minter-contracts/src-hs/Lorentz/Contracts/BondingCurve/Interface/Debug.hs @@ -0,0 +1,38 @@ +-- | Lorentz interface for the bonding curve contract (debug) +module Lorentz.Contracts.BondingCurve.Interface.Debug where + +import Lorentz + +import Lorentz.Contracts.BondingCurve.Interface () +import Lorentz.Contracts.SimpleAdmin (AdminEntrypoints(..)) +import Lorentz.Contracts.Spec.FA2Interface (TokenId) + +-- Same as bonding curve entrypoints, but GetCost +data DebugEntrypoints + = Default () + | Admin AdminEntrypoints + | Set_delegate (Maybe KeyHash) + | Withdraw () + | Buy () + | Buy_offchain Address + | Sell TokenId + | Sell_offchain (TokenId, Address) + + -- | Get the current cost (debug only) + | Cost Natural + + -- | Get the Michelson implementation of (x ^ n) for (x, n) input (debug only) + | Pow (Natural, Natural) + + -- | Get the Michelson implementation of the example formula 0 (debug only) + | ExampleFormula0 Natural + deriving stock (Eq, Show) + +customGeneric "DebugEntrypoints" ligoLayout +deriving anyclass instance IsoValue DebugEntrypoints +deriving anyclass instance HasAnnotation DebugEntrypoints + +instance ParameterHasEntrypoints DebugEntrypoints where + -- EpdRecursive so that AdminEntrypoints are reached + type ParameterEntrypointsDerivation DebugEntrypoints = EpdRecursive + diff --git a/packages/minter-contracts/src-hs/Lorentz/Contracts/FA2.hs b/packages/minter-contracts/src-hs/Lorentz/Contracts/FA2.hs index baeef2b56..99f34d2d2 100644 --- a/packages/minter-contracts/src-hs/Lorentz/Contracts/FA2.hs +++ b/packages/minter-contracts/src-hs/Lorentz/Contracts/FA2.hs @@ -8,6 +8,7 @@ module Lorentz.Contracts.FA2 import Fmt (Buildable(..), genericF) import Lorentz +import Util () -- instance Buildable ByteString import qualified Lorentz.Contracts.Spec.FA2Interface as FA2I @@ -34,6 +35,7 @@ data TokenMetadata = TokenMetadata customGeneric "TokenMetadata" rightComb deriving anyclass instance IsoValue TokenMetadata deriving anyclass instance HasAnnotation TokenMetadata +instance Buildable TokenMetadata where build = genericF -- | TZIP-12 (and, consequently, @morley-ledgers@) does not prescribe any specific -- layout for the FA2 parameter. diff --git a/packages/minter-contracts/src-hs/Lorentz/Contracts/MinterCollection/Nft/Contract.hs b/packages/minter-contracts/src-hs/Lorentz/Contracts/MinterCollection/Nft/Contract.hs new file mode 100644 index 000000000..a4a9f89ee --- /dev/null +++ b/packages/minter-contracts/src-hs/Lorentz/Contracts/MinterCollection/Nft/Contract.hs @@ -0,0 +1,13 @@ +-- | Lorentz bindings for NFT multi-asset contract +module Lorentz.Contracts.MinterCollection.Nft.Contract where + +import Lorentz (Contract) +import Lorentz.Test.Import (embedContractM) + +import Lorentz.Contracts.MinterSdk (inBinFolder) + +import Lorentz.Contracts.MinterCollection.Nft.Types + +nftContract :: Contract NftEntrypoints NftStorage +nftContract = $$(embedContractM (inBinFolder "fa2_multi_nft_asset.tz")) + diff --git a/packages/minter-contracts/src-hs/Lorentz/Contracts/MinterCollection/Nft/Types.hs b/packages/minter-contracts/src-hs/Lorentz/Contracts/MinterCollection/Nft/Types.hs new file mode 100644 index 000000000..15a860a44 --- /dev/null +++ b/packages/minter-contracts/src-hs/Lorentz/Contracts/MinterCollection/Nft/Types.hs @@ -0,0 +1,124 @@ + +-- | Lorentz bindings for NFT multi-asset contract +module Lorentz.Contracts.MinterCollection.Nft.Types where + +import Fmt (Buildable(..), genericF) +import Lorentz + +import Lorentz.Contracts.SimpleAdmin (AdminEntrypoints(..), AdminStorage(..)) +import qualified Lorentz.Contracts.FA2 as FA2 + +import Lorentz.Contracts.Spec.FA2Interface (TokenId(..)) + +-- TODO: move to Lorentz.Contracts.SimpleAdmin +import Lorentz.Contracts.BondingCurve.Interface (exampleAdminStorage) + +-- type nft_meta = (token_id, token_metadata) big_map +-- type ledger = (token_id, address) big_map +-- type nft_token_storage = { +-- ledger : ledger; +-- token_metadata : nft_meta; +-- next_token_id : token_id; +-- operators : operator_storage; +-- } +data NftTokenStorage = NftTokenStorage + { ledger :: BigMap TokenId Address + , token_metadata :: BigMap TokenId FA2.TokenMetadata + , next_token_id :: TokenId + , operators :: FA2.OperatorStorage + } deriving stock (Eq, Show) + +customGeneric "NftTokenStorage" ligoLayout +deriving anyclass instance IsoValue NftTokenStorage +deriving anyclass instance HasAnnotation NftTokenStorage +instance Buildable NftTokenStorage where build = genericF + +exampleNftTokenStorage :: NftTokenStorage +exampleNftTokenStorage = NftTokenStorage + { ledger = mempty + , token_metadata = mempty + , next_token_id = TokenId 0 + , operators = mempty + } + +-- type nft_asset_storage = { +-- assets : nft_token_storage; +-- admin : admin_storage; +-- metadata: (string, bytes) big_map; (* contract metadata *) +-- } +data NftStorage = NftStorage + { assets :: NftTokenStorage + , admin :: AdminStorage + , metadata :: BigMap MText ByteString + } deriving stock (Eq, Show) + +customGeneric "NftStorage" ligoLayout +deriving anyclass instance IsoValue NftStorage +deriving anyclass instance HasAnnotation NftStorage +instance Buildable NftStorage where build = genericF + +exampleNftStorage :: NftStorage +exampleNftStorage = NftStorage + { assets = exampleNftTokenStorage + , admin = exampleAdminStorage + , metadata = mempty + } + +-- | exampleNftStorage with admin set +exampleNftStorageWithAdmin :: Address -> NftStorage +exampleNftStorageWithAdmin admin = + exampleNftStorage { admin = AdminStorage admin Nothing False } + +---- | Print properly-formatted michelson values for exampleStorage +---- +---- ("admin","Pair (Pair \"tz2C97sask3WgSSg27bJhFxuBwW6MMU4uTPK\" False) None") +---- ("market_contract","\"tz2UXHa5WU79MnWF5uKFRM6qUowX13pdgJGy\"") +---- "{ Pair (Pair \"tz2C97sask3WgSSg27bJhFxuBwW6MMU4uTPK\" False) None; \"tz2UXHa5WU79MnWF5uKFRM6qUowX13pdgJGy\"; 0; 1; 2; 100; Pair { Pair 6 { 7; 8 } } { 4; 5 }; 3 }" +--printExampleStorage' :: IO () +--printExampleStorage' = do +-- print $ ("admin" :: String, printLorentzValue False exampleAdminStorage) +-- print $ ("market_contract" :: String, printLorentzValue False $ market_contract exampleStorage') +-- print $ printLorentzValue False exampleStorage' + + +-- type mint_tokens_param = mint_token_param list +type MintTokensParam = [MintTokenParam] + +-- type mint_token_param = +-- [@layout:comb] +-- { +-- token_metadata: token_metadata; +-- owner : address; +-- } +data MintTokenParam = MintTokenParam + { token_metadata :: FA2.TokenMetadata + , owner :: Address + } deriving stock (Eq, Show) + +customGeneric "MintTokenParam" ligoCombLayout +deriving anyclass instance IsoValue MintTokenParam +deriving anyclass instance HasAnnotation MintTokenParam + + +-- type nft_asset_entrypoints = +-- | Assets of fa2_entry_points +-- | Mint of mint_tokens_param +-- | Burn of (token_id * bytes) +-- | Update_metadata of (token_metadata list) +-- | Admin of admin_entrypoints +data NftEntrypoints + = Assets FA2.Parameter + | Mint MintTokensParam + | Burn (TokenId, Address) + | Update_metadata [FA2.TokenMetadata] + | Admin AdminEntrypoints + deriving stock (Eq, Show) + +customGeneric "NftEntrypoints" ligoLayout +deriving anyclass instance IsoValue NftEntrypoints +deriving anyclass instance HasAnnotation NftEntrypoints + +instance ParameterHasEntrypoints NftEntrypoints where + -- EpdRecursive so that AdminEntrypoints are reached + type ParameterEntrypointsDerivation NftEntrypoints = EpdRecursive + diff --git a/packages/minter-contracts/src-hs/Lorentz/Contracts/SimpleAdmin.hs b/packages/minter-contracts/src-hs/Lorentz/Contracts/SimpleAdmin.hs index a8cc55b10..58e082876 100644 --- a/packages/minter-contracts/src-hs/Lorentz/Contracts/SimpleAdmin.hs +++ b/packages/minter-contracts/src-hs/Lorentz/Contracts/SimpleAdmin.hs @@ -11,7 +11,7 @@ data AdminStorage = AdminStorage { admin :: Address , pendingAdmin :: Maybe Address , paused :: Bool - } + } deriving stock (Eq, Show) customGeneric "AdminStorage" ligoLayout deriving anyclass instance IsoValue AdminStorage @@ -22,6 +22,7 @@ data AdminEntrypoints = Set_admin Address | Confirm_admin | Pause Bool + deriving stock (Eq, Show) customGeneric "AdminEntrypoints" ligoLayout deriving anyclass instance IsoValue AdminEntrypoints @@ -34,7 +35,7 @@ initAdminStorage :: Address -> AdminStorage initAdminStorage admin = AdminStorage { admin = admin , pendingAdmin = Nothing - , paused = False + , paused = False } -- Errors @@ -52,5 +53,5 @@ notPendingAdmin = [mt|NOT_A_PENDING_ADMIN|] noPendingAdmin :: MText noPendingAdmin = [mt|NO_PENDING_ADMIN|] -errPaused :: MText +errPaused :: MText errPaused = [mt|PAUSED|] diff --git a/packages/minter-contracts/src/bonding-curve.ts b/packages/minter-contracts/src/bonding-curve.ts new file mode 100644 index 000000000..f283cd9a6 --- /dev/null +++ b/packages/minter-contracts/src/bonding-curve.ts @@ -0,0 +1,112 @@ +import { Contract } from './type-aliases'; +import { TezosToolkit } from '@taquito/taquito'; +import { originateContract } from './ligo'; +import { + BondingCurveCode, +} from '../bin-ts'; +import { $log } from '@tsed/logger'; + + +export async function originateBondingCurve( + tz: TezosToolkit, + storage: string | Record, +): Promise { + $log.info(`originating bonding curve contract..`); + return originateContract(tz, BondingCurveCode.code, storage, 'bonding-curve'); +} + + +// Convert an int to its superscript form, e.g. 123 -> '¹²³' +function toSuperscript(num: number) { + const superscripts = "⁰¹²³⁴⁵⁶⁷⁸⁹"; + const num_str = num.toString(); + let output = ''; + for (let i = 0; i < num_str.length; i++) { + output += superscripts[parseInt(num_str[i])]; + } + return output; +} + +// const toSuperscriptTests = [0,1,2,3,4,5,6,7,8,9,10,11,22,154,1234654]; +// for (let i = 0; i < toSuperscriptTests.length; i++) { +// console.log(toSuperscriptTests[i], toSuperscript(toSuperscriptTests[i])); +// } + +// // 0 "⁰" +// // ?editor_console=true:57 1 "¹" +// // ?editor_console=true:57 2 "²" +// // ?editor_console=true:57 3 "³" +// // ?editor_console=true:57 4 "⁴" +// // ?editor_console=true:57 5 "⁵" +// // ?editor_console=true:57 6 "⁶" +// // ?editor_console=true:57 7 "⁷" +// // ?editor_console=true:57 8 "⁸" +// // ?editor_console=true:57 9 "⁹" +// // ?editor_console=true:57 10 "¹⁰" +// // ?editor_console=true:57 11 "¹¹" +// // ?editor_console=true:57 22 "²²" +// // ?editor_console=true:57 154 "¹⁵⁴" +// // ?editor_console=true:57 1234654 "¹²³⁴⁶⁵⁴" + +// Convert a coefficient list to a Unicode polynomial string +export function toPolynomialUnicode(coefficients: Array) { + let output = ''; + for (let i = 0; i < coefficients.length; i++) { + if (i == 0) { + if (coefficients[i] !== 0) { + output += `${coefficients[i]} + `; + } + } else if (coefficients[i] === 1) { + output += `x${toSuperscript(i)} + `; + } else if (coefficients[i] === -1) { + output = output.replace(/ \+ $/, ''); + output += ` - x${toSuperscript(i)} + `; + } else if (coefficients[i] !== 0) { + output += `${coefficients[i]} × x${toSuperscript(i)} + `; + } + } + return output.replace(/ \+ $/, ''); +} + +// Convert a coefficient list to an ASCII polynomial string +export function toPolynomialAscii(coefficients: Array) { + let output = ''; + for (let i = 0; i < coefficients.length; i++) { + if (i == 0) { + if (coefficients[i] !== 0) { + output += `${coefficients[i]} + `; + } + } else if (coefficients[i] === 1) { + output += `x^${i} + `; + } else if (coefficients[i] === -1) { + output = output.replace(/ \+ $/, ''); + output += ` - x^${i} + `; + } else if (coefficients[i] !== 0) { + output += `${coefficients[i]} * x^${i} + `; + } + } + return output.replace(/ \+ $/, ''); +} + +// let input = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 11, 12]; +// let input2 = [-1, -1, -2, -3, -4, -5, -6, 1, -8, -9, -1, -11, -12]; + +// x¹ + 2 * x² + 3 * x³ + 4 * x⁴ + 5 * x⁵ + 6 * x⁶ + 7 * x⁷ + 8 * x⁸ + 9 * x⁹ + x¹⁰ + 11 * x¹¹ + 12 * x¹² +// console.log(toPolynomialUnicode(input)); + +// x^1 + 2 * x^2 + 3 * x^3 + 4 * x^4 + 5 * x^5 + 6 * x^6 + 7 * x^7 + 8 * x^8 + 9 * x^9 + x^10 + 11 * x^11 + 12 * x^12 +// console.log(toPolynomialAscii(input)); + +// -1 - x¹ + -2 * x² + -3 * x³ + -4 * x⁴ + -5 * x⁵ + -6 * x⁶ + x⁷ + -8 * x⁸ + -9 * x⁹ - x¹⁰ + -11 * x¹¹ + -12 * x¹² +// console.log(toPolynomialUnicode(input2)); + +// -1 - x^1 + -2 * x^2 + -3 * x^3 + -4 * x^4 + -5 * x^5 + -6 * x^6 + x^7 + +// -8 * x^8 + -9 * x^9 - x^10 + -11 * x^11 + -12 * x^12 +// console.log(toPolynomialAscii(input2)); + + +export { + BondingCurveCode, + BondingCurveContractType, +} from '../bin-ts'; + diff --git a/packages/minter-contracts/src/compile-ligo.ts b/packages/minter-contracts/src/compile-ligo.ts index 8440dd316..9af45d759 100644 --- a/packages/minter-contracts/src/compile-ligo.ts +++ b/packages/minter-contracts/src/compile-ligo.ts @@ -40,29 +40,65 @@ type CompileSourceEntry = { // add contracts here const compileSources: CompileSourceEntry[] = [ { - srcFile: 'minter_collection/nft/fa2_multi_nft_faucet.mligo', - mainFn: 'nft_faucet_main', - dstFile: 'fa2_multi_nft_faucet.tz', + srcFile: 'bonding_curve/bonding_curve.mligo', + mainFn: 'bonding_curve_main', + dstFile: 'bonding_curve.tz', contract: true, }, { - srcFile: 'minter_collection/nft/fa2_multi_nft_asset_no_admin.mligo', - mainFn: 'nft_asset_main', - dstFile: 'fa2_multi_nft_asset_no_admin.tz', + srcFile: 'bonding_curve/bonding_curve_debug.mligo', + mainFn: 'bonding_curve_main', + dstFile: 'bonding_curve_debug.tz', contract: true, }, + { + srcFile: 'bonding_curve/bonding_curve_piecewise.mligo', + mainFn: 'bonding_curve_main', + dstFile: 'bonding_curve_piecewise.tz', + contract: true, + }, + { + srcFile: 'bonding_curve/bonding_curve_piecewise_debug.mligo', + mainFn: 'bonding_curve_main', + dstFile: 'bonding_curve_piecewise_debug.tz', + contract: true, + }, + { + srcFile: 'bonding_curve/bonding_curve.mligo', + mainFn: 'example_formula0', + dstFile: 'bonding_curve_example_formula_0.tz', + contract: false, + }, + { srcFile: 'minter_collection/nft/fa2_multi_nft_asset_simple_admin.mligo', mainFn: 'nft_asset_main', dstFile: 'fa2_multi_nft_asset.tz', contract: true, }, + + // broken: minting doesn't work because of storage.admin.admin call + // { + // srcFile: 'minter_collection/nft/fa2_multi_nft_asset_no_admin.mligo', + // mainFn: 'nft_asset_main', + // dstFile: 'fa2_multi_nft_asset_no_admin.tz', + // contract: true, + // }, + // multi_admin is untested + // { + // srcFile: 'minter_collection/nft/fa2_multi_nft_asset_multi_admin.mligo', + // mainFn: 'nft_asset_main', + // dstFile: 'fa2_multi_nft_asset_multi_admin.tz', + // contract: true, + // }, + { - srcFile: 'minter_collection/nft/fa2_multi_nft_asset_multi_admin.mligo', - mainFn: 'nft_asset_main', - dstFile: 'fa2_multi_nft_asset_multi_admin.tz', + srcFile: 'minter_collection/nft/fa2_multi_nft_faucet.mligo', + mainFn: 'nft_faucet_main', + dstFile: 'fa2_multi_nft_faucet.tz', contract: true, }, + { srcFile: 'minter_collection/nft/fa2_multi_nft_asset_non_pausable_simple_admin.mligo', mainFn: 'nft_asset_main', @@ -482,6 +518,7 @@ const compileSources: CompileSourceEntry[] = [ dstFile: 'pausable_wallet.tz', contract: true, }, + ]; const filterSources = (sources: CompileSourceEntry[]): CompileSourceEntry[] => { diff --git a/packages/minter-contracts/test-hs/Test/BondingCurve.hs b/packages/minter-contracts/test-hs/Test/BondingCurve.hs new file mode 100644 index 000000000..174edb0ca --- /dev/null +++ b/packages/minter-contracts/test-hs/Test/BondingCurve.hs @@ -0,0 +1,1342 @@ +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE InstanceSigs #-} + +-- | Tests for bonding curve contract +module Test.BondingCurve where + +import Fmt (Buildable) +import Prelude hiding (swap) +import System.IO (writeFile) + +import qualified Data.Text.Lazy as L +import Test.Tasty (TestTree, testGroup) + +import Lorentz.Base +import Lorentz.Value +import Michelson.Printer +import Michelson.Text (unsafeMkMText) +import Michelson.Typed.Scope (ConstantScope) +import Michelson.Typed.Sing () -- (KnownT) +import Morley.Nettest +import Morley.Nettest.Tasty +import Tezos.Address + +import qualified Lorentz.Contracts.FA2 as FA2 +import Lorentz.Contracts.Spec.FA2Interface +import Lorentz.Contracts.BondingCurve +import Lorentz.Contracts.BondingCurve.Interface +import Lorentz.Contracts.BondingCurve.Interface.Debug (DebugEntrypoints(..)) +import Lorentz.Contracts.MinterCollection.Nft.Types + +import Test.Util + +import Test.SimpleAdmin +import Test.MinterCollection.Nft (originateNft) + +---------------------------------------------------------------------------------------- +-- Originators +---------------------------------------------------------------------------------------- + +originateBondingCurve + :: MonadNettest caps base m + => Storage (Lambda Natural Mutez) + -> m (ContractHandler Entrypoints (Storage (Lambda Natural Mutez))) +originateBondingCurve storage = + originateSimple "bonding-curve" storage bondingCurveContract + +originateBondingCurveWithBalance + :: MonadNettest caps base m + => Mutez + -> Storage (Lambda Natural Mutez) + -> m (ContractHandler Entrypoints (Storage (Lambda Natural Mutez))) +originateBondingCurveWithBalance balance storage = + originate $ OriginateData + { odName = "bonding-curve" + , odBalance = balance + , odStorage = storage + , odContract = bondingCurveContract + } + +originateDebugBondingCurve + :: MonadNettest caps base m + => Storage (Lambda Natural Mutez) + -> m (ContractHandler DebugEntrypoints (Storage (Lambda Natural Mutez))) +originateDebugBondingCurve storage = + originateSimple "debug-bonding-curve" storage debugBondingCurveContract + +originateBondingCurvePiecewise + :: MonadNettest caps base m + => Storage PiecewisePolynomial + -> m (ContractHandler Entrypoints (Storage PiecewisePolynomial)) +originateBondingCurvePiecewise storage = + originateSimple "bonding-curve-piecewise" storage bondingCurvePiecewiseContract + +originateBondingCurvePiecewiseWithBalance + :: MonadNettest caps base m + => Mutez + -> Storage PiecewisePolynomial + -> m (ContractHandler Entrypoints (Storage PiecewisePolynomial)) +originateBondingCurvePiecewiseWithBalance balance storage = + originate $ OriginateData + { odName = "bonding-curve-piecewise" + , odBalance = balance + , odStorage = storage + , odContract = bondingCurvePiecewiseContract + } + +originateDebugBondingCurvePiecewise + :: MonadNettest caps base m + => Storage PiecewisePolynomial + -> m (ContractHandler DebugEntrypoints (Storage PiecewisePolynomial)) +originateDebugBondingCurvePiecewise storage = + originateSimple "debug-bonding-curve-piecewise" storage debugBondingCurvePiecewiseContract + +---------------------------------------------------------------------------------------- +-- Admin tests +---------------------------------------------------------------------------------------- + +-- Test SimpleAdmin admin ownership transfer +test_AdminChecks :: TestTree +test_AdminChecks = + adminOwnershipTransferChecks @Entrypoints @(Storage (Lambda Natural Mutez)) + (\admin -> + originateBondingCurve + (exampleStorageWithAdmin admin) + ) + +-- Test SimpleAdmin admin ownership transfer +test_AdminChecksPiecewise :: TestTree +test_AdminChecksPiecewise = + adminOwnershipTransferChecks @Entrypoints @(Storage PiecewisePolynomial) + (\admin -> + originateBondingCurvePiecewise + (exampleStoragePiecewiseWithAdmin admin) + ) + +---------------------------------------------------------------------------------------- +-- Test data +---------------------------------------------------------------------------------------- + +tokenMetadata0 :: TokenMetadata +tokenMetadata0 = mkTokenMetadata "nft-symbol-0" "nft-name-0" "12" + +tokenMetadata0' :: TokenId -> FA2.TokenMetadata +tokenMetadata0' tokenId = FA2.TokenMetadata + { tokenId = tokenId + , tokenInfo = tokenMetadata0 + } + + + +---------------------------------------------------------------------------------------- +-- Integration tests +---------------------------------------------------------------------------------------- + +withdrawTest :: forall c. + String + -> (forall caps base m. MonadNettest caps base m + => Address + -> Address + -> Mutez + -> m (ContractHandler Entrypoints (Storage c))) + -> TestTree +withdrawTest name originator = nettestScenarioCaps ("Withdraw " <> name) $ do + setup <- doFA2Setup + let admin ::< alice ::< SNil = sAddresses setup + let !SNil = sTokens setup + + -- ensure admin has no tez + withSender admin $ + getBalance admin >>= transferMoney alice + getBalance admin @@== 0 + + let withdrawAmount = 1234 + bondingCurve <- originator admin alice withdrawAmount + + -- admin only + withSender alice $ + call bondingCurve (Call @"Withdraw") () + & expectError (unsafeMkMText "NOT_AN_ADMIN") + + withSender admin $ + call bondingCurve (Call @"Withdraw") () + + getBalance admin @@== withdrawAmount + +withdrawTestPiecewise :: TestTree +withdrawTestPiecewise = withdrawTest @PiecewisePolynomial "Piecewise" $ \admin alice withdrawAmount -> do + let bondingCurveStorage :: Storage PiecewisePolynomial = + (exampleStoragePiecewiseWithAdmin admin) + { + market_contract = alice + , unclaimed = withdrawAmount + } + originateBondingCurvePiecewiseWithBalance withdrawAmount bondingCurveStorage + +withdrawTestLambda :: TestTree +withdrawTestLambda = withdrawTest @(Lambda Natural Mutez) "Lambda" $ \admin alice withdrawAmount -> do + let bondingCurveStorage :: Storage (Lambda Natural Mutez) = + (exampleStorageWithAdmin admin) + { + market_contract = alice + , unclaimed = withdrawAmount + } + originateBondingCurveWithBalance withdrawAmount bondingCurveStorage + + +withdrawBakingRewardsTest :: forall c. (Buildable c, Eq c) + => String + -> (forall caps base m. MonadNettest caps base m + => Address + -> Address + -> m (Storage c, ContractHandler Entrypoints (Storage c))) + -> TestTree +withdrawBakingRewardsTest name originator = nettestScenarioOnEmulatorCaps ("Withdraw Baking Rewards " <> name) $ do + setup <- doFA2Setup + let admin ::< alice ::< bob ::< SNil = sAddresses setup + let !SNil = sTokens setup + halfAdminBalance <- (`div` 2) <$> getBalance admin + + -- ensure admin has no tez and that alice, bob have sufficient tez + withSender admin $ do + transferMoney alice halfAdminBalance + transferMoney bob halfAdminBalance + getBalance admin @@== 0 + + let aliceRewardAmount = 12 + let bobRewardAmount = 24 + let withdrawAmount = aliceRewardAmount + bobRewardAmount + (bondingCurveStorage, bondingCurve) <- originator admin alice + + withSender alice $ + transferMoney bondingCurve aliceRewardAmount + + postAliceRewardStorage <- getStorage' bondingCurve + postAliceRewardStorage @== bondingCurveStorage { unclaimed = aliceRewardAmount } + + withSender bob $ + transfer $ + TransferData + { tdTo = bondingCurve + , tdAmount = bobRewardAmount + , tdEntrypoint = DefEpName + , tdParameter = () + } + + postBobRewardStorage <- getStorage' bondingCurve + postBobRewardStorage @== bondingCurveStorage { unclaimed = withdrawAmount } + + -- admin only + withSender alice $ + call bondingCurve (Call @"Withdraw") () + & expectError (unsafeMkMText "NOT_AN_ADMIN") + + withSender admin $ + call bondingCurve (Call @"Withdraw") () + + getBalance admin @@== withdrawAmount + +withdrawBakingRewardsTestPiecewise :: TestTree +withdrawBakingRewardsTestPiecewise = withdrawBakingRewardsTest @PiecewisePolynomial "Piecewise" $ \admin alice -> do + let bondingCurveStorage :: Storage PiecewisePolynomial = + (exampleStoragePiecewiseWithAdmin admin) + { + market_contract = alice + , unclaimed = 0 + } + bondingCurve <- originateBondingCurvePiecewise bondingCurveStorage + return (bondingCurveStorage, bondingCurve) + +withdrawBakingRewardsTestLambda :: TestTree +withdrawBakingRewardsTestLambda = withdrawBakingRewardsTest @(Lambda Natural Mutez) "Lambda" $ \admin alice -> do + let bondingCurveStorage :: Storage (Lambda Natural Mutez) = + (exampleStorageWithAdmin admin) + { + market_contract = alice + , unclaimed = 0 + } + bondingCurve <- originateBondingCurve bondingCurveStorage + return (bondingCurveStorage, bondingCurve) + + +buyNoMintTest :: forall c. + String + -> (forall caps base m. MonadNettest caps base m + => Address + -> Address + -> m (ContractHandler Entrypoints (Storage c))) + -> TestTree +buyNoMintTest name originator = nettestScenarioCaps ("Buy: NO_MINT " <> name) $ do + setup <- doFA2Setup + let admin ::< alice ::< SNil = sAddresses setup + let !SNil = sTokens setup + + bondingCurve <- originator admin alice + + withSender alice $ + call bondingCurve (Call @"Buy") () + & expectError (unsafeMkMText "NO_MINT") + +buyNoMintTestPiecewise :: TestTree +buyNoMintTestPiecewise = buyNoMintTest @PiecewisePolynomial "Piecewise" $ \admin alice -> do + let bondingCurveStorage :: Storage PiecewisePolynomial = + (exampleStoragePiecewiseWithAdmin admin) + { + market_contract = alice + , cost_mutez = constantPiecewisePolynomial 0 + } + originateBondingCurvePiecewise bondingCurveStorage + +buyNoMintTestLambda :: TestTree +buyNoMintTestLambda = buyNoMintTest @(Lambda Natural Mutez) "Lambda" $ \admin alice -> do + let bondingCurveStorage :: Storage (Lambda Natural Mutez) = + (exampleStorageWithAdmin admin) + { + market_contract = alice + , cost_mutez = constantLambda 0 + } + originateBondingCurve bondingCurveStorage + + +-- sell with token_index = 0 always fails with NO_TOKENS +sellTokenIndex0Test :: forall c. + String + -> (forall caps base m. MonadNettest caps base m + => Address + -> Address + -> m (ContractHandler Entrypoints (Storage c))) + -> TestTree +sellTokenIndex0Test name originator = nettestScenarioOnEmulatorCaps ("Sell: token_index = 0 " <> name) $ do + setup <- doFA2Setup + let admin ::< alice ::< SNil = sAddresses setup + let !SNil = sTokens setup + nft <- originateNft (exampleNftStorageWithAdmin admin) + + bondingCurve <- originator admin (toAddress nft) + + withSender admin $ + call bondingCurve (Call @"Sell") (TokenId 0) + & expectError (unsafeMkMText "NO_TOKENS") + + withSender alice $ + call bondingCurve (Call @"Sell") (TokenId 0) + & expectError (unsafeMkMText "NO_TOKENS") + + +sellTokenIndex0TestPiecewise :: TestTree +sellTokenIndex0TestPiecewise = sellTokenIndex0Test @PiecewisePolynomial "Piecewise" $ \admin nftAddress -> do + let bondingCurveStorage :: Storage PiecewisePolynomial = + (exampleStoragePiecewiseWithAdmin admin) + { + market_contract = nftAddress + , token_index = 0 + } + originateBondingCurvePiecewise bondingCurveStorage + +sellTokenIndex0TestLambda :: TestTree +sellTokenIndex0TestLambda = sellTokenIndex0Test @(Lambda Natural Mutez) "Lambda" $ \admin nftAddress -> do + let bondingCurveStorage :: Storage (Lambda Natural Mutez) = + (exampleStorageWithAdmin admin) + { + market_contract = nftAddress + , token_index = 0 + } + originateBondingCurve bondingCurveStorage + + +-- sell with token_index = 0 always fails with NO_TOKENS +sellOffchainTokenIndex0Test :: forall c. + String + -> (forall caps base m. MonadNettest caps base m + => Address + -> Address + -> m (ContractHandler Entrypoints (Storage c))) + -> TestTree +sellOffchainTokenIndex0Test name originator = nettestScenarioOnEmulatorCaps ("Sell_offchain: token_index = 0 " <> name) $ do + setup <- doFA2Setup + let admin ::< alice ::< SNil = sAddresses setup + let !SNil = sTokens setup + nft <- originateNft (exampleNftStorageWithAdmin admin) + bondingCurve <- originator admin (toAddress nft) + + withSender admin $ + call bondingCurve (Call @"Sell_offchain") (TokenId 0, admin) + & expectError (unsafeMkMText "NO_TOKENS") + + withSender admin $ + call bondingCurve (Call @"Sell_offchain") (TokenId 0, alice) + & expectError (unsafeMkMText "NO_TOKENS") + +sellOffchainTokenIndex0TestPiecewise :: TestTree +sellOffchainTokenIndex0TestPiecewise = sellOffchainTokenIndex0Test @PiecewisePolynomial "Piecewise" $ \admin nftAddress -> do + let bondingCurveStorage :: Storage PiecewisePolynomial = + (exampleStoragePiecewiseWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = constantPiecewisePolynomial 0 + , token_index = 0 + } + originateBondingCurvePiecewise bondingCurveStorage + +sellOffchainTokenIndex0TestLambda :: TestTree +sellOffchainTokenIndex0TestLambda = sellOffchainTokenIndex0Test @(Lambda Natural Mutez) "Lambda" $ \admin nftAddress -> do + let bondingCurveStorage :: Storage (Lambda Natural Mutez) = + (exampleStorageWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = constantLambda 0 + , token_index = 0 + } + originateBondingCurve bondingCurveStorage + + + +--- too little/much tez +--- Spec: +-- + Mints token using `token_metadata` from storage to buyer +-- + Increments `token_index` +-- + Adds the `basis_points` fee to the `unclaimed` tez in storage +buyTest :: forall c. (Buildable c, Eq c) + => String + -> (forall caps base m. MonadNettest caps base m + => Address + -> Address + -> m (Storage c, ContractHandler DebugEntrypoints (Storage c))) + -> TestTree +buyTest name originator = nettestScenarioOnEmulatorCaps ("Buy " <> name) $ do + setup <- doFA2Setup + let admin ::< alice ::< SNil = sAddresses setup + let !SNil = sTokens setup + nft <- originateNft ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { ledger = [(TokenId 0, admin)] + , next_token_id = TokenId 1 + } }) + (bondingCurveStorage, bondingCurve) <- originator admin (toAddress nft) + + -- admin needs to set operator on (TokenId 0) to allow bondingCurve to mint + withSender admin $ + call nft (Call @"Update_operators") + [ AddOperator OperatorParam + { opOwner = admin + , opOperator = toAddress bondingCurve + , opTokenId = TokenId 0 + } + ] + + withSender alice $ + call bondingCurve (Call @"Cost") 0 + & expectError (WrappedValue (0 :: Integer)) + + preBuyStorage <- getStorage' bondingCurve + preBuyStorage @== bondingCurveStorage + + withSender alice $ + call bondingCurve (Call @"Buy") () + & expectError (WrappedValue ((unsafeMkMText "WRONG_TEZ_PRICE", toEnum 0 :: Mutez), toEnum 10 :: Mutez)) + + -- buy one token + withSender alice $ + transfer $ + TransferData + { tdTo = bondingCurve + , tdAmount = 10 + , tdEntrypoint = ep "buy" + , tdParameter = () + } + + postBuyStorage <- getStorage' bondingCurve + postBuyStorage @== bondingCurveStorage { token_index = 1 } + + +buyTestPiecewise :: TestTree +buyTestPiecewise = buyTest @PiecewisePolynomial "Piecewise" $ \admin nftAddress -> do + let bondingCurveStorage :: Storage PiecewisePolynomial = + (exampleStoragePiecewiseWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = constantPiecewisePolynomial 0 + , auction_price = 10 + } + bondingCurve <- originateDebugBondingCurvePiecewise bondingCurveStorage + return (bondingCurveStorage, bondingCurve) + +buyTestLambda :: TestTree +buyTestLambda = buyTest @(Lambda Natural Mutez) "Lambda" $ \admin nftAddress -> do + let bondingCurveStorage :: Storage (Lambda Natural Mutez) = + (exampleStorageWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = constantLambda 0 + , auction_price = 10 + } + bondingCurve <- originateDebugBondingCurve bondingCurveStorage + return (bondingCurveStorage, bondingCurve) + + +buyOffchainTest :: forall c. (Buildable c, Eq c) + => String + -> (forall caps base m. MonadNettest caps base m + => Address + -> Address + -> m (Storage c, ContractHandler DebugEntrypoints (Storage c))) + -> TestTree +buyOffchainTest name originator = nettestScenarioOnEmulatorCaps ("Buy_offchain " <> name) $ do + setup <- doFA2Setup + let admin ::< alice ::< bob ::< charlie ::< SNil = sAddresses setup + let !SNil = sTokens setup + nft <- originateNft ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { ledger = [(TokenId 0, admin)] + , next_token_id = TokenId 1 + } }) + (bondingCurveStorage, bondingCurve) <- originator admin (toAddress nft) + + -- admin needs to set operator on (TokenId 0) to allow bondingCurve to mint + withSender admin $ + call nft (Call @"Update_operators") + [ AddOperator OperatorParam + { opOwner = admin + , opOperator = toAddress bondingCurve + , opTokenId = TokenId 0 + } + ] + + -- not admin only + withSender alice $ + call bondingCurve (Call @"Buy_offchain") alice + + withSender admin $ + call bondingCurve (Call @"Buy_offchain") alice + + withSender bob $ + call bondingCurve (Call @"Buy_offchain") bob + + withSender admin $ + call bondingCurve (Call @"Buy") () + + -- the token admin bought can't be transferred by charlie + withSender charlie $ + call nft (Call @"Transfer") + [ TransferItem + { tiFrom = admin + , tiTxs = [ TransferDestination + { tdTo = charlie + , tdTokenId = TokenId 4 + , tdAmount = 1 + } ] + } + ] + & expectError (unsafeMkMText "FA2_NOT_OPERATOR") + + -- the token admin bought can be transferred by admin to charlie + withSender admin $ + call nft (Call @"Transfer") + [ TransferItem + { tiFrom = admin + , tiTxs = [ TransferDestination + { tdTo = charlie + , tdTokenId = TokenId 4 + , tdAmount = 1 + } ] + } + ] + + postBuyNftStorage <- getStorage' nft + postBuyNftStorage @== (exampleNftStorageWithAdmin admin) { + assets = exampleNftTokenStorage { + next_token_id = TokenId 5 + , ledger = + [ (TokenId 0, admin) + , (TokenId 1, alice) + , (TokenId 2, alice) + , (TokenId 3, bob) + , (TokenId 4, charlie) + ] + , operators = [(FA2.OperatorKey + { owner = admin + , operator = toAddress bondingCurve + , tokenId = TokenId 0 + }, ())] + , token_metadata = + [ (TokenId 1, tokenMetadata0' (TokenId 1)) + , (TokenId 2, tokenMetadata0' (TokenId 2)) + , (TokenId 3, tokenMetadata0' (TokenId 3)) + , (TokenId 4, tokenMetadata0' (TokenId 4)) + ] + } } + + postBuyStorage <- getStorage' bondingCurve + postBuyStorage @== bondingCurveStorage { token_index = 4 } + + +buyOffchainTestPiecewise :: TestTree +buyOffchainTestPiecewise = buyOffchainTest @PiecewisePolynomial "Piecewise" $ \admin nftAddress -> do + let bondingCurveStorage :: Storage PiecewisePolynomial = + (exampleStoragePiecewiseWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = constantPiecewisePolynomial 0 + , token_metadata = tokenMetadata0 + } + bondingCurve <- originateDebugBondingCurvePiecewise bondingCurveStorage + return (bondingCurveStorage, bondingCurve) + +buyOffchainTestLambda :: TestTree +buyOffchainTestLambda = buyOffchainTest @(Lambda Natural Mutez) "Lambda" $ \admin nftAddress -> do + let bondingCurveStorage :: Storage (Lambda Natural Mutez) = + (exampleStorageWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = constantLambda 0 + , token_metadata = tokenMetadata0 + } + bondingCurve <- originateDebugBondingCurve bondingCurveStorage + return (bondingCurveStorage, bondingCurve) + + + +buyBatchOffchainTest :: forall c. (Buildable c, Eq c) + => String + -> (forall caps base m. MonadNettest caps base m + => Address + -> Address + -> m (Storage c, ContractHandler DebugEntrypoints (Storage c))) + -> TestTree +buyBatchOffchainTest name originator = nettestScenarioOnEmulatorCaps ("Buy_offchain (batch) " <> name) $ do + setup <- doFA2Setup + let admin ::< alice ::< bob ::< SNil = sAddresses setup + let !SNil = sTokens setup + nft <- originateNft ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { ledger = [(TokenId 0, admin)] + , next_token_id = TokenId 1 + } }) + (bondingCurveStorage, bondingCurve) <- originator admin (toAddress nft) + + -- admin needs to set operator on (TokenId 0) to allow bondingCurve to mint + withSender admin $ + call nft (Call @"Update_operators") + [ AddOperator OperatorParam + { opOwner = admin + , opOperator = toAddress bondingCurve + , opTokenId = TokenId 0 + } + ] + + withSender admin $ + call bondingCurve (Call @"Buy_offchain") alice + + withSender admin $ + call bondingCurve (Call @"Buy_offchain") bob + + postBuyNftStorage <- getStorage' nft + postBuyNftStorage @== (exampleNftStorageWithAdmin admin) { + assets = exampleNftTokenStorage { + next_token_id = TokenId 3 + , ledger = [(TokenId 0, admin), (TokenId 1, alice), (TokenId 2, bob)] + , operators = [(FA2.OperatorKey + { owner = admin + , operator = toAddress bondingCurve + , tokenId = TokenId 0 + }, ())] + , token_metadata = [(TokenId 1, tokenMetadata0' (TokenId 1)), (TokenId 2, tokenMetadata0' (TokenId 2))] + } } + + postBuyStorage <- getStorage' bondingCurve + postBuyStorage @== bondingCurveStorage { token_index = 2 } + + +buyBatchOffchainTestPiecewise :: TestTree +buyBatchOffchainTestPiecewise = buyBatchOffchainTest @PiecewisePolynomial "Piecewise" $ \admin nftAddress -> do + let bondingCurveStorage :: Storage PiecewisePolynomial = + (exampleStoragePiecewiseWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = constantPiecewisePolynomial 0 + , token_metadata = tokenMetadata0 + } + bondingCurve <- originateDebugBondingCurvePiecewise bondingCurveStorage + return (bondingCurveStorage, bondingCurve) + +buyBatchOffchainTestLambda :: TestTree +buyBatchOffchainTestLambda = buyBatchOffchainTest @(Lambda Natural Mutez) "Lambda" $ \admin nftAddress -> do + let bondingCurveStorage :: Storage (Lambda Natural Mutez) = + (exampleStorageWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = constantLambda 0 + , token_metadata = tokenMetadata0 + } + bondingCurve <- originateDebugBondingCurve bondingCurveStorage + return (bondingCurveStorage, bondingCurve) + + +--- call w/ admin (no tokens owned) +--- call w/ seller +--- Spec: +-- + Price is calculared as in `Buy`, without the `basis_points` fee: +-- * `auction_price` +-- * `cost_mutez` applied to `token_index` +-- + The token is burned on the FA2 marketplace +-- + Tez equal to the price is sent to the seller +-- , nettestScenarioCaps "Sell" $ do +sellTest :: forall c. + String + -> (forall caps base m. MonadNettest caps base m + => Address + -> Address + -> m (ContractHandler Entrypoints (Storage c))) + -> TestTree +sellTest name originator = nettestScenarioOnEmulatorCaps ("Sell " <> name) $ do + setup <- doFA2Setup + let admin ::< minter ::< alice ::< bob ::< SNil = sAddresses setup + let !SNil = sTokens setup + nft <- originateNft ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { ledger = [(TokenId 0, admin)] + , next_token_id = TokenId 1 + } }) + bondingCurve <- originator admin (toAddress nft) + + -- admin needs to set operator on (TokenId 0) to allow bondingCurve to mint + withSender admin $ + call nft (Call @"Update_operators") + [ AddOperator OperatorParam + { opOwner = admin + , opOperator = toAddress bondingCurve + , opTokenId = TokenId 0 + } + , AddOperator OperatorParam + { opOwner = admin + , opOperator = minter + , opTokenId = TokenId 0 + } + ] + + -- alice can't sell a token that doesn't exist + withSender alice $ + call bondingCurve (Call @"Sell") (TokenId 1) + & expectError (unsafeMkMText "WRONG_ID") + + -- mint to alice + withSender minter $ + call nft (Call @"Mint") [MintTokenParam + { token_metadata = tokenMetadata0' (TokenId 1) + , owner = alice + }] + + -- bob can't sell alice's token + withSender bob $ + call bondingCurve (Call @"Sell") (TokenId 1) + & expectError (unsafeMkMText "NOT_BURNER") + + -- before token_id=1 burned + preBurnStorage <- getStorage' nft + preBurnStorage @== (exampleNftStorageWithAdmin admin) { + assets = exampleNftTokenStorage { + next_token_id = TokenId 2 + , ledger = [(TokenId 0, admin), (TokenId 1, alice)] + , operators = [(FA2.OperatorKey + { owner = admin + , operator = toAddress bondingCurve + , tokenId = TokenId 0 + }, ()) + , (FA2.OperatorKey + { owner = admin + , operator = minter + , tokenId = TokenId 0 + }, ()) + ] + , token_metadata = [(TokenId 1, tokenMetadata0' (TokenId 1))] + } } + + withSender alice $ + call bondingCurve (Call @"Sell") (TokenId 1) + + -- can't sell twice + withSender alice $ + call bondingCurve (Call @"Sell") (TokenId 1) + & expectError (unsafeMkMText "NO_TOKENS") + + -- ensure tokenId0 burned + postBurnStorage <- getStorage' nft + postBurnStorage @== (exampleNftStorageWithAdmin admin) { + assets = exampleNftTokenStorage { + next_token_id = TokenId 2 + , ledger = [(TokenId 0, admin)] + , operators = [(FA2.OperatorKey + { owner = admin + , operator = toAddress bondingCurve + , tokenId = TokenId 0 + }, ()) + , (FA2.OperatorKey + { owner = admin + , operator = minter + , tokenId = TokenId 0 + }, ()) + ] + } } + + +sellTestPiecewise :: TestTree +sellTestPiecewise = sellTest @PiecewisePolynomial "Piecewise" $ \admin nftAddress -> do + let bondingCurveStorage :: Storage PiecewisePolynomial = + (exampleStoragePiecewiseWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = constantPiecewisePolynomial 0 + , token_index = 1 -- token_index must be > 0 to sell + , token_metadata = tokenMetadata0 + } + originateBondingCurvePiecewise bondingCurveStorage + +sellTestLambda :: TestTree +sellTestLambda = sellTest @(Lambda Natural Mutez) "Lambda" $ \admin nftAddress -> do + let bondingCurveStorage :: Storage (Lambda Natural Mutez) = + (exampleStorageWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = constantLambda 0 + , token_index = 1 -- token_index must be > 0 to sell + , token_metadata = tokenMetadata0 + } + originateBondingCurve bondingCurveStorage + + +sellOffchainTest :: forall c. + String + -> (forall caps base m. MonadNettest caps base m + => Mutez + -> Address + -> Address + -> m (ContractHandler Entrypoints (Storage c))) + -> TestTree +sellOffchainTest name originator = nettestScenarioOnEmulatorCaps ("Sell_offchain " <> name) $ do + setup <- doFA2Setup + let admin ::< minter ::< alice ::< bob ::< SNil = sAddresses setup + let !SNil = sTokens setup + nft <- originateNft ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { ledger = [(TokenId 0, admin)] + , next_token_id = TokenId 1 + } }) + bondingCurve <- originator 10 admin (toAddress nft) + + -- admin needs to set operator on (TokenId 0) to allow bondingCurve to mint + withSender admin $ + call nft (Call @"Update_operators") + [ AddOperator OperatorParam + { opOwner = admin + , opOperator = toAddress bondingCurve + , opTokenId = TokenId 0 + } + , AddOperator OperatorParam + { opOwner = admin + , opOperator = minter + , opTokenId = TokenId 0 + } + ] + + -- admin only + withSender alice $ + call bondingCurve (Call @"Sell_offchain") (TokenId 1, alice) + & expectError (unsafeMkMText "NOT_AN_ADMIN") + + withSender admin $ + call bondingCurve (Call @"Sell_offchain") (TokenId 1, alice) + & expectError (unsafeMkMText "WRONG_ID") + + -- mint to alice + withSender minter $ + call nft (Call @"Mint") [MintTokenParam + { token_metadata = tokenMetadata0' (TokenId 1) + , owner = alice + }] + + + -- bob can't sell alice's token + withSender bob $ + call bondingCurve (Call @"Sell") (TokenId 1) + & expectError (unsafeMkMText "NOT_BURNER") + + preSellStorage <- getStorage' nft + preSellStorage @== (exampleNftStorageWithAdmin admin) { + assets = exampleNftTokenStorage { + next_token_id = TokenId 2 + , ledger = [(TokenId 0, admin), (TokenId 1, alice)] + , operators = [(FA2.OperatorKey + { owner = admin + , operator = toAddress bondingCurve + , tokenId = TokenId 0 + }, ()) + , (FA2.OperatorKey + { owner = admin + , operator = minter + , tokenId = TokenId 0 + }, ()) + ] + , token_metadata = [(TokenId 1, tokenMetadata0' (TokenId 1))] + } } + + aliceBalanceBefore <- getBalance alice + + withSender admin $ + call bondingCurve (Call @"Sell_offchain") (TokenId 1, alice) + + aliceBalanceAfter <- getBalance alice + (aliceBalanceAfter - aliceBalanceBefore) @== 10 + + -- can't sell twice + withSender admin $ + call bondingCurve (Call @"Sell_offchain") (TokenId 1, alice) + & expectError (unsafeMkMText "NO_TOKENS") + + -- ensure (TokenId 1) burned + postBurnStorage <- getStorage' nft + postBurnStorage @== (exampleNftStorageWithAdmin admin) { + assets = exampleNftTokenStorage { + next_token_id = TokenId 2 + , ledger = [(TokenId 0, admin)] + , operators = [(FA2.OperatorKey + { owner = admin + , operator = toAddress bondingCurve + , tokenId = TokenId 0 + }, ()) + , (FA2.OperatorKey + { owner = admin + , operator = minter + , tokenId = TokenId 0 + }, ()) + ] + } } + + +sellOffchainTestPiecewise :: TestTree +sellOffchainTestPiecewise = sellOffchainTest @PiecewisePolynomial "Piecewise" $ \bondingCurveBalance admin nftAddress -> do + let bondingCurveStorage :: Storage PiecewisePolynomial = + (exampleStoragePiecewiseWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = constantPiecewisePolynomial 10 + , token_index = 1 -- token_index > 0 to sell tokens, otherwise no tokens to sell + , token_metadata = tokenMetadata0 + } + originateBondingCurvePiecewiseWithBalance bondingCurveBalance bondingCurveStorage + +sellOffchainTestLambda :: TestTree +sellOffchainTestLambda = sellOffchainTest @(Lambda Natural Mutez) "Lambda" $ \bondingCurveBalance admin nftAddress -> do + let bondingCurveStorage :: Storage (Lambda Natural Mutez) = + (exampleStorageWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = constantLambda 10 + , token_index = 1 -- token_index must be > 0 to sell + , token_metadata = tokenMetadata0 + } + originateBondingCurveWithBalance bondingCurveBalance bondingCurveStorage + + +buySellTest :: forall c. (Buildable c, Eq c, ConstantScope (ToT c), IsoValue c) + => String + -> (forall caps base m. MonadNettest caps base m + => Mutez + -> Natural + -> Address + -> Address + -> m (Storage c, ContractHandler DebugEntrypoints (Storage c))) + -> TestTree +buySellTest name originator = nettestScenarioOnEmulatorCaps ("Buy Sell " <> name) $ do + let logFile = "buy_sell_test_data_" <> name <> ".txt" + liftIO $ writeFile logFile "Buy Sell Test\n" + + let dontForceSingleLine = False + let log = liftIO . appendFile logFile . ("\n" <>) + + setup <- doFA2Setup + let admin ::< alice ::< bob ::< charlie ::< SNil = sAddresses setup + + log "(admin, alice, bob, charlie)" + log $ show (formatAddress admin, formatAddress alice, formatAddress bob, formatAddress charlie) + log "" + + let !SNil = sTokens setup + let nftStorage = ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { ledger = [(TokenId 0, admin)] + , next_token_id = TokenId 1 + } }) + log "nft storage" + log . L.toStrict . printTypedValue dontForceSingleLine $ toVal nftStorage + log "" + + nft <- originateNft nftStorage + log "nft address" + log . formatAddress $ toAddress nft + log "" + + let auctionPrice = 100 + let basisPoints = 100 + (bondingCurveStorage, bondingCurve) <- originator auctionPrice basisPoints admin (toAddress nft) + + log "bonding curve storage" + log . L.toStrict . printTypedValue dontForceSingleLine $ toVal bondingCurveStorage + log "" + + log "bonding curve address" + log . formatAddress $ toAddress bondingCurve + log "" + + -- admin needs to set operator on (TokenId 0) to allow bondingCurve to mint + let updateOperators :: [UpdateOperator] = + [ AddOperator OperatorParam + { opOwner = admin + , opOperator = toAddress bondingCurve + , opTokenId = TokenId 0 + } + ] + log "admin -> nft: update_operators" + log . L.toStrict . printTypedValue dontForceSingleLine $ toVal updateOperators + log "" + withSender admin $ + call nft (Call @"Update_operators") updateOperators + + let buyers :: [(Integer, Address)] = + [ (10, alice) + , (60, bob) + , (170, charlie) + ] + + forM_ (zip [0..] buyers) $ \(index, (amount, buyer)) -> do + + withSender buyer $ + call bondingCurve (Call @"Cost") index + & expectError (WrappedValue amount) + + let insufficientAmount :: Mutez = fromIntegral $ amount + let buyAmount :: Mutez = fromIntegral $ fromIntegral auctionPrice + amount + + -- basis_points fee required + withSender buyer $ + transfer ( + TransferData + { tdTo = bondingCurve + , tdAmount = insufficientAmount + , tdEntrypoint = ep "buy" + , tdParameter = () + }) + & expectError (WrappedValue ((unsafeMkMText "WRONG_TEZ_PRICE", insufficientAmount), buyAmount)) + + log "buyer -> bondingCurve: buy" + log "buyer:" + log $ formatAddress buyer + log "amount:" + log . L.toStrict . printTypedValue dontForceSingleLine $ toVal buyAmount + log "" + withSender buyer $ + transfer $ + TransferData + { tdTo = bondingCurve + , tdAmount = buyAmount + , tdEntrypoint = ep "buy" + , tdParameter = () + } + + let sellers :: [(Natural, (Integer, Address))] = zip [1..] buyers + + forM_ (reverse sellers) $ \(tokenId, (expectedCost, seller)) -> do + sellerBalanceBefore <- getBalance seller + + log "seller -> bondingCurve: sell" + log "seller:" + log $ formatAddress seller + log "parameter:" + log . L.toStrict . printTypedValue dontForceSingleLine $ toVal (TokenId tokenId) + log "" + withSender seller $ + call bondingCurve (Call @"Sell") (TokenId tokenId) + + let preFeeSellAmount = auctionPrice + fromIntegral expectedCost + let sellAmount = fromInteger . removeBasisPointFee basisPoints . fromIntegral $ preFeeSellAmount + + -- ensure cost was expected + sellerBalanceAfter <- getBalance seller + (tokenId, (sellerBalanceAfter - sellerBalanceBefore)) @== (tokenId, sellAmount) + + -- ensure zero tokens remaining and unclaimed is expected + postSellStorage <- getStorage' bondingCurve + postSellStorage @== bondingCurveStorage { unclaimed = 4 } + + log "admin -> bondingCurve: withdraw" + log "admin:" + log $ formatAddress admin + log "parameter:" + log . L.toStrict . printTypedValue dontForceSingleLine $ toVal () + log "" + withSender admin $ + call bondingCurve (Call @"Withdraw") () + + getBalance bondingCurve @@== 0 + + -- ensure storage reset after all tokens sold and Withdraw is called + postWithdrawStorage <- getStorage' bondingCurve + postWithdrawStorage @== bondingCurveStorage + + +buySellTestPiecewise :: TestTree +buySellTestPiecewise = buySellTest @PiecewisePolynomial "Piecewise" $ \auctionPrice basisPoints admin nftAddress -> do + let bondingCurveStorage :: Storage PiecewisePolynomial = + (exampleStoragePiecewiseWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = polynomialToPiecewisePolynomial [10, 20, 30] + , auction_price = auctionPrice + , basis_points = basisPoints + } + bondingCurve <- originateDebugBondingCurvePiecewise bondingCurveStorage + return (bondingCurveStorage, bondingCurve) + +buySellTestLambda :: TestTree +buySellTestLambda = buySellTest @(Lambda Natural Mutez) "Lambda" $ \auctionPrice basisPoints admin nftAddress -> do + let bondingCurveStorage :: Storage (Lambda Natural Mutez) = + (exampleStorageWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = constantsLambda $ fromInteger . runPiecewisePolynomial (polynomialToPiecewisePolynomial [10, 20, 30]) <$> [0..5] + , auction_price = auctionPrice + , basis_points = basisPoints + } + bondingCurve <- originateDebugBondingCurve bondingCurveStorage + return (bondingCurveStorage, bondingCurve) + + + +buySellOffchainTest :: forall c. (Buildable c, Eq c, ConstantScope (ToT c)) + => String + -> (forall caps base m. MonadNettest caps base m + => Mutez + -> Natural + -> Address + -> Address + -> m (Storage c, ContractHandler DebugEntrypoints (Storage c))) + -> TestTree +buySellOffchainTest name originator = nettestScenarioOnEmulatorCaps ("Buy Sell Offchain " <> name) $ do + setup <- doFA2Setup + let admin ::< alice ::< bob ::< charlie ::< SNil = sAddresses setup + let !SNil = sTokens setup + nft <- originateNft ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { ledger = [(TokenId 0, admin)] + , next_token_id = TokenId 1 + } }) + + let auctionPrice = 100 + let basisPoints = 100 + (bondingCurveStorage, bondingCurve) <- originator auctionPrice basisPoints admin (toAddress nft) + + -- admin needs to set operator on (TokenId 0) to allow bondingCurve to mint + withSender admin $ + call nft (Call @"Update_operators") + [ AddOperator OperatorParam + { opOwner = admin + , opOperator = toAddress bondingCurve + , opTokenId = TokenId 0 + } + ] + + let buyers :: [(Integer, Address)] = + [ (10, alice) + , (60, bob) + , (170, charlie) + ] + + forM_ (zip [0..] buyers) $ \(index, (amount, buyer)) -> do + + withSender buyer $ + call bondingCurve (Call @"Cost") index + & expectError (WrappedValue amount) + + let insufficientAmount :: Mutez = fromIntegral amount + let buyAmount :: Mutez = fromIntegral $ fromIntegral auctionPrice + amount + + -- basis_points fee required + withSender admin $ + transfer ( + TransferData + { tdTo = bondingCurve + , tdAmount = insufficientAmount + , tdEntrypoint = ep "buy_offchain" + , tdParameter = buyer + }) + & expectError (WrappedValue ((unsafeMkMText "WRONG_TEZ_PRICE", insufficientAmount), buyAmount)) + + withSender admin $ + transfer $ + TransferData + { tdTo = bondingCurve + , tdAmount = buyAmount + , tdEntrypoint = ep "buy_offchain" + , tdParameter = buyer + } + + let sellers :: [(Natural, (Integer, Address))] = zip [1..] buyers + + forM_ (reverse sellers) $ \(tokenId, (expectedCost, seller)) -> do + sellerBalanceBefore <- getBalance seller + + withSender admin $ + call bondingCurve (Call @"Sell_offchain") (TokenId tokenId, seller) + + let sellAmount = fromInteger . removeBasisPointFee basisPoints . fromIntegral $ auctionPrice + fromIntegral expectedCost + + -- ensure cost was expected + sellerBalanceAfter <- getBalance seller + (tokenId, (sellerBalanceAfter - sellerBalanceBefore)) @== (tokenId, sellAmount) + + -- ensure zero tokens remaining and unclaimed is expected + postSellStorage <- getStorage' bondingCurve + postSellStorage @== bondingCurveStorage { unclaimed = 4 } + + withSender admin $ + call bondingCurve (Call @"Withdraw") () + + getBalance bondingCurve @@== 0 + + -- ensure storage reset after all tokens sold and Withdraw is called + postWithdrawStorage <- getStorage' bondingCurve + postWithdrawStorage @== bondingCurveStorage + + +buySellOffchainTestPiecewise :: TestTree +buySellOffchainTestPiecewise = buySellOffchainTest @PiecewisePolynomial "Piecewise" $ \auctionPrice basisPoints admin nftAddress -> do + let bondingCurveStorage :: Storage PiecewisePolynomial = + (exampleStoragePiecewiseWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = polynomialToPiecewisePolynomial [10, 20, 30] + , auction_price = auctionPrice + , basis_points = basisPoints + } + bondingCurve <- originateDebugBondingCurvePiecewise bondingCurveStorage + return (bondingCurveStorage, bondingCurve) + +buySellOffchainTestLambda :: TestTree +buySellOffchainTestLambda = buySellOffchainTest @(Lambda Natural Mutez) "Lambda" $ \auctionPrice basisPoints admin nftAddress -> do + let bondingCurveStorage :: Storage (Lambda Natural Mutez) = + (exampleStorageWithAdmin admin) + { + market_contract = nftAddress + , cost_mutez = constantsLambda $ fromInteger . runPiecewisePolynomial (polynomialToPiecewisePolynomial [10, 20, 30]) <$> [0..5] + , auction_price = auctionPrice + , basis_points = basisPoints + } + bondingCurve <- originateDebugBondingCurve bondingCurveStorage + return (bondingCurveStorage, bondingCurve) + + + +test_Integrational :: TestTree +test_Integrational = testGroup "Integrational" + [ withdrawTestPiecewise + , withdrawTestLambda + + , withdrawBakingRewardsTestPiecewise + , withdrawBakingRewardsTestLambda + + , buyNoMintTestPiecewise + , buyNoMintTestLambda + + , buyTestPiecewise + , buyTestLambda + + , buyOffchainTestPiecewise + , buyOffchainTestLambda + + , buyBatchOffchainTestPiecewise + , buyBatchOffchainTestLambda + + , sellTokenIndex0TestPiecewise + , sellTokenIndex0TestLambda + + , sellTestPiecewise + , sellTestLambda + + , sellOffchainTokenIndex0TestPiecewise + , sellOffchainTokenIndex0TestLambda + + , sellOffchainTestPiecewise + , sellOffchainTestLambda + + , buySellTestPiecewise + , buySellTestLambda + + , buySellOffchainTestPiecewise + , buySellOffchainTestLambda + ] + +-- input, expectedOutput, storageF +-- +-- storageF is applied to the generated admin address +callCostTest :: + Natural + -> Integer + -> (Address -> Storage (Lambda Natural Mutez)) + -> TestTree +callCostTest input expectedOutput storageF = + nettestScenarioCaps ("Call Lambda Cost with " ++ show input) $ do + setup <- doFA2Setup @("addresses" :# 1) @("tokens" :# 0) + let admin ::< SNil = sAddresses setup + let bondingCurveStorage = storageF admin + bondingCurve <- originateDebugBondingCurve bondingCurveStorage + + call bondingCurve (Call @"Cost") input + & expectError (WrappedValue expectedOutput) + +-- input, expectedOutput, storageF +-- +-- storageF is applied to the generated admin address +callCostTestPiecewise :: + Natural + -> Integer + -> (Address -> Storage PiecewisePolynomial) + -> TestTree +callCostTestPiecewise input expectedOutput storageF = + nettestScenarioCaps ("Call Piecewise Polynomial Cost with " ++ show input) $ do + setup <- doFA2Setup @("addresses" :# 1) @("tokens" :# 0) + let admin ::< SNil = sAddresses setup + let bondingCurveStorage = storageF admin + bondingCurve <- originateDebugBondingCurvePiecewise bondingCurveStorage + + call bondingCurve (Call @"Cost") input + & expectError (WrappedValue expectedOutput) + +-- input, expectedOutput, storageF +-- +-- storageF is applied to the generated admin address +callPowTest :: + Natural + -> Natural + -> Integer + -> (Address -> Storage PiecewisePolynomial) + -> TestTree +callPowTest x n expectedOutput storageF = + nettestScenarioCaps ("Call Pow with " ++ show (x, n)) $ do + setup <- doFA2Setup @("addresses" :# 1) @("tokens" :# 0) + let admin ::< SNil = sAddresses setup + let bondingCurveStorage = storageF admin + bondingCurve <- originateDebugBondingCurvePiecewise bondingCurveStorage + + call bondingCurve (Call @"Pow") (x, n) + & expectError (WrappedValue expectedOutput) + + +-- test cost function using the debug version of the contract +test_Debug :: TestTree +test_Debug = testGroup "Debug" + [ -- default storage cost_mutez(4) == 34 + callCostTestPiecewise 4 39 exampleStoragePiecewiseWithAdmin + + -- (constantPiecewisePolynomial 0) cost_mutez(12) == 0 + , callCostTestPiecewise 12 0 (\admin -> (exampleStoragePiecewiseWithAdmin admin) + { cost_mutez = constantPiecewisePolynomial 0 }) + + , callPowTest 1 3 1 exampleStoragePiecewiseWithAdmin + , callPowTest 2 3 8 exampleStoragePiecewiseWithAdmin + , callPowTest 3 4 81 exampleStoragePiecewiseWithAdmin + , callPowTest 2 10 1024 exampleStoragePiecewiseWithAdmin + + ] + diff --git a/packages/minter-contracts/test-hs/Test/BondingCurve/Property.hs b/packages/minter-contracts/test-hs/Test/BondingCurve/Property.hs new file mode 100644 index 000000000..81deda7f3 --- /dev/null +++ b/packages/minter-contracts/test-hs/Test/BondingCurve/Property.hs @@ -0,0 +1,624 @@ +{-# LANGUAGE OverloadedLists #-} + +-- | Property tests for bonding curve contract +module Test.BondingCurve.Property where + +import Fmt (Buildable, Builder, build, unlinesF) + +import Prelude hiding (swap) +import Data.List (genericIndex) + +import Hedgehog ((===), Gen, MonadTest, Property, PropertyT, forAll, property) +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Gen.QuickCheck as Gen +import qualified Hedgehog.Range as Range +import Test.QuickCheck (NonEmptyList(..), NonNegative(..)) + +import Lorentz.Contracts.Spec.FA2Interface +import Lorentz.Value (Mutez, ToAddress(..), toMutez) +import Michelson.Text (unsafeMkMText) +import Morley.Nettest +import Morley.Nettest.Abstract (SpecificOrDefaultAliasHint(..)) + +import Lorentz.Contracts.BondingCurve.Interface +import Lorentz.Contracts.MinterCollection.Nft.Types + +import Test.Util + +import Test.BondingCurve +import Test.MinterCollection.Nft (originateNft) + +-- TestData in a format where we get Arbitrary for free +-- +-- data ValidTestData = ValidTestData +-- { piecewisePoly :: ([(NonNegative Integer, NonEmptyList Integer)], NonEmptyList Integer) +-- , polyInput :: NonNegative Integer +-- } +-- deriving stock (Eq, Show, Generic) +-- +-- instance Arbitrary ValidTestData where +-- arbitrary = liftM2 ValidTestData arbitrary arbitrary +-- shrink = recursivelyShrink +type ValidTestData = (([(NonNegative Integer, NonEmptyList Integer)], NonEmptyList Integer), NonNegative Integer) + +-- convert ValidTestData to TestData +fromValidTestData :: ValidTestData -> TestData +fromValidTestData (piecewisePoly, polyInput) = TestData + { piecewisePoly = + uncurry PiecewisePolynomial . + bimap + (fmap (bimap (fromInteger . getNonNegative) getNonEmpty)) + getNonEmpty $ + piecewisePoly + , polyInput = fromInteger $ getNonNegative polyInput + } + +data TestData = TestData + -- | Polynomials have up to + -- - 2^6=128 coefficients + -- - 2^10=1024 coefficient absolute value + -- - 2^9=512 offsets + -- - 2^5=32 segments + { piecewisePoly :: PiecewisePolynomial + + -- Tested up to 2^10=1024 + , polyInput :: Natural + } + deriving stock (Eq, Show) + +-- drop all segments except last_segment +testDataWithOnlyLastSegment :: TestData -> TestData +testDataWithOnlyLastSegment TestData{..} = TestData + { piecewisePoly = PiecewisePolynomial + { segments = [] + , last_segment = last_segment piecewisePoly + } + , polyInput = polyInput + } + +testDataSizes :: TestData -> (Int, Int, Int) +testDataSizes TestData{..} = + ( length (segments piecewisePoly) + , safeMaximum $ fmap (length . snd) (segments piecewisePoly) + , length (last_segment piecewisePoly) + ) + where + -- maximum fails on [] + safeMaximum :: [Int] -> Int + safeMaximum [] = 0 + safeMaximum xs = maximum xs + + +-- | Shrink a list by alternatively removing any element +shrinkList :: [a] -> [[a]] +shrinkList xs = (\i -> take i xs ++ drop (i+1) xs) <$> [0..1 `subtract` length xs] -- this is length - 1, because (-) is overloaded weird by Lorentz + +-- | Shrink a list by alternatively removing any element, except the last one +shrinkListNonEmpty :: [a] -> [[a]] +shrinkListNonEmpty [] = [] +shrinkListNonEmpty [_] = [] +shrinkListNonEmpty xs = (\i -> take i xs ++ drop (i+1) xs) <$> [0..1 `subtract` length xs] -- this is length - 1, because (-) is overloaded weird by Lorentz + +-- shrink towards 0 or keep equal (for shrinkPolynomial) +shrinkCoefficient :: Integer -> [Integer] +shrinkCoefficient x = [x - signum x, x] + +-- cartesianProduct [[1,2],[3,4],[5,6]] +-- [[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]] +cartesianProduct :: [[a]] -> [[a]] +cartesianProduct [] = [[]] +cartesianProduct (x:xs) = do + y <- x + ys <- cartesianProduct xs + return (y:ys) + +-- | all options of shrinking or now each coefficient +shrinkCoefficients :: [Integer] -> [[Integer]] +shrinkCoefficients xs = cartesianProduct $ fmap shrinkCoefficient xs + +-- | Shrink list and/or coefficients +shrinkPolynomial :: [Integer] -> [[Integer]] +-- shrinkPolynomial xs = shrinkList xs >>= shrinkCoefficients +shrinkPolynomial xs = shrinkListNonEmpty xs >>= shrinkCoefficients + +-- | Generate a polynomial +genPolynomial :: Gen [Integer] +genPolynomial = + -- Gen.shrink shrinkList $ + Gen.shrink shrinkListNonEmpty $ + Gen.list (Range.constant 1 32) (Gen.integral (Range.constant -512 512)) + +shrinkPiecewisePolySegment :: (Natural, [Integer]) -> [(Natural, [Integer])] +shrinkPiecewisePolySegment (segmentLength, polynomial) = do + segmentLength' <- [segmentLength, safePred segmentLength..0] + polynomial' <- shrinkPolynomial polynomial + pure (segmentLength', polynomial') + +genPiecewisePolySegment :: Gen (Natural, [Integer]) +genPiecewisePolySegment = Gen.shrink shrinkPiecewisePolySegment $ do + segmentLength <- Gen.integral (Range.constant 0 32) + polynomial <- genPolynomial + pure (segmentLength, polynomial) + +shrinkPiecewisePoly :: PiecewisePolynomial -> [PiecewisePolynomial] +shrinkPiecewisePoly PiecewisePolynomial{..} = do + segments' <- shrinkList segments >>= cartesianProduct . fmap shrinkPiecewisePolySegment + + last_segment' <- shrinkPolynomial last_segment + pure $ PiecewisePolynomial + { segments = segments' + , last_segment = last_segment' + } + +genPiecewisePoly :: Gen PiecewisePolynomial +genPiecewisePoly = Gen.shrink shrinkPiecewisePoly $ do + segments <- Gen.shrink shrinkList $ + Gen.list (Range.constant 0 16) genPiecewisePolySegment + last_segment <- genPolynomial + pure $ PiecewisePolynomial + { segments = segments + , last_segment = last_segment + } + +shrinkTestData :: TestData -> [TestData] +shrinkTestData TestData{..} = do + piecewisePoly' <- shrinkPiecewisePoly piecewisePoly + polyInput' <- [polyInput, safePred polyInput..0] + pure $ TestData + { piecewisePoly = piecewisePoly' + , polyInput = polyInput' + } + +genTestData :: Gen TestData +genTestData = Gen.shrink shrinkTestData $ do + piecewisePoly <- genPiecewisePoly + polyInput <- Gen.integral (Range.constant 0 1024) + pure $ TestData + { piecewisePoly = piecewisePoly + , polyInput = polyInput + } + +-- | TestData where runPiecewisePolynomial piecewisePoly polyInput >= 0 +genNonNegativeTestData :: Gen TestData +genNonNegativeTestData = + Gen.filter + (\TestData{..} -> 0 <= runPiecewisePolynomial piecewisePoly polyInput) + genTestData + + +-- runPolynomial behaves as expected for: +-- f(x) = 1 +hprop_runPolynomial_constant :: Property +hprop_runPolynomial_constant = property $ do + x <- forAll $ Gen.integral (Range.constant (negate 1024) 1024) + runPolynomial [1] x === 1 + +-- runPolynomial behaves as expected for: +-- f(x) = x +hprop_runPolynomial_line :: Property +hprop_runPolynomial_line = property $ do + x <- forAll $ Gen.integral (Range.constant (negate 1024) 1024) + runPolynomial [0, 1] x === x + +-- runPolynomial behaves as expected for: +-- f(x) = 2 x^2 + 3 x - 5 +hprop_runPolynomial_quadratic :: Property +hprop_runPolynomial_quadratic = property $ do + x :: Integer <- forAll $ Gen.integral (Range.constant (negate 1024) 1024) + runPolynomial [-5, 3, 2] x === 2 * x^(2 :: Integer) + 3 * x - 5 + +-- runPiecewisePolynomial (constantPiecewisePolynomial x) _ == x +hprop_runPiecewisePolynomial_constant :: Property +hprop_runPiecewisePolynomial_constant = property $ do + TestData{piecewisePoly, polyInput} <- forAll genTestData + let constant' = maybe 0 fst $ uncons $ last_segment piecewisePoly + runPiecewisePolynomial (constantPiecewisePolynomial constant') polyInput === + constant' + +-- runPiecewisePolynomial (linearPiecewisePolynomial rise run) x == rise + run * x +hprop_runPiecewisePolynomial_linear :: Property +hprop_runPiecewisePolynomial_linear = property $ do + TestData{piecewisePoly, polyInput} <- forAll genTestData + let (rise, run) = case last_segment piecewisePoly of + [] -> (0, 0) + [x] -> (x, 0) + (x:y:_) -> (x, y) + runPiecewisePolynomial (linearPiecewisePolynomial rise run) polyInput === + rise + run * toInteger polyInput + +-- runPiecewisePolynomial is equivalent to runPolynomial when there's only a +-- last_segment +hprop_runPiecewisePolynomial_is_runPolynomial :: Property +hprop_runPiecewisePolynomial_is_runPolynomial = property $ do + TestData{piecewisePoly, polyInput} <- forAll genTestData + let polynomial = last_segment piecewisePoly + + runPolynomial polynomial (toInteger polyInput) === + runPiecewisePolynomial (polynomialToPiecewisePolynomial polynomial) polyInput + +-- runPiecewisePolynomial is equivalent to runPolynomial when the input is +-- >= sum segmentLength's +hprop_runPiecewisePolynomial_is_runPolynomial_after_offsets :: Property +hprop_runPiecewisePolynomial_is_runPolynomial_after_offsets = property $ do + TestData{piecewisePoly, polyInput} <- forAll genTestData + let polynomial = last_segment piecewisePoly + let offsetInput :: Natural = polyInput + sum (fmap fst (segments piecewisePoly)) + + runPolynomial polynomial (toInteger offsetInput) === + runPiecewisePolynomial piecewisePoly offsetInput + + +-- Assert that calling the "Cost" entrypoint matches the implementation of runPiecewisePolynomial +testPiecewisePolynomialUsingCost :: (MonadIO m, MonadTest m) => TestData -> m () +testPiecewisePolynomialUsingCost TestData{piecewisePoly, polyInput} = + clevelandProp $ do + setup <- doFA2Setup @("addresses" :# 1) @("tokens" :# 0) + let admin ::< SNil = sAddresses setup + let bondingCurveStorage = (exampleStorageWithAdmin admin) { cost_mutez = piecewisePoly } + bondingCurve <- originateDebugBondingCurvePiecewise bondingCurveStorage + let expectedCost = runPiecewisePolynomial piecewisePoly polyInput + call bondingCurve (Call @"Cost") polyInput + & expectError (WrappedValue expectedCost) + +-- Call the "Cost" entrypoint on the debugBondingCurveContract to check the +-- LIGO implementation of runPiecewisePolynomial against the Haskell one +-- +-- (Run only on polynomials producing non-negative output for the given input, +-- see genNonNegativeTestData) +hprop_piecewise_polynomial_correct :: Property +hprop_piecewise_polynomial_correct = + property $ do + testData <- fromValidTestData <$> forAll Gen.arbitrary + testPiecewisePolynomialUsingCost testData + +-- hprop_piecewise_polynomial_correct but single-section piecewise polynomials +hprop_piecewise_polynomial_correct_only_last_segment :: Property +hprop_piecewise_polynomial_correct_only_last_segment = + property $ do + testData <- testDataWithOnlyLastSegment . fromValidTestData <$> forAll Gen.arbitrary + testPiecewisePolynomialUsingCost testData + +-- off-by-1 error +-- 194 == 24 + 18 + 32 + 2 + 15 + 18 + 3 + 15 + 12 + 16 + 13 + 26 +unitTestData :: TestData +unitTestData = TestData + { piecewisePoly = + PiecewisePolynomial + { segments = + [ ( 24 , [ -1024 ] ) + , ( 18 , [ -1024 ] ) + , ( 32 , [ -1024 ] ) + , ( 2 , [ -1024 ] ) + , ( 15 , [ -1024 ] ) + , ( 18 , [ -1024 ] ) + , ( 3 , [ -1024 ] ) + , ( 15 , [ -1024 ] ) + , ( 12 , [ -1024 ] ) + , ( 16 , [ -1024 ] ) + , ( 13 , [ -1024 ] ) + , ( 26 , [ -1024 ] ) + ] + , last_segment = [ -1023 ] + } + , polyInput = 194 + } + +unitTestData2 :: TestData +unitTestData2 = TestData + { piecewisePoly = + PiecewisePolynomial + { segments = + [ ( 1 , [ 0 ] ) + , ( 1 , [ 0 ] ) + ] + , last_segment = [ 1 ] + } + , polyInput = 2 + } + +unitTestData3 :: TestData +unitTestData3 = TestData + { piecewisePoly = + PiecewisePolynomial + { segments = + [ ( 1 , [ 0 ] ) + ] + , last_segment = [ 1 ] + } + , polyInput = 1 + } + +hprop_piecewise_polynomial_correct_unit :: Property +hprop_piecewise_polynomial_correct_unit = + property $ do + testPiecewisePolynomialUsingCost unitTestData + testPiecewisePolynomialUsingCost unitTestData2 + testPiecewisePolynomialUsingCost unitTestData3 + + + +-- Assert that calling the "Pow" entrypoint matches the implementation of (^) for natural numbers +hprop_Pow :: Property -- (MonadIO m, MonadTest m) => m () +hprop_Pow = + property $ do + x <- fromIntegral . getNonNegative @Integer <$> forAll Gen.arbitrary + n <- fromIntegral . getNonNegative @Integer <$> forAll Gen.arbitrary + + clevelandProp $ do + setup <- doFA2Setup @("addresses" :# 1) @("tokens" :# 0) + let admin ::< SNil = sAddresses setup + let bondingCurveStorage = exampleStoragePiecewiseWithAdmin admin + bondingCurve <- originateDebugBondingCurvePiecewise bondingCurveStorage + call bondingCurve (Call @"Pow") (x, n) + & expectError (WrappedValue (x ^ n)) + + +-- Assert that calling the "Pow" entrypoint matches the implementation of (^) for natural numbers +hprop_ExampleFormula0 :: Property +hprop_ExampleFormula0 = + property $ do + x <- fromIntegral . getNonNegative @Integer <$> forAll Gen.arbitrary + x' <- (+ 30000) . fromIntegral . getNonNegative @Integer <$> forAll Gen.arbitrary + + clevelandProp $ do + setup <- doFA2Setup @("addresses" :# 1) @("tokens" :# 0) + let admin ::< SNil = sAddresses setup + let bondingCurveStorage = exampleStoragePiecewiseWithAdmin admin + bondingCurve <- originateDebugBondingCurvePiecewise bondingCurveStorage + + let n = id @Natural + let exampleFormula0 :: Natural -> Mutez = \y -> + if y < 30000 + then fromIntegral $ y `div` n 3000 + else fromIntegral $ 10 * (n 1001^y `div` n 1000^y) + + call bondingCurve (Call @"ExampleFormula0") x + & expectError (WrappedValue (exampleFormula0 x)) + + call bondingCurve (Call @"ExampleFormula0") x' + & expectError (WrappedValue (exampleFormula0 x')) + + + +-- Assert that calling the "Pow" entrypoint matches the implementation of (^) for natural numbers +hprop_ExampleFormula0_lambda :: Property +hprop_ExampleFormula0_lambda = + property $ do + x <- fromIntegral . getNonNegative @Integer <$> forAll Gen.arbitrary + + exampleFormula0Lambda <- liftIO bondingCurveExampleFormula0Lambda + + clevelandProp $ do + setup <- doFA2Setup @("addresses" :# 1) @("tokens" :# 0) + let admin ::< SNil = sAddresses setup + let bondingCurveStorage = (exampleStorageWithAdmin admin) { cost_mutez = exampleFormula0Lambda } + bondingCurve <- originateDebugBondingCurve bondingCurveStorage + + let n = id @Natural + let exampleFormula0 :: Natural -> Mutez = \y -> + if y < 30000 + then fromIntegral $ y `div` n 3000 + else fromIntegral $ 10 * (n 1001^y `div` n 1000^y) + + call bondingCurve (Call @"Cost") x + & expectError (WrappedValue (exampleFormula0 x)) + + +hprop_contstantLambda :: Property +hprop_contstantLambda = + property $ do + x <- fromIntegral . getNonNegative @Integer <$> forAll Gen.arbitrary + constant' <- fromInteger . getNonNegative @Integer <$> forAll Gen.arbitrary + + clevelandProp $ do + setup <- doFA2Setup @("addresses" :# 1) @("tokens" :# 0) + let admin ::< SNil = sAddresses setup + let bondingCurveStorage = (exampleStorageWithAdmin admin) { cost_mutez = constantLambda constant' } + bondingCurve <- originateDebugBondingCurve bondingCurveStorage + + call bondingCurve (Call @"Cost") x + & expectError (WrappedValue constant') + +hprop_contstantsLambda :: Property +hprop_contstantsLambda = + property $ do + x <- fromInteger . getNonNegative @Integer <$> forAll Gen.arbitrary + constants <- fmap (fromInteger . getNonNegative @Integer) <$> forAll Gen.arbitrary + + clevelandProp $ do + setup <- doFA2Setup @("addresses" :# 1) @("tokens" :# 0) + let admin ::< SNil = sAddresses setup + let bondingCurveStorage = (exampleStorageWithAdmin admin) { cost_mutez = constantsLambda constants } + bondingCurve <- originateDebugBondingCurve bondingCurveStorage + + if x < toEnum (length constants) + then call bondingCurve (Call @"Cost") x + & expectError (WrappedValue (constants `genericIndex` x)) + + else call bondingCurve (Call @"Cost") x + & expectError (WrappedValue (unsafeMkMText "list too short for index")) + + +-- safePred n = n - 1, but never underflows +safePred :: Natural -> Natural +safePred 0 = 0 +safePred n = pred n + +testDataSmallEnoughForMutez :: MonadIO m => PropertyT m (Mutez, Natural, TestData, [(Integer, Integer)], Integer, Integer) +testDataSmallEnoughForMutez = do + testData@TestData{..} <- return $ fromValidTestData $ + ( ( [] + , NonEmpty [100, 200, 300, 400, 500, 600] + ) + , NonNegative 0) + + let numBuyers = polyInput + let auctionPrice = 10 + let basisPoints = 1 + + let expectedCosts = + [ fromIntegral auctionPrice + runPiecewisePolynomial piecewisePoly buyer + | buyer <- [0..safePred numBuyers] + ] + let expectedCostsWithBasisPoints = + [ cost + calculateBasisPointFee basisPoints cost + | cost <- expectedCosts + ] + let expectedProfit = sum + [ calculateBasisPointFee basisPoints cost + | cost <- expectedCosts + ] + let expectedTotalCostWithFees = sum expectedCostsWithBasisPoints + + if expectedTotalCostWithFees <= fromIntegral (maxBound :: Mutez) + then return + ( auctionPrice + , basisPoints + , testData + , zip expectedCosts expectedCostsWithBasisPoints + , expectedTotalCostWithFees + , expectedProfit) + else do + fail $ "too big: " <> show (auctionPrice, basisPoints, testData) + + +(@<=) :: (HasCallStack, MonadNettest caps base m, Buildable a, Ord a) => a -> a -> m () +(@<=) x y = do + assert (x <= y) $ + unlinesF + ([ "Not <= as asserted:" + , build x + , ">" + , build y + ] :: [Builder]) + + +-- TODO piecewise -> lambda +-- buy many tokens, sell all of them, ensure costs and basis_points as expected +hprop_batch_buy_sell :: Property +hprop_batch_buy_sell = + property $ do + (auctionPrice + , basisPoints + , TestData{piecewisePoly} + , expectedCostsWithBasisPoints + , expectedTotalCostWithFees + , expectedProfit) <- testDataSmallEnoughForMutez + + clevelandProp $ do + setup <- doFA2Setup @("addresses" :# 1) @("tokens" :# 0) + let admin ::< SNil = sAddresses setup + nft <- originateNft ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { ledger = [(TokenId 0, admin)] + , next_token_id = TokenId 1 + } }) + let bondingCurveStorage :: Storage PiecewisePolynomial = + (exampleStorageWithAdmin admin) + { auction_price = auctionPrice + , market_contract = toAddress nft + , cost_mutez = piecewisePoly + , basis_points = basisPoints + } + bondingCurve <- originateBondingCurvePiecewise bondingCurveStorage + + -- admin needs to set operator on (TokenId 0) to allow bondingCurve to mint + withSender admin $ + call nft (Call @"Update_operators") + [ AddOperator OperatorParam + { opOwner = admin + , opOperator = toAddress bondingCurve + , opTokenId = TokenId 0 + } + ] + + let defaultBalance :: Mutez = 900000 + getBalance admin @@== defaultBalance + + -- fill admin balance using fresh address creation + replicateM_ (fromInteger (expectedTotalCostWithFees `div` fromIntegral defaultBalance)) $ do + mutezFiller <- newAddress DefaultAliasHint + + adminBalanceBeforeFill <- getBalance admin + + withSender mutezFiller $ + getBalance mutezFiller >>= transferMoney admin + + -- ensure transferred + adminBalanceAfterFill <- getBalance admin + (adminBalanceAfterFill - adminBalanceBeforeFill) @== defaultBalance + + + adminBalance <- getBalance admin + toMutez (fromIntegral expectedTotalCostWithFees) @<= adminBalance + + -- each buyer buys 1 token + let indexedExpectedCostsWithBasisPoints = zip [1..] expectedCostsWithBasisPoints + buyersAndCosts <- forM indexedExpectedCostsWithBasisPoints $ \(tokenIndex, (expectedCost, expectedCostWithBasisPoints)) -> do + buyer <- newAddress "buyer" + + -- admin fills up buyer's wallet if 0 < cost + if 0 < expectedCostWithBasisPoints + then withSender admin $ + transferMoney buyer (fromIntegral expectedCostWithBasisPoints) + else return () + + buyerBalanceBefore <- getBalance buyer + + -- buy one token + withSender buyer $ + transfer $ + TransferData + { tdTo = bondingCurve + , tdAmount = fromIntegral expectedCostWithBasisPoints + , tdEntrypoint = ep "buy" + , tdParameter = () + } + + buyerBalanceAfter <- getBalance buyer + + -- ensure cost was expected + (buyerBalanceBefore - buyerBalanceAfter) @== fromIntegral expectedCostWithBasisPoints + + return (tokenIndex, buyer, expectedCost) + + forM_ (reverse buyersAndCosts) $ \(tokenIndex, seller, expectedCost) -> do + -- seller needs to set operator to sell + withSender seller $ + call nft (Call @"Update_operators") + [ AddOperator OperatorParam + { opOwner = seller + , opOperator = toAddress bondingCurve + , opTokenId = TokenId tokenIndex + } + ] + + sellerBalanceBefore <- getBalance seller + + -- sell one token + withSender seller $ + call bondingCurve (Call @"Sell") (TokenId tokenIndex) + + -- ensure cost was expected + sellerBalanceAfter <- getBalance seller + (sellerBalanceAfter - sellerBalanceBefore) @== fromIntegral expectedCost + + -- ensure zero tokens remaining + preBuyStorage <- getStorage' bondingCurve + preBuyStorage @== bondingCurveStorage + + if expectedProfit == 0 + then do + -- nothing to withdraw + withSender admin $ + call bondingCurve (Call @"Withdraw") () + & expectError (unsafeMkMText "UNCLAIMED=0") + else do + adminBalanceBefore <- getBalance admin + + -- ensure sum of basis_points fees can be withdrawn + withSender admin $ + call bondingCurve (Call @"Withdraw") () + + adminBalanceAfter <- getBalance admin + (adminBalanceBefore - adminBalanceAfter) @== fromIntegral expectedProfit + diff --git a/packages/minter-contracts/test-hs/Test/MinterCollection/Nft.hs b/packages/minter-contracts/test-hs/Test/MinterCollection/Nft.hs new file mode 100644 index 000000000..2a6361a87 --- /dev/null +++ b/packages/minter-contracts/test-hs/Test/MinterCollection/Nft.hs @@ -0,0 +1,581 @@ +{-# LANGUAGE InstanceSigs #-} +{-# LANGUAGE OverloadedLists #-} + +-- | Tests for NFT multi-asset contract +module Test.MinterCollection.Nft where + +import Prelude hiding (swap) + +import Test.Tasty (TestTree, testGroup) + +import Lorentz.Contracts.Spec.FA2Interface +import qualified Lorentz.Contracts.FA2 as FA2 +import Lorentz.Value () -- ToAddress + +import Michelson.Text (unsafeMkMText) +import Morley.Nettest +import Morley.Nettest.Tasty + + +import Lorentz.Contracts.MinterCollection.Nft.Contract (nftContract) +import Lorentz.Contracts.MinterCollection.Nft.Types + +import Test.SimpleAdmin +import Test.Util + + +---------------------------------------------------------------------------------------- +-- Originators +---------------------------------------------------------------------------------------- + +originateNft + :: MonadNettest caps base m + => NftStorage + -> m (ContractHandler NftEntrypoints NftStorage) +originateNft storage = + originateSimple "nft-multi-asset" storage nftContract + +---------------------------------------------------------------------------------------- +-- Test simple admin +---------------------------------------------------------------------------------------- + +-- Test SimpleAdmin admin ownership transfer +test_AdminChecks :: TestTree +test_AdminChecks = + adminOwnershipTransferChecks @NftEntrypoints @NftStorage + (\admin -> + originateNft + (exampleNftStorageWithAdmin admin) + ) + +---------------------------------------------------------------------------------------- +-- Test data +---------------------------------------------------------------------------------------- + +tokenMetadata0 :: Show a => a -> TokenMetadata +tokenMetadata0 n = mkTokenMetadata ("nft-symbol-" <> show n) ("nft-name-" <> show n) "12" + +tokenMetadata0' :: Natural -> FA2.TokenMetadata +tokenMetadata0' tokenId = FA2.TokenMetadata + { tokenId = TokenId tokenId + , tokenInfo = tokenMetadata0 $ tokenId + } + + +---------------------------------------------------------------------------------------- +-- Integrational tests +---------------------------------------------------------------------------------------- + +-- just transfer and ensure transferred +transferTest :: TestTree +transferTest = nettestScenarioCaps "Transfer" $ do + setup <- doFA2Setup @("addresses" :# 4) @("tokens" :# 0) + let admin ::< minter ::< alice ::< bob ::< SNil = sAddresses setup + nft <- originateNft ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { ledger = [(TokenId 0, minter)] } + }) + + -- transfer from minter to alice, as admin, fails + withSender admin $ + call nft (Call @"Transfer") + [ TransferItem + { tiFrom = minter + , tiTxs = [ TransferDestination + { tdTo = alice + , tdTokenId = TokenId 0 + , tdAmount = 1 + } ] + } + ] + & expectError (unsafeMkMText "FA2_NOT_OPERATOR") + + -- transfer from minter to alice + withSender minter $ + call nft (Call @"Transfer") + [ TransferItem + { tiFrom = minter + , tiTxs = [ TransferDestination + { tdTo = alice + , tdTokenId = TokenId 0 + , tdAmount = 1 + } ] + } + ] + + -- transfer from alice to bob + withSender alice $ + call nft (Call @"Transfer") + [ TransferItem + { tiFrom = alice + , tiTxs = [ TransferDestination + { tdTo = bob + , tdTokenId = TokenId 0 + , tdAmount = 1 + } ] + } + ] + + +-- just update metadata and ensure updated +updateMetadataTest :: TestTree +updateMetadataTest = nettestScenarioOnEmulatorCaps "Update metadata" $ do + setup <- doFA2Setup @("addresses" :# 3) @("tokens" :# 0) + let admin ::< minter ::< alice ::< SNil = sAddresses setup + nft <- originateNft ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { ledger = [(TokenId 0, minter)] + , next_token_id = TokenId 1 + } }) + + -- alice can't update metadata, because not admin + withSender alice $ + call nft (Call @"Update_metadata") [tokenMetadata0' 1] + & expectError (unsafeMkMText "NOT_AN_ADMIN") + + -- admin can update metadata + withSender admin $ + call nft (Call @"Update_metadata") [tokenMetadata0' 1] + + postUpdateStorage <- getStorage' nft + postUpdateStorage @== (exampleNftStorageWithAdmin admin) { + assets = exampleNftTokenStorage { + ledger = [(TokenId 0, minter)] + , next_token_id = TokenId 1 + , token_metadata = [(TokenId 1, tokenMetadata0' 1)] + } } + + + +-- just transfer using operator +operatorTest :: TestTree +operatorTest = nettestScenarioOnEmulatorCaps "Operator update and transfer" $ do + setup <- doFA2Setup @("addresses" :# 4) @("tokens" :# 0) + let admin ::< minter ::< alice ::< bob ::< SNil = sAddresses setup + nft <- originateNft ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { ledger = [(TokenId 0, minter)] } + }) + + -- admin needs to set operator on (TokenId 0) to allow alice to mint + withSender minter $ + call nft (Call @"Update_operators") + [ AddOperator OperatorParam + { opOwner = minter + , opOperator = alice + , opTokenId = TokenId 0 + } + ] + + -- transfer from minter to bob, as admin, fails + withSender admin $ + call nft (Call @"Transfer") + [ TransferItem + { tiFrom = minter + , tiTxs = [ TransferDestination + { tdTo = bob + , tdTokenId = TokenId 0 + , tdAmount = 1 + } ] + } + ] + & expectError (unsafeMkMText "FA2_NOT_OPERATOR") + + -- transfer from minter to bob, as alice (operator) + withSender alice $ + call nft (Call @"Transfer") + [ TransferItem + { tiFrom = minter + , tiTxs = [ TransferDestination + { tdTo = bob + , tdTokenId = TokenId 0 + , tdAmount = 1 + } ] + } + ] + + postTransferStorage <- getStorage' nft + postTransferStorage @== (exampleNftStorageWithAdmin admin) { + assets = exampleNftTokenStorage { + ledger = [(TokenId 0, bob)] + , next_token_id = TokenId 0 + , operators = [(FA2.OperatorKey + { owner = minter + , operator = alice + , tokenId = TokenId 0 + }, ())] + } } + + -- transfer from bob to minter + withSender bob $ + call nft (Call @"Transfer") + [ TransferItem + { tiFrom = bob + , tiTxs = [ TransferDestination + { tdTo = minter + , tdTokenId = TokenId 0 + , tdAmount = 1 + } ] + } + ] + + postTransferStorage2 <- getStorage' nft + postTransferStorage2 @== (exampleNftStorageWithAdmin admin) { + assets = exampleNftTokenStorage { + ledger = [(TokenId 0, minter)] + , next_token_id = TokenId 0 + , operators = [(FA2.OperatorKey + { owner = minter + , operator = alice + , tokenId = TokenId 0 + }, ())] + } } + + + +-- just mint (holder of token_id=0 (#2) mints) (#1 is nft admin) +-- - mint using non-minter (#3) (fails) +-- - mint using minter (#2) +-- - ensure minted to expected target and can be transferred to user #3 +mintTest :: TestTree +mintTest = nettestScenarioCaps "Mint" $ do + setup <- doFA2Setup @("addresses" :# 5) @("tokens" :# 0) + let admin ::< minter ::< alice ::< bob ::< charlie ::< SNil = sAddresses setup + + nft <- originateNft ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { ledger = [(TokenId 0, admin)] + , next_token_id = TokenId 1 + } }) + + -- admin can't mint because they're not an operator of token_id=0 + withSender admin $ + call nft (Call @"Mint") [MintTokenParam + { token_metadata = tokenMetadata0' 1 + , owner = alice + }] + & expectError (unsafeMkMText "NOT_MINTER") + + -- alice can't mint because they're not an operator of token_id=0 + withSender alice $ + call nft (Call @"Mint") [MintTokenParam + { token_metadata = tokenMetadata0' 1 + , owner = bob + }] + & expectError (unsafeMkMText "NOT_MINTER") + + -- minter needs to set operator on (TokenId 0) to allow alice to mint + withSender admin $ + call nft (Call @"Update_operators") + [ AddOperator OperatorParam + { opOwner = admin + , opOperator = minter + , opTokenId = TokenId 0 + } + ] + + -- minter can mint because they're an operator of token_id=0 + withSender minter $ + call nft (Call @"Mint") [MintTokenParam + { token_metadata = tokenMetadata0' 1 + , owner = bob + }] + + -- transfer from bob to charlie + withSender bob $ + call nft (Call @"Transfer") + [ TransferItem + { tiFrom = bob + , tiTxs = [ TransferDestination + { tdTo = charlie + , tdTokenId = TokenId 1 + , tdAmount = 1 + } ] + } + ] + + -- admin can disable minter from being an operator of token_id=0 + -- (preventing minter from minting) + withSender admin $ + call nft (Call @"Update_operators") + [ RemoveOperator OperatorParam + { opOwner = admin + , opOperator = minter + , opTokenId = TokenId 0 + } + ] + + -- minter can no longer mint + withSender minter $ + call nft (Call @"Mint") [MintTokenParam + { token_metadata = tokenMetadata0' 2 -- 3 ?? + , owner = charlie + }] + & expectError (unsafeMkMText "NOT_MINTER") + + +-- ensure multiple users can be minters +multipleMinterTest :: TestTree +multipleMinterTest = nettestScenarioCaps "Multiple minters" $ do + setup <- doFA2Setup @("addresses" :# 6) @("tokens" :# 0) + let admin ::< minter1 ::< minter2 ::< alice ::< bob ::< charlie ::< SNil = sAddresses setup + nft <- originateNft ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { ledger = [(TokenId 0, admin)] + , next_token_id = TokenId 1 + } }) + + -- minter1 can't mint because they're not an operator of token_id=0 + withSender minter1 $ + call nft (Call @"Mint") [MintTokenParam + { token_metadata = tokenMetadata0' 1 + , owner = bob + }] + & expectError (unsafeMkMText "NOT_MINTER") + + -- minter2 can't mint because they're not an operator of token_id=0 + withSender minter2 $ + call nft (Call @"Mint") [MintTokenParam + { token_metadata = tokenMetadata0' 1 + , owner = bob + }] + & expectError (unsafeMkMText "NOT_MINTER") + + -- admin needs to set operator on (TokenId 0) to allow minter1 and minter2 to mint + withSender admin $ + call nft (Call @"Update_operators") + [ AddOperator OperatorParam + { opOwner = admin + , opOperator = minter1 + , opTokenId = TokenId 0 + } + , AddOperator OperatorParam + { opOwner = admin + , opOperator = minter2 + , opTokenId = TokenId 0 + } + ] + + -- minter1 can mint because they're an operator of token_id=0 + withSender minter1 $ + call nft (Call @"Mint") [MintTokenParam + { token_metadata = tokenMetadata0' 1 + , owner = alice + }] + + -- minter2 can mint because they're an operator of token_id=0 + withSender minter2 $ + call nft (Call @"Mint") [MintTokenParam + { token_metadata = tokenMetadata0' 2 + , owner = bob + }] + + -- transfer from alice to charlie + withSender alice $ + call nft (Call @"Transfer") + [ TransferItem + { tiFrom = alice + , tiTxs = [ TransferDestination + { tdTo = charlie + , tdTokenId = TokenId 1 + , tdAmount = 1 + } ] + } + ] + + -- transfer from bob to charlie + withSender bob $ + call nft (Call @"Transfer") + [ TransferItem + { tiFrom = bob + , tiTxs = [ TransferDestination + { tdTo = charlie + , tdTokenId = TokenId 2 + , tdAmount = 1 + } ] + } + ] + + + + +-- storage updates work +-- - Mint (admin) +-- - Update_metadata (admin) +-- - Update_operators (alice -> admin) +-- - Burn (admin) +-- (emulated for easy access to storage) +mintUpdateBurnStorageTest :: TestTree +mintUpdateBurnStorageTest = nettestScenarioOnEmulatorCaps "Mint update burn: storage" $ do + setup <- doFA2Setup @("addresses" :# 4) @("tokens" :# 0) + let admin ::< minter ::< alice ::< bob ::< SNil = sAddresses setup + nft <- originateNft ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { ledger = [(TokenId 0, admin)] + , next_token_id = TokenId 1 + } }) + + -- admin can't mint because they're not an operator of token_id=0 + withSender admin $ + call nft (Call @"Mint") [MintTokenParam + { token_metadata = tokenMetadata0' 1 + , owner = bob + }] + & expectError (unsafeMkMText "NOT_MINTER") + + -- admin needs to set operator on (TokenId 0) to allow alice to mint + withSender admin $ + call nft (Call @"Update_operators") + [ AddOperator OperatorParam + { opOwner = admin + , opOperator = minter + , opTokenId = TokenId 0 + } + ] + + -- now minter can mint to alice + withSender minter $ + call nft (Call @"Mint") [MintTokenParam + { token_metadata = tokenMetadata0' 1 + , owner = alice + }] + + postMintStorage <- getStorage' nft + postMintStorage @== (exampleNftStorageWithAdmin admin) { + assets = exampleNftTokenStorage { + ledger = [(TokenId 0, admin), (TokenId 1, alice)] + , next_token_id = TokenId 2 + , operators = [(FA2.OperatorKey + { owner = admin + , operator = minter + , tokenId = TokenId 0 + }, ())] + , token_metadata = [(TokenId 1, tokenMetadata0' 1)] + } } + + -- -- bob can't update metadata, because not admin + withSender bob $ + call nft (Call @"Update_metadata") [tokenMetadata0' 0] + & expectError (unsafeMkMText "NOT_AN_ADMIN") + + -- admin (as admin) can update metadata + withSender admin $ + call nft (Call @"Update_metadata") [tokenMetadata0' 0] + + -- admin is not an operator, so can't burn + withSender admin $ + call nft (Call @"Burn") (TokenId 1, alice) + & expectError (unsafeMkMText "NOT_BURNER") + + postOperatorStorage <- getStorage' nft + postOperatorStorage @== (exampleNftStorageWithAdmin admin) { + assets = exampleNftTokenStorage { + next_token_id = TokenId 2 + , ledger = [(TokenId 0, admin), (TokenId 1, alice)] + , operators = [(FA2.OperatorKey + { owner = admin + , operator = minter + , tokenId = TokenId 0 + }, ()) + ] + , token_metadata = [(TokenId 0, tokenMetadata0' 0), (TokenId 1, tokenMetadata0' 1)] + } } + + withSender bob $ + call nft (Call @"Burn") (TokenId 1, alice) + & expectError (unsafeMkMText "NOT_BURNER") + + -- admin is not an operator of token_id=0, so can't burn + withSender admin $ + call nft (Call @"Burn") (TokenId 1, alice) + & expectError (unsafeMkMText "NOT_BURNER") + + -- minter is an operator of token_id=0, so can burn + withSender minter $ + call nft (Call @"Burn") (TokenId 1, alice) + + -- ensure token no longer in ledger + postBurnStorage <- getStorage' nft + postBurnStorage @== (exampleNftStorageWithAdmin admin) { + assets = exampleNftTokenStorage { + next_token_id = TokenId 2 + , ledger = [(TokenId 0, admin)] + , operators = [(FA2.OperatorKey + { owner = admin + , operator = minter + , tokenId = TokenId 0 + }, ()) + ] + , token_metadata = [(TokenId 0, tokenMetadata0' 0)] + } } + + +-- mint and burn work +mintUpdateBurnTest :: TestTree +mintUpdateBurnTest = nettestScenarioCaps "Mint burn" $ do + setup <- doFA2Setup @("addresses" :# 4) @("tokens" :# 0) + let admin ::< minter ::< alice ::< bob ::< SNil = sAddresses setup + nft <- originateNft ((exampleNftStorageWithAdmin admin) + { assets = exampleNftTokenStorage { + next_token_id = TokenId 1 + + , ledger = [(TokenId 0, admin)] } + }) + + -- minter needs to be an operator of token_id=0 to mint + withSender admin $ + call nft (Call @"Update_operators") [AddOperator $ OperatorParam + { opOwner = admin + , opOperator = minter + , opTokenId = TokenId 0 + }] + + -- mint to bob + withSender minter $ + call nft (Call @"Mint") [MintTokenParam + { token_metadata = tokenMetadata0' 1 + , owner = bob + }] + + -- alice is not an operator, so can't burn + withSender alice $ + call nft (Call @"Burn") (TokenId 1, bob) + & expectError (unsafeMkMText "NOT_BURNER") + + -- admin makes alice an operator of token_id=0 + withSender admin $ + call nft (Call @"Update_operators") [AddOperator $ OperatorParam + { opOwner = admin + , opOperator = alice + , opTokenId = TokenId 0 + }] + + -- bob's not an operator, so can't burn + withSender bob $ + call nft (Call @"Burn") (TokenId 1, bob) + & expectError (unsafeMkMText "NOT_BURNER") + + -- alice is now an operator of token_id=0, so can burn + withSender minter $ + call nft (Call @"Burn") (TokenId 1, bob) + + -- the token can no longer be transferred and fails with an error + -- demonstrating it doesn't exist + withSender bob $ + call nft (Call @"Transfer") + [ TransferItem + { tiFrom = bob + , tiTxs = [ TransferDestination + { tdTo = alice + , tdTokenId = TokenId 1 + , tdAmount = 1 + } ] + } + ] + & expectError (unsafeMkMText "FA2_TOKEN_UNDEFINED") + + +test_Integrational :: TestTree +test_Integrational = testGroup "Integrational" + [ transferTest + , updateMetadataTest + , operatorTest + , mintTest + , mintUpdateBurnStorageTest + , mintUpdateBurnTest + ] + diff --git a/packages/minter-contracts/test-hs/Test/Util.hs b/packages/minter-contracts/test-hs/Test/Util.hs index 2c0aa12d0..6724ab0a5 100644 --- a/packages/minter-contracts/test-hs/Test/Util.hs +++ b/packages/minter-contracts/test-hs/Test/Util.hs @@ -1,6 +1,8 @@ {-# OPTIONS_GHC -Wno-redundant-constraints #-} {-# OPTIONS_GHC -Wno-orphans #-} +{-# LANGUAGE InstanceSigs #-} + module Test.Util ( (-:) , type (:#) @@ -15,6 +17,7 @@ module Test.Util , balanceOf , mkAllowlistSimpleParam , originateWithAdmin + , WrappedValue(..) -- * Property-based tests , clevelandProp @@ -24,7 +27,6 @@ module Test.Util , Sized ) where - import qualified Data.Foldable as F import qualified Data.Map as Map import Data.Maybe @@ -37,8 +39,11 @@ import GHC.TypeLits (Symbol) import GHC.TypeNats (Nat, type (+)) import Hedgehog (Gen, MonadTest) +import Lorentz.Errors import Lorentz.Test.Consumer import Lorentz.Value +import Michelson.Typed.Scope (ConstantScope) +import Michelson.Typed.Sing (KnownT) import qualified Indigo.Contracts.FA2Sample as FA2 import Lorentz.Contracts.FA2 @@ -315,3 +320,26 @@ iterateM 0 _ _ = pure [] iterateM len gen previous = do current <- gen previous (current :) <$> iterateM (len - 1) gen current + + +-- | Wrap an IsoValue type so that is can be used with expectError +newtype WrappedValue a = WrappedValue + { unwrapValue :: a + } deriving stock (Eq, Ord, Show) + +-- | Note: these are undefined because they're not needed to use WrappedValue to test +instance Typeable a => ErrorHasDoc (WrappedValue a) where + type ErrorRequirements _ = () + + errorDocName = error "ErrorHasDoc (WrappedValue a): undefined errorDocName" + errorDocMdCause = error "ErrorHasDoc (WrappedValue a): undefined errorDocMdCause" + errorDocHaskellRep = error "ErrorHasDoc (WrappedValue a): undefined errorDocHaskellRep" + errorDocDependencies = error "ErrorHasDoc (WrappedValue a): undefined errorDocDependencies" + +instance (IsoValue a, Typeable a, ConstantScope (ToT a)) => IsError (WrappedValue a) where + errorToVal :: WrappedValue a -> (forall t. ErrorScope t => Value t -> r) -> r + errorToVal xs ys = isoErrorToVal (unwrapValue xs) ys + + errorFromVal :: forall t. (KnownT t) => Value t -> Either Text (WrappedValue a) + errorFromVal = fmap WrappedValue . isoErrorFromVal @t @a + diff --git a/packages/minter-contracts/test/bonding-curve.test.ts b/packages/minter-contracts/test/bonding-curve.test.ts new file mode 100644 index 000000000..bb05ba7b9 --- /dev/null +++ b/packages/minter-contracts/test/bonding-curve.test.ts @@ -0,0 +1,503 @@ +import { $log } from '@tsed/logger'; +import { + MichelsonMap, +} from '@taquito/taquito'; +import { BigNumber } from 'bignumber.js'; + +import { bootstrapWithoutLambdaView, TestTz } from './bootstrap-sandbox'; +import { Contract, bytes, address, nat } from '../src/type-aliases'; + +import { originateBondingCurve } from '../src/bonding-curve'; +import { char2Bytes } from '@taquito/tzip16'; +// import { originateNft } from '../src/nft-contracts'; +import { + addOperator, + // transfer, +} from '../src/fa2-interface'; +// import { QueryBalances, queryBalancesWithLambdaView, hasTokens } from './fa2-balance-inspector'; + +// because originateNft doesn't allow raw storage +// could fix by making originateNftRawStorage +import { originateContract } from '../src/ligo'; +import { Fa2MultiNftAssetCode } from '../bin-ts'; + + +jest.setTimeout(180000); // 3 minutes + + +export interface MintEditionParam { + edition_info: MichelsonMap; + number_of_editions: nat; +} + +export interface distribute_edition { + edition_id: nat; + receivers: address[]; +} + +// TODO +describe('bonding-curve: test NFT auction', () => { + // let maxEditions: nat; + // let nftEditionsBob: Contract; + let tezos: TestTz; + + let bondingCurve: Contract; + let nft: Contract; + + // let nftEditionsAlice: Contract; + // let nft1: MintEditionParam; + // let nft2: MintEditionParam; + let edition_1_metadata: MichelsonMap; + let edition_2_metadata: MichelsonMap; + let adminAddress: address; + let aliceAddress: address; + let bobAddress: address; + let charlieAddress: address; + // let queryBalances: QueryBalances; + + beforeAll(async () => { + // skip lambda view contract for now for speed + // tezos = await bootstrap(); + tezos = await bootstrapWithoutLambdaView(); + edition_1_metadata = new MichelsonMap(); + edition_1_metadata.setType({ prim: "map", args: [{ prim: "string" }, { prim: "bytes" }] }); + edition_1_metadata.set("name", "66616b65206e616d65"); + edition_2_metadata = new MichelsonMap(); + edition_2_metadata.setType({ prim: "map", args: [{ prim: "string" }, { prim: "bytes" }] }); + edition_2_metadata.set("name", "74657374206e616d65"); + + // eve is admin + adminAddress = await tezos.eve.signer.publicKeyHash(); + aliceAddress = await tezos.alice.signer.publicKeyHash(); + bobAddress = await tezos.bob.signer.publicKeyHash(); + charlieAddress = await tezos.charlie.signer.publicKeyHash(); + + // queryBalances = queryBalancesWithLambdaView(tezos.lambdaView); + // + // $log.info('originating editions contract'); + // nftEditionsBob = await originateEditionsNftContract(tezos.bob, bobAddress); + + // const bondingCurveStorage: BondingCurveContractType["storage"] = + // { + // admin: { + // admin: bobAddress as bin_address, + // paused: false, + // pending_admin: undefined, + // }, + + // // market_contract: nftEditionsBob.address as bin_address, + // market_contract: bobAddress as bin_address, + + // auction_price: new BigNumber(0) as bin_mutez, + // token_index: new BigNumber(0) as bin_nat, + // token_metadata: { + + // }, + + // // 1% + // basis_points: new BigNumber(100) as bin_nat, + + // // Linear example: + // // Cost(x) = x = 0 * x^0 + 1 * x^1 + // cost_mutez: { + // segments: [], + // last_segment: [new BigNumber(0) as bin_int, new BigNumber(1) as bin_int], + // }, + // unclaimed: new BigNumber(0) as bin_mutez, + // }; + + + // exampleTokenMetadata :: TokenMetadata + // exampleTokenMetadata = mkTokenMetadata symbol name decimals + // where + // symbol = "test_symbol" + // name = "This is a test! [name]" + // decimals = "12" + + // exampleStorage' = Storage + // { admin = exampleAdminStorage + // , market_contract = detGenKeyAddress "dummy-impossible-contract-key" + // , auction_price = toMutez 0 + // , token_index = 2 + // , token_metadata = exampleTokenMetadata + // , basis_points = 100 + // , cost_mutez = examplePiecewisePolynomial' + // , unclaimed = toMutez 3 + // } + + // ("admin","Pair (Pair \"tz2C97sask3WgSSg27bJhFxuBwW6MMU4uTPK\" False) None") + // ("market_contract","\"tz2UXHa5WU79MnWF5uKFRM6qUowX13pdgJGy\"") + // storage for distinguishing fields: + + + // const bondingCurveStorageString2 = ` + // { Pair (Pair "${adminAddress}" False) None; "${market_contractAddress}"; ${auction_price}; ${token_index}; + // { + // Elt "decimals" 0x3132; + // Elt "name" 0x546869732069732061207465737421205b6e616d655d; + // Elt "symbol" 0x746573745f73796d626f6c }; + // ${basis_points}; Pair { Pair 6 { 7; 8 } } { 4; 5 }; 0 }`; + + // expect(bondingCurveStorageString).toBe(bondingCurveStorageString2); + + // const bondingCurveStorageString = "{ Pair (Pair \"tz2C97sask3WgSSg27bJhFxuBwW6MMU4uTPK\" False) None; + // \"tz2UXHa5WU79MnWF5uKFRM6qUowX13pdgJGy\"; 0; 0; { Elt \"decimals\" 0x3132; + // Elt \"name\" 0x546869732069732061207465737421205b6e616d655d; + // Elt \"symbol\" 0x746573745f73796d626f6c }; 100; Pair { Pair 6 { 7; 8 } } { 4; 5 }; 0 }"; + + // before storage update + // const bondingCurveStorageString = ` + // { Pair (Pair "${adminAddress}" False) None; "${market_contractAddress}"; ${auction_price}; ${token_index}; + // Pair 42 { + // Elt "decimals" 0x3132; + // Elt "name" 0x546869732069732061207465737421205b6e616d655d; + // Elt "symbol" 0x746573745f73796d626f6c }; + // ${basis_points}; Pair { Pair 6 { 7; 8 } } { 4; 5 }; 0 }`; + + + // nftEditionsAlice = await tezos.alice.contract.at(nftEditionsBob.address); + // $log.info(`editions contract originated`); + // const contractStorage : any = await nftEditionsBob.storage(); + // maxEditions = await contractStorage.max_editions_per_run; + }); + + // test('Minimal test to originate', async () => { + // $log.info("Minimal test to originate"); + + // const adminAddress = aliceAddress; + // const market_contractAddress = aliceAddress; + // const auction_price = 0; + // const token_index = 0; + // const basis_points = 100; + + // const token_name = "test_symbol"; + // const token_symbol = "This is a test! [name]"; + // const token_decimals = "12"; + + // // examplePiecewisePolynomial' = PiecewisePolynomial + // // { segments = [(6, [7, 8])] + // // , last_segment = [4, 5] + // // } + // const segments = '{ Pair 6 { 7; 8 } }'; + // const last_segment = '{ 4; 5 }'; + // const unclaimed_mutez = 0; + + // const bondingCurveStorageString = ` + // { Pair (Pair "${adminAddress}" False) None; "${market_contractAddress}"; ${auction_price}; ${token_index}; + // { + // Elt "decimals" 0x${char2Bytes(token_decimals)}; + // Elt "name" 0x${char2Bytes(token_name)}; + // Elt "symbol" 0x${char2Bytes(token_symbol)} }; + // ${basis_points}; Pair ${segments} ${last_segment}; ${unclaimed_mutez} + // }`; + + // $log.info(`originating bonding curve contract with storage:\n${bondingCurveStorageString}`); + // // bondingCurve = await originateBondingCurve(tezos.bob, bondingCurveStorage as Record); + // bondingCurve = await originateBondingCurve(tezos.bob, bondingCurveStorageString); + // $log.info(`bonding curve contract originated: ${bondingCurve}`); + + // expect('ok').toBe('ok'); + // }); + + + test('Buy sell test', async () => { + + // (admin, alice, bob, charlie) + // ("admin_address","alice_address","bob_address","charlie_address") + + const admin_address = adminAddress; + const admin_toolkit = tezos.eve; + + // nft storage + // const nft_storage = + // `Pair { Pair (Pair "${admin_address}" False) None; Pair { Elt 0 "${admin_address}" } 1; { }; { } } { }`; + + const meta_uri = char2Bytes('tezos-storage:content'); + const sample_metadata = { + name: 'example_name', + description: 'sample_token', + interfaces: ['TZIP-012', 'TZIP-016'], + }; + const meta_content = char2Bytes(JSON.stringify(sample_metadata, null, 2)); + + const nft_storage = `(Pair (Pair (Pair (Pair "${admin_address}" False) None) (Pair (Pair { Elt 0 "${admin_address}" } 1) (Pair { } { }))) { Elt "" 0x${meta_uri} ; Elt "content" 0x${meta_content} })`; + + + $log.info(`originating nft contract with storage:\n${nft_storage}`); + const nft_contract = await originateContract(tezos.bob, Fa2MultiNftAssetCode.code, nft_storage, 'nft'); + const nft_address = nft_contract.address; + + const bonding_curve_storage = + `{ Pair (Pair "${admin_address}" False) None; "${nft_address}"; 100; 0; { Elt "decimals" 0x3132; Elt "name" 0x546869732069732061207465737421205b6e616d655d; Elt "symbol" 0x746573745f73796d626f6c }; 100; Pair { } { 10; 20; 30 }; 0 }`; + + $log.info(`originating bonding curve contract with storage:\n${bonding_curve_storage}`); + const bonding_curve_contract = await originateBondingCurve(tezos.bob, bonding_curve_storage); + const bonding_curve_address = bonding_curve_contract.address; + + $log.info("admin -> nft: update_operators (bonding curve -> token_id=0)"); + const op_update_operators = await addOperator(nft_address, admin_toolkit, bonding_curve_address, new BigNumber(0)); + + const bonding_curve_alice = await tezos.alice.contract.at(bonding_curve_contract.address); + const bonding_curve_bob = await tezos.bob.contract.at(bonding_curve_contract.address); + const bonding_curve_charlie = await tezos.charlie.contract.at(bonding_curve_contract.address); + + // alice -> bondingCurve: buy + $log.info(`alice -> bondingCurve: buy`); + const alice_buy_op = await bonding_curve_alice.methods.buy().send({ amount: 111, mutez: true }); + await alice_buy_op.confirmation(); + + try { + + // bob -> bondingCurve: buy + $log.info(`bob -> bondingCurve: buy`); + const bob_buy_op = await bonding_curve_bob.methods.buy().send({ amount: 161, mutez: true }); + await bob_buy_op.confirmation(); + + } catch (ex:any) { + $log.info(`ex str: ${JSON.stringify(ex, null, 2)}`); + $log.info(`message: ${ex.message}`); + + expect(ex.message).toMatch('test'); + } + + // charlie -> bondingCurve: buy + $log.info(`charlie -> bondingCurve: buy`); + const charlie_buy_op = await bonding_curve_charlie.methods.buy().send({ amount: 272, mutez: true }); + await charlie_buy_op.confirmation(); + + + // $log.info(`charlie -> bondingCurve: sell(3)`); + // charlie -> bondingCurve: sell + // parameter: 3 + + // $log.info(`bob -> bondingCurve: sell(2)`); + // bob -> bondingCurve: sell + // parameter: 2 + + // $log.info(`alice -> bondingCurve: sell(1)`); + // alice -> bondingCurve: sell + // parameter: 1 + + // admin -> bondingCurve: withdraw + // parameter: Unit + + expect('ok').toBe('ok'); + }); + + + // test('change admin by non admin should fail', async () => { + // const opSetAdmin = nftEditionsAlice.methods.set_admin(aliceAddress).send(); + // return expect(opSetAdmin).rejects.toHaveProperty('message', 'NOT_AN_ADMIN'); + // }); + + // test('pause by non admin should fail', async () => { + // const opPause = nftEditionsAlice.methods.pause([true]).send(); + // return expect(opPause).rejects.toHaveProperty('message', 'NOT_AN_ADMIN'); + // }); + + // test('change admin by admin should succeed', async () => { + // $log.info("Testing change admin"); + // const opSetAdmin = await nftEditionsBob.methods.set_admin(aliceAddress).send(); + // await opSetAdmin.confirmation(); + // const opConfirmAdmin = await nftEditionsAlice.methods.confirm_admin(["unit"]).send(); + // await opConfirmAdmin.confirmation(); + // const contractStorage1 : any = await nftEditionsBob.storage(); + // const admin = await contractStorage1.nft_asset_storage.admin.admin; + // expect(admin).toEqual(aliceAddress); + // $log.info("Admin changed successfully"); + + // $log.info("Set admin back"); + // const opSetAdminBack = await nftEditionsAlice.methods.set_admin(bobAddress).send(); + // await opSetAdminBack.confirmation(); + // const opConfirmAdminBob = await nftEditionsBob.methods.confirm_admin(["unit"]).send(); + // await opConfirmAdminBob.confirmation(); + // const contractStorage2 : any = await nftEditionsBob.storage(); + // const finalAdmin = await contractStorage2.nft_asset_storage.admin.admin; + // expect(finalAdmin).toEqual(bobAddress); + // $log.info("Admin changed back successfully"); + // }); + + // // test('minting by non admin should fail', async () => { + // // const nft = { + // // edition_info: edition_1_metadata, + // // number_of_editions: new BigNumber(1000), + // // }; + // // const opMint = nftEditionsAlice.methods.mint_editions([nft]).send(); + // // return expect(opMint).rejects.toHaveProperty('message', 'NOT_AN_ADMIN'); + // // }); + + // // test('minting too large of an edition set should fail', async () => { + // // const nft = { + // // edition_info: edition_1_metadata, + // // number_of_editions: maxEditions.plus(1), + // // }; + // // const opMint = nftEditionsBob.methods.mint_editions([nft]).send(); + // // return expect(opMint).rejects.toHaveProperty('message', 'EDITION_RUN_TOO_LARGE'); + // // }); + + // // NOTE: needs to be run synchronously, tests that follow depend on these editions having been minted + // test('mint 1000 editions of nft1 and 2 of nft2', async () => { + // nft1 = { + // edition_info: edition_1_metadata, + // number_of_editions: new BigNumber(1000), + // }; + + // nft2 = { + // edition_info: edition_2_metadata, + // number_of_editions: new BigNumber(2), + // }; + // const opMint = await nftEditionsBob.methods.mint_editions([nft1, nft2]).send(); + // await opMint.confirmation(); + // $log.info(`Minted editions. Consumed gas: ${opMint.consumedGas}`); + // }); + + // test('distribute editions', async () => { + // const distributeEdition0: distribute_edition = { + // edition_id: new BigNumber(0), + // receivers: [aliceAddress, bobAddress], + // }; + // const distributeEdition1: distribute_edition = { + // edition_id: new BigNumber(1), + // receivers: [aliceAddress, bobAddress], + // }; + // const opDistribute = await nftEditionsBob.methods + // .distribute_editions([distributeEdition0, distributeEdition1]).send(); + // await opDistribute.confirmation(); + // $log.info(`Distributed editions. Consumed gas: ${opDistribute.consumedGas}`); + + // const [aliceHasEdition0, bobHasEdition0] = await hasTokens([ + // { owner: aliceAddress, token_id: new BigNumber(0) }, + // { owner: bobAddress, token_id: new BigNumber(1) }, + // ], queryBalances, nftEditionsBob); + + // const [aliceHasEdition1, bobHasEdition1] = await hasTokens([ + // { owner: aliceAddress, token_id: maxEditions }, + // { owner: bobAddress, token_id: maxEditions.plus(1) }, + // ], queryBalances, nftEditionsBob); + + // expect(aliceHasEdition0).toBe(true); + // expect(bobHasEdition0).toBe(true); + // expect(aliceHasEdition1).toBe(true); + // expect(bobHasEdition1).toBe(true); + // }); + + + // test('distributing too many editions should fail', async () => { + // const distributeEdition1: distribute_edition = { + // edition_id: new BigNumber(1), + // receivers: [aliceAddress], + // }; + // const opDistribute = nftEditionsBob.methods.distribute_editions([distributeEdition1]).send(); + // return expect(opDistribute).rejects.toHaveProperty('message', 'NO_EDITIONS_TO_DISTRIBUTE'); + // }); + // test('distributing from a 0 edition set should fail', async () => { + // const nft3 = { + // edition_info: edition_1_metadata, + // number_of_editions: new BigNumber(0), + // }; + // const opMint = await nftEditionsBob.methods.mint_editions([nft3]).send(); + // await opMint.confirmation(); + // $log.info(`Minted editions. Consumed gas: ${opMint.consumedGas}`); + // const distributeEdition3: distribute_edition = { + // edition_id: new BigNumber(2), + // receivers: [aliceAddress], + // }; + // const opDistribute = nftEditionsBob.methods.distribute_editions([distributeEdition3]).send(); + // return expect(opDistribute).rejects.toHaveProperty('message', 'NO_EDITIONS_TO_DISTRIBUTE'); + // }); + + // test('distributing exactly as many editions available should succeed with 0 editions left to distribute', + // async () => { + // const nft4 = { + // edition_info: edition_1_metadata, + // number_of_editions: new BigNumber(3), + // }; + // const opMint = await nftEditionsBob.methods.mint_editions([nft4]).send(); + // await opMint.confirmation(); + // $log.info(`Minted editions. Consumed gas: ${opMint.consumedGas}`); + // const distributeEdition3: distribute_edition = { + // edition_id: new BigNumber(3), + // receivers: [aliceAddress, aliceAddress, aliceAddress], + // }; + // const opDistribute = await nftEditionsBob.methods.distribute_editions([distributeEdition3]).send(); + // await opDistribute.confirmation(); + // const editions_storage : any = await nftEditionsBob.storage(); + // const editions_metadata = await editions_storage.editions_metadata.get("3"); + // expect(JSON.stringify(editions_metadata.number_of_editions_to_distribute, null, 2)).toEqual("\"0\""); + // }); + + // test('transfer edition', async () => { + // const tokenId = new BigNumber(0); + // const nat1 = new BigNumber(1); + // await transfer(nftEditionsBob.address, tezos.alice, [ + // { + // from_: aliceAddress, + // txs: [{ to_: bobAddress, token_id: tokenId, amount: nat1 }], + // }, + // ]); + // const [aliceHasATokenAfter, bobHasATokenAfter] = await hasTokens([ + // { owner: aliceAddress, token_id: tokenId }, + // { owner: bobAddress, token_id: tokenId }, + // ], queryBalances, nftEditionsBob); + // expect(aliceHasATokenAfter).toBe(false); + // expect(bobHasATokenAfter).toBe(true); + // }); + + // test('transfer edition that does not exist should fail', async () => { + // const tokenId = new BigNumber(1000); //this token should not exist + // const nat1 = new BigNumber(1); + // const opTransfer = transfer(nftEditionsBob.address, tezos.alice, [ + // { + // from_: aliceAddress, + // txs: [{ to_: bobAddress, token_id: tokenId, amount: nat1 }], + // }, + // ]); + // return expect(opTransfer).rejects.toHaveProperty('message', 'FA2_TOKEN_UNDEFINED'); + // }); + + // test('test editions token-metadata with off-chain view', async () => { + // tezos.bob.addExtension(new Tzip16Module()); + // const editionsContractMetadata = await tezos.bob.contract.at(nftEditionsBob.address, tzip16); + // $log.info(`Initialising the views for editions contract ...`); + // const views = await editionsContractMetadata.tzip16().metadataViews(); + // $log.info(`The following view names were found in the metadata: ${Object.keys(views)}`); + // $log.info(`get metadata for edition with token_id 0 ...`); + // const token0Metadata = await views.token_metadata().executeView(0); + // expect(token0Metadata).toEqual({ + // token_id: new BigNumber(0), + // token_info: edition_1_metadata, + // }); + // }); + + // test('Distributing from an edition set you did not create should fail', async () => { + // const distributeEdition0: distribute_edition = { + // edition_id: new BigNumber(0), + // receivers: [aliceAddress], + // }; + // const opDistribute = nftEditionsAlice.methods.distribute_editions([distributeEdition0]).send(); + // return expect(opDistribute).rejects.toHaveProperty('message', 'INVALID_DISTRIBUTOR'); + // }); + + // test('Distributing from a non existing edition set should fail', async () => { + // const distributeEdition5: distribute_edition = { + // edition_id: new BigNumber(5), + // receivers: [aliceAddress], + // }; + // const opDistribute = nftEditionsBob.methods.distribute_editions([distributeEdition5]).send(); + // return expect(opDistribute).rejects.toHaveProperty('message', 'INVALID_EDITION_ID'); + // }); + + // test('Distributing while contract is paused should fail', async () => { + // $log.info("pausing the contract"); + // const opPause = await nftEditionsBob.methods.pause([true]).send(); + // await opPause.confirmation(); + // $log.info("contract paused"); + // const distributeEdition0: distribute_edition = { + // edition_id: new BigNumber(0), + // receivers: [aliceAddress], + // }; + // const opDistribute = nftEditionsBob.methods.distribute_editions([distributeEdition0]).send(); + // return expect(opDistribute).rejects.toHaveProperty('message', 'PAUSED'); + // }); + +}); diff --git a/packages/minter-contracts/test/bootstrap-sandbox.ts b/packages/minter-contracts/test/bootstrap-sandbox.ts index ea94a5ff4..6fb0e1bac 100644 --- a/packages/minter-contracts/test/bootstrap-sandbox.ts +++ b/packages/minter-contracts/test/bootstrap-sandbox.ts @@ -8,6 +8,7 @@ type TestKeys = { bob: Signer; alice: Signer; eve: Signer; + charlie: Signer; lambdaView?: string; }; @@ -21,13 +22,18 @@ async function flextesaKeys(): Promise { const eve = await InMemorySigner.fromSecretKey( 'edsk3Sb16jcx9KrgMDsbZDmKnuN11v4AbTtPBgBSBTqYftd8Cq3i1e', ); - return { bob, alice, eve }; + const charlie = await InMemorySigner.fromSecretKey( + 'edsk3nM41ygNfSxVU4w1uAW3G9EnTQEB5rjojeZedLTGmiGRcierVv', + ); + + return { bob, alice, eve, charlie }; } export type TestTz = { bob: TezosToolkit; alice: TezosToolkit; eve: TezosToolkit; + charlie: TezosToolkit; lambdaView?: string; }; @@ -59,12 +65,30 @@ export async function awaitForNetwork(tz: TezosToolkit): Promise { $log.info('connected to Tezos network'); } +export async function bootstrapWithoutLambdaView(): Promise { + const { bob, alice, eve, charlie } = await flextesaKeys(); + const rpc = 'http://localhost:20000'; + const bobToolkit = signerToToolkit(bob, rpc); + const aliceToolkit = signerToToolkit(alice, rpc); + const eveToolkit = signerToToolkit(eve, rpc); + const charlieToolkit = signerToToolkit(charlie, rpc); + + await awaitForNetwork(bobToolkit); + return { + bob: bobToolkit, + alice: aliceToolkit, + eve: eveToolkit, + charlie: charlieToolkit, + }; +} + export async function bootstrap(): Promise { - const { bob, alice, eve } = await flextesaKeys(); + const { bob, alice, eve, charlie } = await flextesaKeys(); const rpc = 'http://localhost:20000'; const bobToolkit = signerToToolkit(bob, rpc); const aliceToolkit = signerToToolkit(alice, rpc); const eveToolkit = signerToToolkit(eve, rpc); + const charlieToolkit = signerToToolkit(charlie, rpc); await awaitForNetwork(bobToolkit); @@ -79,6 +103,8 @@ export async function bootstrap(): Promise { bob: bobToolkit, alice: aliceToolkit, eve: eveToolkit, + charlie: charlieToolkit, lambdaView: lambdaContract.address, }; } +