Cách chuyển code erc721 solidity sang ink!
Định nghĩa on-chain storage và events và errors
Solidity
string public name;
string public symbol;
uint256 public nextTokenIdToMint;
address public contractOwner;
// token id => owner
mapping(uint256 => address) internal _owners;
// owner => token count
mapping(address => uint256) internal _balances;
// token id => approved address
mapping(uint256 => address) internal _tokenApprovals;
// owner => (operator => yes/no)
mapping(address => mapping(address => bool)) internal _operatorApprovals;
// token id => token uri
mapping(uint256 => string) _tokenUris;
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
Ink!
/// A token ID.
pub type TokenId = u32;
#[ink(storage)]
#[derive(Default)]
pub struct Erc721 {
name: String,
symbol: String,
contract_owner: Option<AccountId>,
/// Mapping from token to owner.
token_owner: Mapping<TokenId, AccountId>,
/// Mapping from token to approvals users.
token_approvals: Mapping<TokenId, AccountId>,
/// Mapping from owner to number of owned token.
owned_tokens_count: Mapping<AccountId, u32>,
/// Mapping from owner to operator approvals.
operator_approvals: Mapping<(AccountId, AccountId), ()>,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[ink::scale_derive(Encode, Decode, TypeInfo)]
pub enum Error {
NotOwner,
NotApproved,
TokenExists,
TokenNotFound,
CannotInsert,
CannotFetchValue,
NotAllowed,
}
#[ink(event)]
pub struct Transfer {
#[ink(topic)]
from: Option<AccountId>,
#[ink(topic)]
to: Option<AccountId>,
#[ink(topic)]
id: TokenId,
}
#[ink(event)]
pub struct Approval {
#[ink(topic)]
from: AccountId,
#[ink(topic)]
to: AccountId,
#[ink(topic)]
id: TokenId,
}
#[ink(event)]
pub struct ApprovalForAll {
#[ink(topic)]
owner: AccountId,
#[ink(topic)]
operator: AccountId,
approved: bool,
}
Tạo constructor
Solidity
constructor(string memory _name, string memory _symbol) {
name = _name;
symbol = _symbol;
nextTokenIdToMint = 0;
contractOwner = msg.sender;
}
Ink!
#[ink(constructor)]
pub fn new() -> Self {
Default::default()
}
Tạo hàm balanceOf
Solidity
function balanceOf(address _owner) public view returns(uint256) {
require(_owner != address(0), "!Add0");
return _balances[_owner];
}
Ink!
#[ink(message)]
pub fn balance_of(&self, owner: AccountId) -> u32 {
self.balance_of_or_zero(&owner)
}
Tạo hàm ownerOf
Ink!
function ownerOf(uint256 _tokenId) public view returns(address) {
return _owners[_tokenId];
}
Ink!
#[ink(message)]
pub fn owner_of(&self, id: TokenId) -> Option<AccountId> {
self.token_owner.get(id)
}
Tạo hàm transfer
Solidity
function transfer(address _to, uint256 _value) public returns(bool) {
require((_balances[msg.sender] >= _value) && (_balances[msg.sender] != 0), "!Bal");
_balances[msg.sender] -= _value;
_balances[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
Ink!
#[ink(message)]
pub fn transfer(&mut self, to: AccountId, value: Balance) -> Result<()> {
let from = self.env().caller();
self.transfer_from_to(&from, &to, value)
}
Tạo hàm transferFrom
Solidity
function transferFrom(address _from, address _to, uint256 _tokenId) public payable {
// unsafe transfer without onERC721Received, used for contracts that dont implement
require(ownerOf(_tokenId) == msg.sender || _tokenApprovals[_tokenId] == msg.sender || _operatorApprovals[ownerOf(_tokenId)][msg.sender], "!Auth");
_transfer(_from, _to, _tokenId);
}
function _transfer(address _from, address _to, uint256 _tokenId) internal {
require(ownerOf(_tokenId) == _from, "!Owner");
require(_to != address(0), "!ToAdd0");
delete _tokenApprovals[_tokenId];
_balances[_from] -= 1;
_balances[_to] += 1;
_owners[_tokenId] = _to;
emit Transfer(_from, _to, _tokenId);
}
Ink!
#[ink(message)]
pub fn transfer_from(
&mut self,
from: AccountId,
to: AccountId,
id: TokenId,
) -> Result<(), Error> {
self.transfer_token_from(&from, &to, id)?;
Ok(())
}
Tạo hàm approve
Solidity
function approve(address _approved, uint256 _tokenId) public payable {
require(ownerOf(_tokenId) == msg.sender, "!Owner");
_tokenApprovals[_tokenId] = _approved;
emit Approval(ownerOf(_tokenId), _approved, _tokenId);
}
Ink!
#[ink(message)]
pub fn approve(&mut self, to: AccountId, id: TokenId) -> Result<(), Error> {
self.approve_for(&to, id)?;
Ok(())
}
Tạo hàm setApproveAll
Solidity
function setApprovalForAll(address _operator, bool _approved) public {
_operatorApprovals[msg.sender][_operator] = _approved;
emit ApprovalForAll(msg.sender, _operator, _approved);
}
Ink!
#[ink(message)]
pub fn set_approval_for_all(
&mut self,
to: AccountId,
approved: bool,
) -> Result<(), Error> {
self.approve_for_all(to, approved)?;
Ok(())
}
Tạo hàm getApproved()
và isApprovedForAll()
Solidity
function getApproved(uint256 _tokenId) public view returns (address) {
return _tokenApprovals[_tokenId];
}
function isApprovedForAll(address _owner, address _operator) public view returns (bool) {
return _operatorApprovals[_owner][_operator];
}
Ink!
/// Returns the approved account ID for this token if any.
#[ink(message)]
pub fn get_approved(&self, id: TokenId) -> Option<AccountId> {
self.token_approvals.get(id)
}
/// Returns `true` if the operator is approved by the owner.
#[ink(message)]
pub fn is_approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool {
self.approved_for_all(owner, operator)
}
Tạo hàm mint()
Solidity
function mintTo(address _to, string memory _uri) public {
require(contractOwner == msg.sender, "!Auth");
_owners[nextTokenIdToMint] = _to;
_balances[_to] += 1;
_tokenUris[nextTokenIdToMint] = _uri;
emit Transfer(address(0), _to, nextTokenIdToMint);
nextTokenIdToMint += 1;
}
Ink!
#[ink(message)]
pub fn mint(&mut self, id: TokenId) -> Result<(), Error> {
let caller = self.env().caller();
self.add_token_to(&caller, id)?;
self.env().emit_event(Transfer {
from: Some(AccountId::from([0x0; 32])),
to: Some(caller),
id,
});
Ok(())
}
Helper function
Hàm clear_approval()
Ink!
fn clear_approval(&mut self, id: TokenId) {
self.token_approvals.remove(id);
}
Hàm balance_of_or_zero()
Ink!
fn balance_of_or_zero(&self, of: &AccountId) -> u32 {
self.owned_tokens_count.get(of).unwrap_or(0)
}
Hàm approved_for_all()
Ink!
fn approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool {
self.operator_approvals.contains((&owner, &operator))
}
Hàm approved_or_owner()
Ink!
fn approved_or_owner(
&self,
from: AccountId,
id: TokenId,
owner: AccountId,
) -> bool {
from != AccountId::from([0x0; 32])
&& (from == owner
|| self.token_approvals.get(id) == Some(from)
|| self.approved_for_all(owner, from))
}
Hàm add_token_to()
Ink!
fn add_token_to(&mut self, to: &AccountId, id: TokenId) -> Result<(), Error> {
let Self {
token_owner,
owned_tokens_count,
..
} = self;
if token_owner.contains(id) {
return Err(Error::TokenExists);
}
if *to == AccountId::from([0x0; 32]) {
return Err(Error::NotAllowed);
};
let count = owned_tokens_count
.get(to)
.map(|c| c.checked_add(1).unwrap())
.unwrap_or(1);
owned_tokens_count.insert(to, &count);
token_owner.insert(id, to);
Ok(())
}
Hàm remove_token_to()
Ink!
fn remove_token_from(
&mut self,
from: &AccountId,
id: TokenId,
) -> Result<(), Error> {
let Self {
token_owner,
owned_tokens_count,
..
} = self;
if !token_owner.contains(id) {
return Err(Error::TokenNotFound);
}
let count = owned_tokens_count
.get(from)
.map(|c| c.checked_sub(1).unwrap())
.ok_or(Error::CannotFetchValue)?;
owned_tokens_count.insert(from, &count);
token_owner.remove(id);
Ok(())
}
Hàm transfer_from_to()
Ink!
fn transfer_token_from(
&mut self,
from: &AccountId,
to: &AccountId,
id: TokenId,
) -> Result<(), Error> {
let caller = self.env().caller();
let owner = self.owner_of(id).ok_or(Error::TokenNotFound)?;
if !self.approved_or_owner(caller, id, owner) {
return Err(Error::NotApproved);
};
if owner != *from {
return Err(Error::NotOwner);
};
self.clear_approval(id);
self.remove_token_from(from, id)?;
self.add_token_to(to, id)?;
self.env().emit_event(Transfer {
from: Some(*from),
to: Some(*to),
id,
});
Ok(())
}