ERC-721 NFT
ERC-721 with ERC-6093 errors, including safeTransferFrom and its receiver check.
erc721.lithe
(* ERC-721: ownership, approvals, operators, safe and unsafe transfers, mint, and burn. *)
import "std/abi";
import "std/address";
import "std/evm";
interface ERC721Receiver is {
function onERC721Received(operator : address; from : address; tokenId : uint; data : bytes) : bytes4;
}
error ERC721NonexistentToken of uint;
error ERC721InvalidReceiver of address;
error ERC721InsufficientApproval of record {
operator : address;
tokenId : uint;
}
error ERC721InvalidSender of address;
error ERC721InvalidOwner of address;
error AlreadyMinted of uint;
error NotMinter of address;
event Transfer of record {
indexed from : address;
indexed to : address;
indexed tokenId : uint;
}
event Approval of record {
indexed owner : address;
indexed approved : address;
indexed tokenId : uint;
}
event ApprovalForAll of record {
indexed owner : address;
indexed operator : address;
approved : bool;
}
storage is record {
minter : address;
owners : map(uint, address);
balances : map(address, uint);
approvals : map(uint, address);
operators : map(address, map(address, bool));
}
external function init() is {
storage.minter := Evm.sender();
}
view function requireOwner(const id : uint) : address
raises ERC721NonexistentToken
is {
const owner : address := storage.owners[id];
assert(owner <> Address.zero, ERC721NonexistentToken(id));
} with owner
external view function ownerOf(const tokenId : uint) : address
raises ERC721NonexistentToken
is
requireOwner(tokenId)
external view function balanceOf(const owner : address) : uint
raises ERC721InvalidOwner
is {
assert(owner <> Address.zero, ERC721InvalidOwner(owner));
} with storage.balances[owner]
external view function getApproved(const tokenId : uint) : address
raises ERC721NonexistentToken
is {
const owner : address := requireOwner(tokenId);
} with storage.approvals[tokenId]
external view function isApprovedForAll(const owner : address; const operator : address) : bool is
storage.operators[owner][operator]
external function approve(const to : address; const tokenId : uint)
raises ERC721NonexistentToken | ERC721InsufficientApproval
is {
const owner : address := requireOwner(tokenId);
assert(
Evm.sender() = owner or storage.operators[owner][Evm.sender()],
ERC721InsufficientApproval { operator = Evm.sender(); tokenId = tokenId; }
);
storage.approvals[tokenId] := to;
emit Approval { owner = owner; approved = to; tokenId = tokenId; };
}
external function setApprovalForAll(const operator : address; const approved : bool) is {
storage.operators[Evm.sender()][operator] := approved;
emit ApprovalForAll { owner = Evm.sender(); operator = operator; approved = approved; };
}
function move(const from : address; const to : address; const tokenId : uint)
raises ERC721NonexistentToken | ERC721InsufficientApproval | ERC721InvalidSender | ERC721InvalidReceiver
is {
assert(to <> Address.zero, ERC721InvalidReceiver(to));
const owner : address := requireOwner(tokenId);
assert(owner = from, ERC721InvalidSender(from));
const caller : address := Evm.sender();
assert(
caller = owner or storage.approvals[tokenId] = caller or storage.operators[owner][caller],
ERC721InsufficientApproval { operator = caller; tokenId = tokenId; }
);
storage.approvals[tokenId] := Address.zero;
storage.balances[from] := storage.balances[from] - 1;
storage.balances[to] := storage.balances[to] + 1;
storage.owners[tokenId] := to;
emit Transfer { from = from; to = to; tokenId = tokenId; };
}
(* A contract receiver must answer with `onERC721Received`'s selector. The call runs
after the transfer's effects. A receiver that answers wrongly, or reverts without a
reason, is rejected; one that reverts with a reason has it passed on unchanged.
Either reverts the whole transfer. *)
function checkReceived(const from : address; const to : address; const tokenId : uint; const data : bytes)
raises ERC721InvalidReceiver | forwarded
is {
if Evm.codeSize(to) > 0 then {
defer ERC721Receiver(to).onERC721Received(Evm.sender(), from, tokenId, data) then {
| Ok(answer : bytes4) -> {
assert(answer = Abi.selector(ERC721Receiver.onERC721Received), ERC721InvalidReceiver(to));
}
| Error(reason : bytes) -> {
if reason.length = 0 then {
fail ERC721InvalidReceiver(to);
}
forward reason;
}
};
}
}
external function transferFrom(const from : address; const to : address; const tokenId : uint)
raises ERC721NonexistentToken | ERC721InsufficientApproval | ERC721InvalidSender | ERC721InvalidReceiver
is {
move(from, to, tokenId);
}
external nonreentrant function safeTransferFrom(const from : address; const to : address; const tokenId : uint)
raises ERC721NonexistentToken | ERC721InsufficientApproval | ERC721InvalidSender | ERC721InvalidReceiver | forwarded
is {
move(from, to, tokenId);
checkReceived(from, to, tokenId, "");
}
external nonreentrant function safeTransferFrom(
const from : address;
const to : address;
const tokenId : uint;
const data : bytes
)
raises ERC721NonexistentToken | ERC721InsufficientApproval | ERC721InvalidSender | ERC721InvalidReceiver | forwarded
is {
move(from, to, tokenId);
checkReceived(from, to, tokenId, data);
}
external function mint(const to : address; const tokenId : uint)
raises NotMinter | AlreadyMinted | ERC721InvalidReceiver
is {
assert(Evm.sender() = storage.minter, NotMinter(Evm.sender()));
assert(to <> Address.zero, ERC721InvalidReceiver(to));
assert(storage.owners[tokenId] = Address.zero, AlreadyMinted(tokenId));
storage.owners[tokenId] := to;
storage.balances[to] := storage.balances[to] + 1;
emit Transfer { from = Address.zero; to = to; tokenId = tokenId; };
}
external function burn(const tokenId : uint)
raises ERC721NonexistentToken | ERC721InsufficientApproval
is {
const owner : address := requireOwner(tokenId);
assert(Evm.sender() = owner, ERC721InsufficientApproval { operator = Evm.sender(); tokenId = tokenId; });
storage.approvals[tokenId] := Address.zero;
storage.balances[owner] := storage.balances[owner] - 1;
storage.owners[tokenId] := Address.zero;
emit Transfer { from = owner; to = Address.zero; tokenId = tokenId; };
}safeTransferFromis overloaded, as the standard requires:safeTransferFrom(address,address,uint256)andsafeTransferFrom(address,address,uint256,bytes).ERC721Receiver(to)turns the recipient's address into an interface value, so the receiver hook can be deferred.- The hook is a recoverable call. A receiver that reverts without a reason becomes a typed
ERC721InvalidReceivererror; one with a reason has it forwarded, so the safe transfers raiseforwarded.

