Zilliqa / ZRC

Zilliqa Reference Contracts
MIT License
40 stars 57 forks source link

ZRC#1 #22

Open AmritKumar opened 5 years ago

AmritKumar commented 5 years ago

Comment tracker for ZRC 1.

VexyCats commented 4 years ago

Here are some things that are missing or need to be added.

Mapping of tokenID -> tokenURI

field tokenURI: Map Uint256 String= Emp Uint256 String

Some of the functions that need to be added to properly allow for full functionality plus contract to contract interaction:

TransferFrom(from, to, tokenID)- Callback needed for contract to contract interactions

isApproved(addressToCheckIfApproved, tokenID)(also known as getApproval)- Callback needed for contract to contract interactions

balanceOf(address: ByStr20) - Callback needed for contract to contract interactions

isOwner(tokenID, address) - Callback needed for contract to contract interactions

totalSupply() - Callback needed for contract to contract interactions

name()

symbol()

Mint() and MintWithTokenURI(string URL) - Mint should have a role attached to it - such that I can add another address as an approved address to mint tokens.

tokenURI(tokenID: uint256) - Callback needed for contract to contract interactions. Returns the token url

Suggestions:

 getTokenOwner <- tokenOwners[tokenId];
  match getTokenOwner with
  | None =>
    (* Token do not exist, return error code *)
    err = CodeNotFound;
    MakeError err
  | Some tokenOwner =>
    (* Check if sender is tokenOwner *)
    isOwner = builtin eq _sender tokenOwner;

Should probably be its own transition or library tokenExists(), plus a callback to allow for easy checking from outside contracts. Callback may or may not be needed but probably should atleast add it as a library to speed up calls in other transitions that check if exists.

snowsledge commented 4 years ago

Revamped ZRC-1 according to comments. See: https://github.com/Zilliqa/ZRC/pull/33

VexyCats commented 4 years ago

Suggest adding tokenID as an item returned in the callback for getApproved.

(* @getter: Get approved_addr for token_id *)
transition getApproved(token_id: Uint256)
  some_token_approval <- token_approvals[token_id];
  match some_token_approval with
  | Some addr => 
    msg_to_sender = { _tag : "getApprovedCallBack"; _recipient : _sender; _amount : Uint128 0; 
                      approved_addr : addr; tokenID: token_id};
    msgs = one_msg msg_to_sender;
    send msgs
  | None => throw
  end
end

Other than that it looks great.

snowsledge commented 4 years ago

@VexyCats Done! https://github.com/Zilliqa/ZRC/pull/33/commits/2c385f276fc803fe4c55e5f849e765ca34214818

VexyCats commented 4 years ago

Current ZRC-1 implementation used on Mintable:

scilla_version 0

(***************************************************)
(*               Associated library                *)
(***************************************************)
import BoolUtils
library NonfungibleToken

(* User-defined ADTs *)
type Dummy =
| Dummy

type Operation =
| Add
| Sub

(* Global variables *)
let zero = Uint256 0
let one = Uint256 1
let verdad = Dummy
let add_operation = Add
let sub_operation = Sub

(* Library functions *)
let one_msg = 
  fun (msg : Message) => 
    let nil_msg = Nil {Message} in
    Cons {Message} msg nil_msg

let two_msgs =
  fun (msg1 : Message) =>
  fun (msg2 : Message) =>
    let msgs_tmp = one_msg msg2 in
    Cons {Message} msg1 msgs_tmp

let get_bal =
  fun (some_bal: Option Uint256) =>
    match some_bal with
    | Some bal => bal
    | None => zero
    end

(* Error exception *)
type Error =
  | CodeNotContractOwner
  | CodeIsSelf
  | CodeTokenExists
  | CodeIsNotMinter
  | CodeNotApproved
  | CodeNotTokenOwner
  | CodeNotFound
  | CodeNotApprovedForAll
  | CodeNotOwnerOrOperator
  | CodeNotApprovedSpenderOrOperator

