Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZRC#1 #22

Open
AmritKumar opened this issue Oct 28, 2019 · 5 comments
Open

ZRC#1 #22

AmritKumar opened this issue Oct 28, 2019 · 5 comments

Comments

@AmritKumar
Copy link
Contributor

Comment tracker for ZRC 1.

@VexyCats
Copy link

VexyCats commented Jan 30, 2020

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
Copy link
Contributor

Revamped ZRC-1 according to comments. See:
#33

@VexyCats
Copy link

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
Copy link
Contributor

@VexyCats Done! 2c385f2

@VexyCats
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants