From 31ceecb832079025818ef9ba194a9fd4fcb9d3fa Mon Sep 17 00:00:00 2001 From: Eli Guenzburger Date: Mon, 15 Mar 2021 17:49:18 -0400 Subject: [PATCH] Initial royalty nft commit --- contracts/bin/nft_royalty_faucet.tz | 499 ++++++++++++++++++ contracts/ligo/fa2/fa2_errors.mligo | 2 +- contracts/ligo/fa2/fa2_interface.mligo | 1 + contracts/ligo/fa2/lib/fa2_operator_lib.mligo | 2 + contracts/ligo/math.mligo | 7 + .../english_auction/english_auction_tez.mligo | 9 +- .../fa2_multi_nft_faucet_with_royalty.mligo | 21 + .../fa2_multi_nft_token_with_royalty.mligo | 68 +++ contracts/src/compile-contracts.ts | 12 + 9 files changed, 612 insertions(+), 9 deletions(-) create mode 100644 contracts/bin/nft_royalty_faucet.tz create mode 100644 contracts/ligo/math.mligo create mode 100644 contracts/ligo/src/minter_collection/fa2_multi_nft_faucet_with_royalty.mligo create mode 100644 contracts/ligo/src/minter_collection/fa2_multi_nft_token_with_royalty.mligo diff --git a/contracts/bin/nft_royalty_faucet.tz b/contracts/bin/nft_royalty_faucet.tz new file mode 100644 index 000000000..bd41fcd3d --- /dev/null +++ b/contracts/bin/nft_royalty_faucet.tz @@ -0,0 +1,499 @@ +{ parameter + (or (or %assets + (or %fA2 + (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)))))) + (pair %get_Royalty + (nat %token_id) + (pair (mutez %fee) (contract %cb (pair address mutez))))) + (list %mint + (pair (pair %token_metadata (nat %token_id) (map %token_info string bytes)) + (address %owner)))) ; + storage + (pair (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)))))) + { DUP ; + CDR ; + SWAP ; + CAR ; + DUP ; + CDR ; + SWAP ; + CAR ; + DIG 2 ; + UNPAIR ; + UNPAIR ; + DIG 2 ; + DUP ; + DUG 3 ; + CAR ; + CAR ; + DIG 3 ; + DUP ; + DUG 4 ; + CDR ; + CAR ; + PAIR ; + DUG 2 ; + DUP ; + DUG 3 ; + DIG 2 ; + UNPAIR ; + SWAP ; + DIG 2 ; + ITER { DUP ; + DUG 2 ; + CDR ; + ITER { SWAP ; + DIG 2 ; + DUP ; + DUG 3 ; + CAR ; + IF_NONE + { UNIT } + { DIG 4 ; + DUP ; + DUG 5 ; + DIG 3 ; + DUP ; + DUG 4 ; + CDR ; + CAR ; + PAIR ; + SENDER ; + DIG 2 ; + PAIR ; + PAIR ; + DIG 5 ; + DUP ; + DUG 6 ; + SWAP ; + EXEC } ; + DROP ; + PUSH nat 1 ; + DIG 2 ; + DUP ; + DUG 3 ; + CDR ; + CDR ; + COMPARE ; + GT ; + IF { DROP 2 ; DIG 5 ; DUP ; DUG 6 ; FAILWITH } + { PUSH nat 0 ; + DIG 2 ; + DUP ; + DUG 3 ; + CDR ; + CDR ; + COMPARE ; + EQ ; + IF { DUP ; + DIG 2 ; + CDR ; + CAR ; + GET ; + IF_NONE { DROP ; DIG 6 ; DUP ; DUG 7 ; FAILWITH } { DROP } } + { SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + DIG 3 ; + DUP ; + DUG 4 ; + CAR ; + IF_NONE + { DROP } + { DIG 2 ; + DUP ; + DUG 3 ; + DIG 2 ; + DUP ; + DUG 3 ; + GET ; + IF_NONE + { DROP 3 ; DIG 7 ; DUP ; DUG 8 ; FAILWITH } + { COMPARE ; + EQ ; + IF { NONE address ; SWAP ; UPDATE } + { DROP 2 ; DIG 6 ; DUP ; DUG 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 ; + DIG 2 ; + DUP ; + DUG 3 ; + CDR ; + DIG 3 ; + DUP ; + DUG 4 ; + CAR ; + CDR ; + DIG 2 ; + PAIR ; + PAIR ; + DUG 2 ; + DROP 2 ; + NIL operation ; + PAIR } ; + SWAP ; + APPLY ; + PUSH string "FA2_ROYALTY_UNDEFINED" ; + DIG 3 ; + UNPAIR ; + IF_LEFT + { SWAP ; + DUP ; + DUG 2 ; + CAR ; + SWAP ; + IF_LEFT + { DIG 3 ; + DROP ; + IF_LEFT + { IF_LEFT + { DIG 3 ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + CAR ; + SWAP ; + DUP ; + CAR ; + MAP { DIG 2 ; + DUP ; + DUG 3 ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + GET ; + IF_NONE + { DROP ; DIG 4 ; DUP ; DUG 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 } ; + PUSH unit Unit ; + RIGHT unit ; + RIGHT unit ; + RIGHT unit ; + IF_LEFT + { DROP ; PUSH string "FA2_TX_DENIED" ; FAILWITH } + { IF_LEFT + { DROP ; PUSH bool False ; PUSH bool True ; PAIR } + { IF_LEFT + { DROP ; PUSH bool True ; PUSH bool True ; PAIR } + { DROP ; PUSH bool True ; PUSH bool False ; PAIR } } } ; + LAMBDA + (pair (pair bool bool) + (pair (pair address address) (pair nat (big_map (pair address (pair address nat)) unit)))) + unit + { DUP ; + CDR ; + SWAP ; + CAR ; + DUP ; + CDR ; + SWAP ; + CAR ; + DIG 2 ; + UNPAIR ; + UNPAIR ; + DIG 2 ; + UNPAIR ; + DIG 3 ; + DUP ; + DUG 4 ; + DIG 3 ; + DUP ; + DUG 4 ; + COMPARE ; + EQ ; + DIG 5 ; + AND ; + IF { DROP 5 ; UNIT } + { DIG 4 ; + NOT ; + IF { DROP 4 ; PUSH string "FA2_NOT_OWNER" ; FAILWITH } + { DIG 3 ; + PAIR ; + DIG 2 ; + PAIR ; + MEM ; + IF { UNIT } { PUSH string "FA2_NOT_OPERATOR" ; FAILWITH } } } } ; + SWAP ; + APPLY ; + DIG 2 ; + SWAP ; + DIG 2 ; + PAIR ; + PAIR ; + DIG 2 ; + SWAP ; + EXEC } } + { DROP 2 ; + SWAP ; + DROP ; + SWAP ; + DROP ; + PUSH string "FA2_OPERATORS_UNSUPPORTED" ; + FAILWITH } } + { DIG 4 ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CDR ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + GET ; + IF_NONE + { DIG 3 ; DROP ; DIG 3 ; FAILWITH } + { DIG 5 ; + DROP ; + CDR ; + PUSH string "royalty" ; + GET ; + IF_NONE + { DIG 3 ; FAILWITH } + { UNPACK (pair (address %creator) (nat %fee_percent)) ; + IF_NONE { DIG 3 ; FAILWITH } { DIG 4 ; DROP } } } ; + PUSH nat 100 ; + DIG 2 ; + DUP ; + DUG 3 ; + CDR ; + CAR ; + DIG 2 ; + DUP ; + DUG 3 ; + CDR ; + MUL ; + EDIV ; + IF_NONE + { PUSH string "DIVISION_BY_ZERO" ; FAILWITH } + { UNPAIR ; + PUSH mutez 0 ; + DIG 2 ; + COMPARE ; + GT ; + IF { PUSH mutez 1 ; ADD } {} } ; + DIG 2 ; + CDR ; + CDR ; + PUSH mutez 0 ; + DIG 2 ; + DIG 3 ; + CAR ; + PAIR ; + TRANSFER_TOKENS ; + SWAP ; + NIL operation ; + DIG 2 ; + CONS ; + PAIR } ; + UNPAIR ; + DIG 2 ; + CDR ; + DIG 2 ; + PAIR ; + SWAP ; + PAIR } + { DIG 2 ; + DROP ; + DIG 3 ; + DROP ; + SWAP ; + DUP ; + DUG 2 ; + CAR ; + NIL (pair (option address) (pair nat nat)) ; + PAIR ; + SWAP ; + ITER { DUP ; + DUG 2 ; + CAR ; + CAR ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + CDR ; + SWAP ; + DUP ; + DUG 2 ; + COMPARE ; + LT ; + IF { DROP 3 ; PUSH string "FA2_INVALID_TOKEN_ID" ; FAILWITH } + { SWAP ; + DUP ; + DUG 2 ; + CDR ; + DIG 2 ; + DUP ; + DUG 3 ; + CDR ; + CDR ; + CDR ; + DIG 4 ; + DUP ; + DUG 5 ; + CAR ; + DIG 3 ; + DUP ; + DUG 4 ; + SWAP ; + SOME ; + SWAP ; + UPDATE ; + SWAP ; + DUP ; + DUG 2 ; + CDR ; + CAR ; + PAIR ; + SWAP ; + CAR ; + PAIR ; + DUP ; + CDR ; + PUSH nat 1 ; + DIG 3 ; + DUP ; + DUG 4 ; + ADD ; + DIG 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 ; + DIG 2 ; + CDR ; + DIG 2 ; + PAIR ; + SWAP ; + PAIR } } } + diff --git a/contracts/ligo/fa2/fa2_errors.mligo b/contracts/ligo/fa2/fa2_errors.mligo index a585b8937..f2d4f1d33 100644 --- a/contracts/ligo/fa2/fa2_errors.mligo +++ b/contracts/ligo/fa2/fa2_errors.mligo @@ -22,7 +22,7 @@ and it is initiated neither by the token owner nor a permitted operator let fa2_not_operator = "FA2_NOT_OPERATOR" (** `update_operators` entrypoint is invoked and `operator_transfer_policy` is -`No_transfer` or `Owner_transfer` +`No_transfer` or `Owner_transfer` or `Operator_transfer` *) let fa2_operators_not_supported = "FA2_OPERATORS_UNSUPPORTED" (** diff --git a/contracts/ligo/fa2/fa2_interface.mligo b/contracts/ligo/fa2/fa2_interface.mligo index 639fc75b6..d368b995c 100644 --- a/contracts/ligo/fa2/fa2_interface.mligo +++ b/contracts/ligo/fa2/fa2_interface.mligo @@ -101,6 +101,7 @@ type operator_transfer_policy = | No_transfer | Owner_transfer | Owner_or_operator_transfer + | Operator_transfer type owner_hook_policy = [@layout:comb] diff --git a/contracts/ligo/fa2/lib/fa2_operator_lib.mligo b/contracts/ligo/fa2/lib/fa2_operator_lib.mligo index f09547d14..5f4318ff0 100644 --- a/contracts/ligo/fa2/lib/fa2_operator_lib.mligo +++ b/contracts/ligo/fa2/lib/fa2_operator_lib.mligo @@ -13,6 +13,7 @@ helper functions To be part of FA2 storage to manage permitted operators *) type operator_storage = ((address * (address * token_id)), unit) big_map +type contract_operator_storage = address list (** Updates operator storage using an `update_operator` command. @@ -66,6 +67,7 @@ let make_operator_validator (tx_policy : operator_transfer_policy) : operator_va | No_transfer -> (failwith fa2_tx_denied : bool * bool) | Owner_transfer -> true, false | Owner_or_operator_transfer -> true, true + | Operator_transfer -> false, true in (fun (owner, operator, token_id, ops_storage : address * address * token_id * operator_storage) -> diff --git a/contracts/ligo/math.mligo b/contracts/ligo/math.mligo new file mode 100644 index 000000000..3b0ad3fa2 --- /dev/null +++ b/contracts/ligo/math.mligo @@ -0,0 +1,7 @@ +let ceil_div (tz_qty, nat_qty : tez * nat) : tez = + let ediv1 : (tez * tez) option = ediv tz_qty nat_qty in + match ediv1 with + | None -> (failwith "DIVISION_BY_ZERO" : tez) + | Some e -> + let (quotient, remainder) = e in + if remainder > 0mutez then (quotient + 1mutez) else quotient \ No newline at end of file diff --git a/contracts/ligo/src/english_auction/english_auction_tez.mligo b/contracts/ligo/src/english_auction/english_auction_tez.mligo index 6565002b1..3fa48a457 100644 --- a/contracts/ligo/src/english_auction/english_auction_tez.mligo +++ b/contracts/ligo/src/english_auction/english_auction_tez.mligo @@ -1,5 +1,6 @@ #include "../../fa2/fa2_interface.mligo" #include "../../fa2_modules/pauseable_admin_option.mligo" +#include "../../math.mligo" type fa2_tokens = [@layout:comb] @@ -115,14 +116,6 @@ let auction_in_progress (auction : auction) : bool = let first_bid (auction : auction) : bool = auction.highest_bidder = auction.seller -let ceil_div (tz_qty, nat_qty : tez * nat) : tez = - let ediv1 : (tez * tez) option = ediv tz_qty nat_qty in - match ediv1 with - | None -> (failwith "DIVISION_BY_ZERO" : tez) - | Some e -> - let (quotient, remainder) = e in - if remainder > 0mutez then (quotient + 1mutez) else quotient - let valid_bid_amount (auction : auction) : bool = (Tezos.amount >= (auction.current_bid + (ceil_div (auction.min_raise_percent * auction.current_bid, 100n)))) || (Tezos.amount >= auction.current_bid + auction.min_raise) || diff --git a/contracts/ligo/src/minter_collection/fa2_multi_nft_faucet_with_royalty.mligo b/contracts/ligo/src/minter_collection/fa2_multi_nft_faucet_with_royalty.mligo new file mode 100644 index 000000000..5e92a3feb --- /dev/null +++ b/contracts/ligo/src/minter_collection/fa2_multi_nft_faucet_with_royalty.mligo @@ -0,0 +1,21 @@ +#include "fa2_multi_nft_token_with_royalty.mligo" +#include "fa2_multi_nft_manager.mligo" + +type nft_faucet_entrypoints = + | Assets of fa2_royalty_entry_points + | Mint of mint_tokens_param + +type nft_faucet_storage = { + assets: nft_token_storage; + metadata: (string, bytes) big_map; (* contract metadata *) + } + +let nft_royalty_faucet_main (param, storage : nft_faucet_entrypoints * nft_faucet_storage) + : operation list * nft_faucet_storage = + match param with + | Assets fa2 -> + let ops, new_assets = fa2_royalty_main (fa2, storage.assets) in + ops, { storage with assets = new_assets; } + | Mint mp -> + let ops, new_assets = mint_tokens (mp, storage.assets) in + ops, { storage with assets = new_assets; } diff --git a/contracts/ligo/src/minter_collection/fa2_multi_nft_token_with_royalty.mligo b/contracts/ligo/src/minter_collection/fa2_multi_nft_token_with_royalty.mligo new file mode 100644 index 000000000..8e82a23df --- /dev/null +++ b/contracts/ligo/src/minter_collection/fa2_multi_nft_token_with_royalty.mligo @@ -0,0 +1,68 @@ +#if !FA2_MULTI_NFT_TOKEN_WITH_ROYALTY + +#define FA2_MULTI_NFT_TOKEN_WITH_ROYALTY + +#include "../../fa2/fa2_interface.mligo" +#include "../../fa2/fa2_errors.mligo" + +#include "../../fa2/lib/fa2_operator_lib.mligo" +#include "../../fa2/lib/fa2_owner_hooks_lib.mligo" +#include "../../math.mligo" +#include "fa2_multi_nft_token.mligo" + +type royalty_data = +[@layout:comb] +{ + creator : address; + fee_percent : nat +} + +type get_royalty_params = +[@layout:comb] +{ + token_id : token_id; + fee : tez; + cb : (address * tez) contract; +} + +type fa2_royalty_entry_points = + | FA2 of fa2_entry_points + | Get_Royalty of get_royalty_params + +let royalty_undefined = "FA2_ROYALTY_UNDEFINED" + +let getRoyalty (params, storage : get_royalty_params * nft_token_storage) + : (operation list) * nft_token_storage = + let royalty_data : royalty_data = ( match (Big_map.find_opt params.token_id storage.token_metadata) with + | Some md -> (match (Big_map.find_opt "royalty" md.token_info) with + | Some royalty_bytes -> ( match ((Bytes.unpack royalty_bytes) : royalty_data option ) with + | Some royalty_data -> royalty_data + | None -> (failwith royalty_undefined : royalty_data)) + | None -> (failwith royalty_undefined : royalty_data)) + | None -> (failwith fa2_token_undefined : royalty_data)) in + let royalty_fee = ceil_div(royalty_data.fee_percent * params.fee, 100n) in + let op = Tezos.transaction (royalty_data.creator, royalty_fee) 0mutez params.cb + in [op], storage + +let fa2_main (param, storage : fa2_entry_points * nft_token_storage) + : (operation list) * nft_token_storage = + match param with + | Transfer txs -> + let tx_descriptors = transfers_to_descriptors txs in + let operator_transfer : operator_transfer_policy = Operator_transfer in + let operator_validator = make_operator_validator operator_transfer in + (* will validate that a sender is an operator.*) + fa2_transfer (tx_descriptors, operator_validator, storage) + | Balance_of p -> + let op = get_balance (p, storage.ledger) in + [op], storage + | Update_operators updates -> + let u = (failwith fa2_operators_not_supported : (operation list) * nft_token_storage) + in u + +let fa2_royalty_main (param, storage : fa2_royalty_entry_points * nft_token_storage) + : (operation list) * nft_token_storage = match param with + | FA2 entrypoints -> fa2_main(entrypoints, storage) + | Get_Royalty royalty_param -> getRoyalty(royalty_param, storage) + +#endif diff --git a/contracts/src/compile-contracts.ts b/contracts/src/compile-contracts.ts index fe7b86a9d..01febe38d 100644 --- a/contracts/src/compile-contracts.ts +++ b/contracts/src/compile-contracts.ts @@ -15,6 +15,7 @@ async function main(): Promise { await compileEnglishAuctionTezContract(env); await compileFtFaucetContract(env); await compileFtContract(env); + await compileNftWithRoyaltyFaucetContract(env); // add other contracts here process.exit(0); @@ -46,6 +47,17 @@ async function compileNftContract(env: LigoEnv): Promise { $log.info('compiled NFT contract'); } +async function compileNftWithRoyaltyFaucetContract(env: LigoEnv): Promise { + $log.info('compiling nft with royalty faucet contract'); + + await compileContract( + env, + 'minter_collection/fa2_multi_nft_faucet_with_royalty.mligo', + 'nft_royalty_faucet_main', + 'nft_royalty_faucet.tz' + ); + $log.info('compiled nft with royalty faucet contract'); +} async function compileFtFaucetContract(env: LigoEnv): Promise { $log.info('compiling FT faucet contract');