Open AmritKumar opened 5 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.
Revamped ZRC-1 according to comments. See: https://github.com/Zilliqa/ZRC/pull/33
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.
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
Comment tracker for ZRC 1.