let make_error =
  fun (result : Error) =>
    let result_code = 
      match result with
      | CodeNotContractOwner             => Int32 -1
      | CodeIsSelf                       => Int32 -2
      | CodeTokenExists                  => Int32 -3
      | CodeIsNotMinter                  => Int32 -4
      | CodeNotApproved                  => Int32 -5
      | CodeNotTokenOwner                => Int32 -6
      | CodeNotFound                     => Int32 -7
      | CodeNotApprovedForAll            => Int32 -8
      | CodeNotOwnerOrOperator           => Int32 -9
      | CodeNotApprovedSpenderOrOperator => Int32 -10
      end
    in
    { _exception : "Error"; code : result_code }

(***************************************************)
(*             The contract definition             *)
(***************************************************)

contract NonfungibleToken
(
  contract_owner: ByStr20,
  name : String,
  symbol: String,
  dex_address: ByStr20
)

(* Mutable fields *)

(* Mapping of minters available *)
field minters: Map ByStr20 Dummy =
    let emp_map = Emp ByStr20 Dummy in
    builtin put emp_map contract_owner verdad

(* Mapping between token_id to token_owner *)
field token_owners: Map Uint256 ByStr20 = Emp Uint256 ByStr20

(* Mapping from owner to number of owned tokens *)
field owned_token_count: Map ByStr20 Uint256 = Emp ByStr20 Uint256

(* Mapping between token_id to approved address                              *)
(* @dev: There can only be one approved address per token at any given time. *)
field token_approvals: Map Uint256 ByStr20 = Emp Uint256 ByStr20

(* Mapping of token_owner to operator  *)
field operator_approvals: Map ByStr20 (Map ByStr20 Dummy)
                            = Emp ByStr20 (Map ByStr20 Dummy)

(* Mapping from token_id to token_uri *)
field token_uris: Map Uint256 String = Emp Uint256 String

(* Total token count *)
field total_supply: Uint256 = Uint256 0

(* Emit Errors *)
procedure ThrowError(err : Error)
  e = make_error err;
  throw e
end

procedure IsContractOwner()
  is_contract_owner = builtin eq contract_owner _sender;
  match is_contract_owner with
  | True => 
  | False =>
    err = CodeNotContractOwner;
    ThrowError err
  end
end

procedure IsSelf(address_a: ByStr20, address_b: ByStr20)
  is_self = builtin eq address_a address_b;
  match is_self with
  | False =>
  | True =>
    err = CodeIsSelf;
    ThrowError err
  end
end

procedure IsTokenExists(token_id: Uint256)
  token_exist <- exists token_owners[token_id];
  match token_exist with
  | False =>
  | True =>
    err = CodeTokenExists;
    ThrowError err
  end
end

procedure IsMinter(address: ByStr20)
  is_minter <- exists minters[_sender];
  match is_minter with
  | True =>
  | False =>
    err = CodeIsNotMinter;
    ThrowError err
  end
end

procedure IsTokenOwner(token_id: Uint256, address: ByStr20)
  some_token_owner <- token_owners[token_id];
  match some_token_owner with
  | Some addr => 
    is_token_owner = builtin eq addr address;
    match is_token_owner with
    | True =>
    | False =>
      err = CodeNotTokenOwner;
      ThrowError err
    end
  | None =>
    err = CodeNotFound;
    ThrowError err
  end
end

procedure IsApprovedForAll(token_owner: ByStr20, operator: ByStr20)
  is_operator_approved <- exists operator_approvals[token_owner][operator];
  match is_operator_approved with
  | True =>
  | False =>
    err = CodeNotApprovedForAll;
    ThrowError err
  end
end

procedure IsOwnerOrOperator(token_owner: ByStr20)
  is_owner = builtin eq _sender token_owner;
  is_approved_for_all <- exists operator_approvals[token_owner][_sender];
  is_authorised = orb is_owner is_approved_for_all;
  match is_authorised with
  | True =>
  | False =>
    err = CodeNotOwnerOrOperator;
    ThrowError err
  end
end

procedure IsApprovedSpenderOrOperator(token_id: Uint256, token_owner: ByStr20)
  some_token_approval <- token_approvals[token_id];
  is_approved = match some_token_approval with
    | None => False
    | Some approved_address => 
      builtin eq _sender approved_address
    end;
  is_operator <- exists operator_approvals[token_owner][_sender];
  is_authorised = orb is_approved is_operator;
  match is_authorised with
  | True =>
  | False =>
    err = CodeNotApprovedSpenderOrOperator;
    ThrowError err
  end
end

procedure UpdateTokenCount(operation: Operation, address: ByStr20)
  match operation with
  | Add =>
    some_to_count <- owned_token_count[address];
    new_to_count = 
      let current_count = get_bal some_to_count in
      builtin add current_count one;
    owned_token_count[address] := new_to_count
  | Sub =>
    some_from_count <- owned_token_count[address];
    new_from_count = 
      let current_count = get_bal some_from_count in
        let is_zero = builtin eq current_count zero in
          match is_zero with
          | True => zero
          | False => builtin sub current_count one
          end;
    owned_token_count[address] := new_from_count
  end
end

(* Getter transitions *)

(* @dev: Get number of NFTs assigned to a token_owner *)
transition BalanceOf(address: ByStr20)
  some_bal <- owned_token_count[address];
  balance = get_bal some_bal;
  msg_to_sender = { _tag : "BalanceOfCallBack"; _recipient : _sender; _amount : Uint128 0;
                   balance : balance};
  msgs = one_msg msg_to_sender;
  send msgs
end

(* @dev: Get total supply of NFTs minted *)
transition TotalSupply()
  current_supply <- total_supply;
  msg_to_sender = { _tag : "TotalSupplyCallBack"; _recipient : _sender; _amount : Uint128 0;
                   total_supply : current_supply};
  msgs = one_msg msg_to_sender;
  send msgs
end

(* @dev: Get name of the NFTs *)
transition Name()
  msg_to_sender = { _tag : "NameCallBack"; _recipient : _sender; _amount : Uint128 0;
                   name : name};
  msgs = one_msg msg_to_sender;
  send msgs
end

(* @dev: Get name of the NFTs *)
transition Symbol()
  msg_to_sender = { _tag : "SymbolCallBack"; _recipient : _sender; _amount : Uint128 0;
                   symbol : symbol};
  msgs = one_msg msg_to_sender;
  send msgs
end

(* @dev: Get approved_addr for token_id *)
transition GetApproved(token_id: Uint256)
  some_token_approval <- token_approvals[token_id];
  match some_token_approval with
  | Some addr => 
    msg_to_sender = { _tag : "GetApprovedCallBack"; _recipient : _sender; _amount : Uint128 0; 
                      approved_addr : addr; token_id : token_id};
    msgs = one_msg msg_to_sender;
    send msgs
  | None => 
    err = CodeNotApproved;
    ThrowError err
  end
end

(* @dev: Get the token_uri of a certain token_id *)
transition GetTokenURI(token_id: Uint256)
  some_token_uri <- token_uris[token_id];
  match some_token_uri with
  | Some token_uri =>
    msg_to_sender = { _tag : "GetTokenURICallBack"; _recipient : _sender; _amount : Uint128 0; 
                      token_uri : token_uri};
    msgs = one_msg msg_to_sender;
    send msgs
  | None =>
    err = CodeNotFound;
    ThrowError err
  end
end

(* @dev: Check if a token_id is owned by a token_owner *)
transition CheckTokenOwner(token_id: Uint256, address: ByStr20)
  IsTokenOwner token_id address;
  msg_to_sender = { _tag : "IsOwnerCallBack"; _recipient : _sender; _amount : Uint128 0};
  msgs = one_msg msg_to_sender;
  send msgs
end

(* @dev: Check if address is operator for token_owner *)
transition CheckApprovedForAll(token_owner: ByStr20, operator: ByStr20)
  IsApprovedForAll token_owner operator;
  msg_to_sender = { _tag : "IsApprovedForAllCallBack"; _recipient : _sender; _amount : Uint128 0};
  msgs = one_msg msg_to_sender;
  send msgs
end

(* Interface transitions *)

(* @dev:    Add or remove approved minters. Only contract_owner can approve minters. *)
(* @param:  minter      - Address of the minter to be approved or removed            *)
transition ConfigureMinter(minter: ByStr20)
  IsContractOwner;
  some_minter <- minters[minter];
  match some_minter with
  | Some Dummy => 
    (* Remove minter *)
    delete minters[minter];
    e = {_eventname: "RemovedMinterSuccess"; minter: minter};
    event e
  | None =>
    (* Add minter *)
    minters[minter] := verdad;
    e = {_eventname: "AddMinterSuccess"; minter: minter};
    event e
  end
end

(* @dev:    Mint new tokens. Only contract_owner can mint.         *)
(* @param:  to        - Address of the token recipient             *)
(* @param:  token_id  - ID of the new token to be minted           *)
(* @param:  token_uri -  URI of the the new token to be minted     *)
transition Mint(to: ByStr20, token_id: Uint256, token_uri: String)
  IsTokenExists token_id;
  IsMinter _sender;
  (* Mint new token *)
  token_owners[token_id] := to;
  (* Add to owner count *)
  UpdateTokenCount add_operation to;
  (* Add token_uri for token_id *)
  token_uris[token_id] := token_uri;
  (* Add to total_supply *)
  current_supply <- total_supply;
  new_supply = builtin add current_supply one;
  total_supply := new_supply; 
     token_approvals[token_id] := dex_address;
        (* Emit event *)
        e = {_eventname: "AddApprovalSuccess"; initiator: _sender; approved_addr: dex_address; token: token_id};
        event e;
  e = {_eventname: "MintSuccess"; by: _sender; recipient: to;
        token_id: token_id; token_uri: token_uri};
  event e;
  msg_to_recipient = { _tag : "RecipientAcceptMint"; _recipient : to; _amount : Uint128 0 };
  msg_to_sender = { _tag : "MintCallBack"; _recipient : _sender; _amount : Uint128 0;
                    recipient : to; token_id : token_id; token_uri : token_uri };
  msgs = two_msgs msg_to_recipient msg_to_sender;
  send msgs
end

(* @dev:    Burn existing tokens. Only token_owner or an operator can burn a NFT. *)
(* @param:  token_id - Unique ID of the NFT to be destroyed                       *)
transition Burn(token_id: Uint256)
  (* Check if token exists *)
  some_token_owner <- token_owners[token_id];
  match some_token_owner with
  | None =>
    err = CodeNotFound;
    ThrowError err
  | Some token_owner =>
    IsOwnerOrOperator token_owner;
    (* Destroy existing token *)
    delete token_owners[token_id];
    delete token_approvals[token_id];
    delete token_uris[token_id];
    (* Deduct from owned_token_count *)
    UpdateTokenCount sub_operation token_owner;
    (* Deduct from total_supply *)
    current_supply <- total_supply;
    new_supply = builtin sub current_supply one;
    total_supply := new_supply;
    e = {_eventname: "BurnSuccess"; initiator: _sender; burn_address: token_owner; token: token_id};
    event e;
    msg_to_sender = { _tag : "BurnCallBack"; _recipient : _sender; _amount : Uint128 0;
                      initiator : _sender; burn_address : token_owner; token_id : token_id };
    msgs = one_msg msg_to_sender;
    send msgs
  end
end

(* @dev: Approves OR remove an address ability to transfer a given token_id *)
(* There can only be one approved_spender per token at any given time       *)
(* param: to       - Address to be approved for the given token_id          *)
(* param: token_id - Unique ID of the NFT to be approved                    *)
transition SetApprove(to: ByStr20, token_id: Uint256)
  some_token_owner <- token_owners[token_id];
  match some_token_owner with
  | None =>
    err = CodeNotFound;
    ThrowError err
  | Some token_owner =>
    IsOwnerOrOperator token_owner;
    is_approved <- exists token_approvals[token_id];
    match is_approved with
    | True =>
      (* Remove approved_spender *)
      delete token_approvals[token_id];
      e = {_eventname: "RemoveApprovalSuccess"; initiator: _sender; removed_spender: to; token_id: token_id};
      event e;
      msg_to_sender = { _tag : "RemoveApprovalSuccessCallBack"; _recipient : _sender; _amount : Uint128 0; 
                        removed_spender : to; token_id : token_id };
      msgs = one_msg msg_to_sender;
      send msgs
    | False =>
      (* Add approved_spender *)
      token_approvals[token_id] := to;
      e = {_eventname: "AddApprovalSuccess"; initiator: _sender; approved_addr: to; token: token_id};
      event e;
      msg_to_sender = { _tag : "AddApprovalSuccessCallBack"; _recipient : _sender; _amount : Uint128 0; 
                        approved_spender : to; token_id : token_id };
      msgs = one_msg msg_to_sender;
      send msgs
    end
  end
end

(* @dev: Sets or unsets an operator for the _sender                *)
(* @param: to       - Address to be set or unset as an operator    *)
transition SetApprovalForAll(to: ByStr20)
  IsSelf to _sender;
  is_operator <- exists operator_approvals[_sender][to];
  match is_operator with
  | False =>
    (* Add operator *)
    operator_approvals[_sender][to] := verdad;
    e = {_eventname: "AddApprovalForAllSuccess"; initiator: _sender; operator: to};
    event e
  | True =>
    (* Remove operator *)
    delete operator_approvals[_sender][to];
    e = {_eventname: "RemoveApprovalForAllSuccess"; initiator: _sender; operator: to};
    event e
  end;
  new_status = negb is_operator;
  msg_to_sender = { _tag : "SetApprovalForAllSuccessCallBack"; _recipient : _sender; _amount : Uint128 0; 
                    operator : to; status : new_status};
  msgs = one_msg msg_to_sender;
  send msgs
end

(* @dev: Transfer the ownership of a given token_id to another address. token_owner only transition. *)
(* @param: to       - Recipient address for the token                                                *)
(* @param: token_id - Unique ID of the NFT to be transferred                                         *)
transition Transfer(to: ByStr20, token_id: Uint256)
  IsSelf to _sender;
  IsTokenOwner token_id _sender;
  (* Change token_owner for that token_id *)
  token_owners[token_id] := to;
  (* Delete tokenApproval entry for that token_id *)
  delete token_approvals[token_id];
  (* Subtract one from previous token owner count *)
  UpdateTokenCount sub_operation _sender;
  (* Add one to the new token owner count *)
  UpdateTokenCount add_operation to;
  e = {_eventname: "TransferSuccess"; from: _sender; recipient: to; token: token_id};
  event e;
  msg_to_recipient = { _tag : "RecipientAcceptTransfer"; _recipient : to; _amount : Uint128 0; 
                      from : _sender; recipient : to; token_id : token_id };
  msg_to_sender = { _tag : "TransferSuccessCallBack"; _recipient : _sender; _amount : Uint128 0; 
                    from : _sender; recipient : to; token_id : token_id };
  msgs = two_msgs msg_to_recipient msg_to_sender;
  send msgs
end

(* @dev: Transfer the ownership of a given token_id to another address. approved_spender or operator only transition. *)
(* @param: to       - Recipient address for the NFT                                                                   *)
(* @param: token_id - Unique ID of the NFT to be transferred                                                          *)
transition TransferFrom(to: ByStr20, token_id: Uint256)
  some_token_owner <- token_owners[token_id];
  match some_token_owner with
  | None =>
    err = CodeNotFound;
    ThrowError err
  | Some token_owner =>
    IsSelf to token_owner;
    IsApprovedSpenderOrOperator token_id token_owner;
    (* Change token_owner for that token_id *)
    token_owners[token_id] := to;
    (* Delete tokenApproval entry for that token_id *)
    delete token_approvals[token_id];
    (* Subtract one from previous token owner count *)
    UpdateTokenCount sub_operation token_owner;
    (* Add one to the new token owner count *)
    UpdateTokenCount add_operation to;
    e = {_eventname: "TransferFromSuccess"; from: token_owner; recipient: to; token: token_id};
    event e;
    msg_to_recipient = { _tag : "RecipientAcceptTransferFrom"; _recipient : to; _amount : Uint128 0; 
                        from : token_owner; recipient : to; token_id : token_id };
    msg_to_sender = { _tag : "TransferFromSuccessCallBack"; _recipient : _sender; _amount : Uint128 0; 
                      from : token_owner; recipient : to; token_id : token_id };
    msgs = two_msgs msg_to_recipient msg_to_sender;
    send msgs
  end
end