Chuyển code erc721 solidity sang ink!

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()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(())
    }
Ink!

Tài liệu tham khảo: