Skip to content
This repository has been archived by the owner on Aug 4, 2023. It is now read-only.

Added support for multiple royalty payments for FT tokens #188

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
(*
One of the possible implementations of token management API which can create
new fungible tokens, mint and burn them.

Token manager API allows to:

1. Create new toke types,
2. Mint and burn tokens to some existing or new owner account.

Burn operation fails if the owner holds less tokens then burn amount.
*)

#if !TOKEN_MANAGER
#define TOKEN_MANAGER

#include "fa2_multi_ft_token.mligo"

type mint_burn_tx =
[@layout:comb]
{
owner : address;
token_id : token_id;
amount : nat;
}

type mint_burn_tokens_param = mint_burn_tx list


(* `token_manager` entry points *)
type token_manager =
| Create_token of (token_metadata * royalty)
| Mint_tokens of mint_burn_tokens_param
| Burn_tokens of mint_burn_tokens_param

let verify_royalty (ry : royalty) : unit =
let sum_values = (fun (acc, entry : nat * (address * nat)) -> acc + entry.1) in
let expected_total = Map.fold sum_values royalty.scheme 0 in
if expected_total <> royalty.total then (failwith "Invalid total!" : unit)
else (() : unit)

let create_token (metadata, royalty, storage
: token_metadata * royalty * multi_ft_token_storage) : multi_ft_token_storage =
(* extract token id *)
let new_token_id = metadata.token_id in
let existing_meta = Big_map.find_opt new_token_id storage.token_metadata in
match existing_meta with
| Some m -> (failwith "FA2_DUP_TOKEN_ID" : multi_ft_token_storage)
| None ->
let verify_royalty (royalty) in
let meta = Big_map.add new_token_id metadata storage.token_metadata in
let supply = Big_map.add new_token_id 0n storage.token_total_supply in
let new_rates = Big_map.add new_token_id royalty storage.rates in
{ storage with
token_metadata = meta;
token_total_supply = supply;
rates = new_rates;
}


let mint_update_balances (txs, ledger : (mint_burn_tx list) * ledger) : ledger =
let mint = fun (l, tx : ledger * mint_burn_tx) ->
inc_balance (tx.owner, tx.token_id, tx.amount, l) in

List.fold mint txs ledger

let mint_update_total_supply (txs, total_supplies
: (mint_burn_tx list) * token_total_supply) : token_total_supply =
let update = fun (supplies, tx : token_total_supply * mint_burn_tx) ->
let supply_opt = Big_map.find_opt tx.token_id supplies in
match supply_opt with
| None -> (failwith fa2_token_undefined : token_total_supply)
| Some ts ->
let new_s = ts + tx.amount in
Big_map.update tx.token_id (Some new_s) supplies in

List.fold update txs total_supplies

let mint_tokens (param, storage : mint_burn_tokens_param * multi_ft_token_storage)
: multi_ft_token_storage =
let new_ledger = mint_update_balances (param, storage.ledger) in
let new_supply = mint_update_total_supply (param, storage.token_total_supply) in
let new_s = { storage with
ledger = new_ledger;
token_total_supply = new_supply;
} in
new_s

let burn_update_balances(txs, ledger : (mint_burn_tx list) * ledger) : ledger =
let burn = fun (l, tx : ledger * mint_burn_tx) ->
dec_balance (tx.owner, tx.token_id, tx.amount, l) in

List.fold burn txs ledger

let burn_update_total_supply (txs, total_supplies
: (mint_burn_tx list) * token_total_supply) : token_total_supply =
let update = fun (supplies, tx : token_total_supply * mint_burn_tx) ->
let supply_opt = Big_map.find_opt tx.token_id supplies in
match supply_opt with
| None -> (failwith fa2_token_undefined : token_total_supply)
| Some ts ->
let new_s = match Michelson.is_nat (ts - tx.amount) with
| None -> (failwith fa2_insufficient_balance : nat)
| Some s -> s
in
Big_map.update tx.token_id (Some new_s) supplies in

List.fold update txs total_supplies

let burn_tokens (param, storage : mint_burn_tokens_param * multi_ft_token_storage)
: multi_ft_token_storage =

let new_ledger = burn_update_balances (param, storage.ledger) in
let new_supply = burn_update_total_supply (param, storage.token_total_supply) in
let new_s = { storage with
ledger = new_ledger;
token_total_supply = new_supply;
} in
new_s

let ft_token_manager (param, s : token_manager * multi_ft_token_storage)
: (operation list) * multi_ft_token_storage =
match param with

| Create_token {token_metadata, royalty} ->
let new_s = create_token (token_metadata, royalty, s) in
(([]: operation list), new_s)

| Mint_tokens param ->
let new_s = mint_tokens (param, s) in
([] : operation list), new_s

| Burn_tokens param ->
let new_s = burn_tokens (param, s) in
([] : operation list), new_s

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#if !FA2_MAC_TOKEN
#define FA2_MAC_TOKEN

#include "../../../fa2/fa2_interface.mligo"
#include "../../../fa2/fa2_errors.mligo"
#include "../../../fa2/lib/fa2_operator_lib.mligo"

type royalty_transfer = {
tez : tez;
transfer : transfer;
}

type fa2_entry_points =
| Transfer of transfer list
| Balance_of of balance_of_param
| Update_operators of update_operator list
| Royalty_transfer of royalty_transfer list
(* | Token_metadata_registry of address contract *)

(* (owner,token_id) -> balance *)
type ledger = ((address * token_id), nat) big_map

(* token_id -> total_supply *)
type token_total_supply = (token_id, nat) big_map

type royalty = {
scheme : (address, nat) map;
total : nat;
}

type rates = (token_id, royalty) big_map

#if !LIMITED_TOKEN_MANAGER

type multi_ft_token_storage = {
ledger : ledger;
operators : operator_storage;
token_total_supply : token_total_supply;
token_metadata : token_metadata_storage;
rates : rates;
}

#else

type multi_ft_token_storage = {
ledger : ledger;
operators : operator_storage;
token_total_supply : token_total_supply;
token_metadata : token_metadata_storage;
next_token_id : token_id;
rates : rates;
}

#endif

(**
Ensures sum of (tez associated with each transfer) do not exceed Tezos.amount.
*)
let verify_royalty_transfer_list (rts : royalty_transfer list) : unit =
let amt : tez = Tezos.amount in
let sum (acc, rt : tez * royalty_transfer) : tez = acc + rt.tez in
let tez_sum = List.fold_left sum 0tez rts in
if tez_sum <= amt then () : unit
else (failwith "List of tez to associate w/ each transfer not valid!" : unit)

let get_balance_amt (key, ledger : (address * nat) * ledger) : nat =
let bal_opt = Big_map.find_opt key ledger in
match bal_opt with
| None -> 0n
| Some b -> b

let inc_balance (owner, token_id, amt, ledger
: address * token_id * nat * ledger) : ledger =
let key = owner, token_id in
let bal = get_balance_amt (key, ledger) in
let updated_bal = bal + amt in
if updated_bal = 0n
then Big_map.remove key ledger
else Big_map.update key (Some updated_bal) ledger

let dec_balance (owner, token_id, amt, ledger
: address * token_id * nat * ledger) : ledger =
let key = owner, token_id in
let bal = get_balance_amt (key, ledger) in
match Michelson.is_nat (bal - amt) with
| None -> (failwith fa2_insufficient_balance : ledger)
| Some new_bal ->
if new_bal = 0n
then Big_map.remove key ledger
else Big_map.update key (Some new_bal) ledger

(**
Update leger balances according to the specified transfers. Fails if any of the
permissions or constraints are violated.
@param txs transfers to be applied to the ledger
@param validate_op function that validates of the tokens from the particular owner can be transferred.
*)
let transfer (txs, validate_op, storage
: (transfer list) * operator_validator * multi_ft_token_storage)
: ledger =
let make_transfer = fun (l, tx : ledger * transfer) ->
List.fold
(fun (ll, dst : ledger * transfer_destination) ->
if not Big_map.mem dst.token_id storage.token_metadata
then (failwith fa2_token_undefined : ledger)
else
let u = validate_op (tx.from_, Tezos.sender, dst.token_id, storage.operators) in
let lll = dec_balance (tx.from_, dst.token_id, dst.amount, ll) in
inc_balance(dst.to_, dst.token_id, dst.amount, lll)
) tx.txs l
in
List.fold make_transfer txs storage.ledger

let royalty_transfer (rts, validate_op, storage
: (royalty_transfer list) * operator_validator * multi_ft_token_storage)
: operation list * multi_ft_token_storage =
let oplist_concat = (fun (left, right : operation list * operation list) -> // want some way to concat op lists
let (concat = fun (acc, op : operation list, operation) -> acc :: op) in
List.fold_left concat left right) in
let token_payments = (fun (tok_id, amt, current_owner: token_id, tez, address) -> // create a list of ops for the payments
let scheme : (address, nat) map = (Big_map.find_opt tok_id storage.rates).scheme in
let total : nat = (Big_map.find_opt tok_id storage.rates).total in
let get_payment_op = (fun (acc, entry : op list * (address, nat)) -> // to itereate over the entries and add the op of each
(if entry.0 = Tezos.self_address then
let op = Tezos.transaction (() : unit) ((entry.1 * amt) / total) current_owner
else let op = Tezos.trasnaction (() : unit) ((entry.1 * amt) / total) entry.0) in
acc :: op
) in
Map.fold get_payment_op scheme ([] : operation list)
) in
verify_royalty_transfer_list (rts) in // rts must be valid
let make_transfer = fun (l, rt : ledger * royalty_transfer) ->
List.fold
(fun (ll, dst : ledger * transfer_destination) ->
if not Big_map.mem dst.token_id storage.token_metadata
then (failwith fa2_token_undefined : ledger)
else
let u = validate_op (rt.transfer.from_, Tezos.sender, dst.token_id, storage.operators) in
let lll = dec_balance (rt.transfer.from_, dst.token_id, dst.amount, ll) in
inc_balance(dst.to_, dst.token_id, dst.amount, lll) in

) rt.transfer.txs l
in
let make_payment = fun (ops, rt : operation list * royalty_transfer) ->
List.fold
(fun (opss, dst : operation list * transfer_destination) ->
oplist_concat opss (token_payments (dst.token_id, rt.tez, rt.transfer.from_))
) rt.transfer.txs ops
let new_storage : multi_ft_token_storage = { storage with ledger = List.fold make_transfer rts storage.ledger; } in
let ops : operation list = List.fold make_payment rts ([] : operation list) in
(ops, new_storage)

let get_balance (p, ledger, tokens
: balance_of_param * ledger * token_metadata_storage) : operation =
let to_balance = fun (r : balance_of_request) ->
if not Big_map.mem r.token_id tokens
then (failwith fa2_token_undefined : balance_of_response)
else
let key = r.owner, r.token_id in
let bal = get_balance_amt (key, ledger) in
let response : balance_of_response = { request = r; balance = bal; } in
response
in
let responses = List.map to_balance p.requests in
Tezos.transaction responses 0mutez p.callback


let fa2_main (param, storage : fa2_entry_points * multi_ft_token_storage)
: (operation list) * multi_ft_token_storage =
match param with
| Transfer txs ->
(*
will validate that a sender is either `from_` parameter of each transfer
or a permitted operator for the owner `from_` address.
*)
let new_ledger = transfer (txs, default_operator_validator, storage) in
let new_storage = { storage with ledger = new_ledger; }
in ([] : operation list), new_storage

| Royalty_transfer rts ->
royalty_transfer (rts, default_operator_validator, storage)

| Balance_of p ->
let op = get_balance (p, storage.ledger, storage.token_metadata) in
[op], storage

| Update_operators updates ->
let new_ops = fa2_update_operators (updates, storage.operators) in
let new_storage = { storage with operators = new_ops; } in
([] : operation list), new_storage

#endif