(Part 16) Ethereum Solidity - ERC721 Third Party Approval, Transfer, Events And Full Implementation(PT 16)

in #utopian-io6 years ago

Repository

https://github.com/igormuba/EthereumSolidityClasses/tree/master/class16

What Will I Learn?

  • ERC721 approval and authorizations
  • Implementing transfers called by third parties
  • Full implementation of ERC721

Requirements

  • Internet connection
  • Code editor
  • Browser

Difficulty

  • Intermediate

Tutorial Contents

On this tutorial, we will work on the approval functions from the ERC721 token standard. Previously we have implemented a fully functional ERC721 token, but the contract we have developed still lacks the approval functions.

The approval functions are used for token owners to allow third parties (like someone else or some app) to manage one of their tokens, or all of their assets. We will work a lot with mapping here.

Approved addresses

We need to set a mapping to keep track of what address has been approved to manage said token. Luckily for us, we have already implemented something very similar to this functionality with the mapping

mapping(uint => address) public ownership;

It is just a matter of either refactor the code or implement the new functionality to change it

NFT Approval

Now, we implement the function that will add an approved address for an NFT (Non-Fungible Token)

According to the ERC721 documentation

/// @notice Set or reaffirm the approved address for an NFT
            /// @dev The zero address indicates there is no approved address.
            /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
            ///  operator of the current owner.
            /// @param _approved The new approved NFT controller
            /// @param _tokenId The NFT to approve
            function approve(address _approved, uint256 _tokenId) external payable;

We have a very similar function for own internal use, but we must implement one with the standard naming so that third-party apps can know how to work with our tokens

    function approve(address _approved, uint256 _tokenId) external payable{
        require(msg.sender == ownership[_tokenId]);
        ownership[_tokenId]=_approved;
        tokens[_tokenId].owner=_approved;
    }

With the function above we are effectively changing the ownership of a token, or, adding an approved address

The require is a security measure, so that, only the current owner of a token can change the approval of the token.

Approval for all

The next step is to set a global approval so that an address (contract or wallet) can manage all the tokens from an account
For this, we will use a mapping of mappings mapping(address=>mapping(address=>bool)) public allowedAddresses;
At first glance, how the mapping of mappings works is not very intuitive, but it is not hard either to understand.
It maps many addresses to one address, so we can set more than one approved address to manage someone's funds

Now, to work with that, when you pass an address to allowedAddresses it returns a mapping, so after that, you need to pass another address to get a boolean. At first, you might think that working with that would look like allowedAddresses[msg.sender[_operator]], but that is wrong, look at the function implemented below to see how it actually works

function setApprovalForAll(address _operator, bool _approved) external{
        allowedAddresses[msg.sender][_operator]= _approved;
    }

Here we do not need to set the require because we are setting on the mapping mapping(address=>mapping(address=>bool)) public allowedAddresses; the caller of the functions allowed addresses, so you can't approve an address for someone else

Get approved

Now, we need to see who is the owner of a token with a function called getApproved to fit on the standard. We already have a function used internally on our contract that looks like that, but we need, once again, to implement a new function to fit on the standard.

    function getApproved(uint256 _tokenId) external view returns (address){
        return ownership[_tokenId];
    }

Is approved for all

This function has a very similar use as the above, but this time, we will use the mapping of mapping we have implemented before, so when we call the function isApprovedForAll passing to it 2 arguments, first, the owner of the tokens, second, the address we want to see if it is authorized to manage the tokens of the owner, we will get a boolean return telling if the second address is authorized to manage the firsts tokens.

    function isApprovedForAll(address _owner, address _operator) external view returns (bool){
        return allowedAddresses[_owner][_operator];
    }

Allowing an approved address to manage the tokens

Now, we need to refactor the code to allow for an approved address to manage the funds from an address that allowed him to manage.

The first function we will refactor is the one to change the price we want for a token, we will change the require so that it allows the allowed address to manage the token value

    function changeValue(uint _id, uint newValue) public{
        require(ownership[_id]==msg.sender||allowedAddresses[ownership[_id]][msg.sender]);
        tokens[_id].value=newValue;
    }

So
ownership[_id]==msg.sender if the caller is the owner or
allowedAddresses[ownership[_id]][msg.sender] the caller is allowed by the owner
tokens[_id].value=newValue; the price is updated

The || stands for "or", so you can guess the piece of code allowedAddresses[ownership[_id]][msg.sender] is the key for easily refactoring all the other transfer functions implemented before, like in

function transferFrom(address _from, address _to, uint256 _id) external payable{
        if (msg.sender==tokens[_id].owner||allowedAddresses[ownership[_id]][msg.sender]){
            uniqueTokens[msg.sender]+=1;
            uniqueTokens[tokens[_id].owner]-=1;
            tokens[_id].owner = msg.sender;
            ownership[_id] = msg.sender;
        }else{
            require(msg.value>=tokens[_id].value);
            balance[tokens[_id].owner]+=msg.value;
            uniqueTokens[msg.sender]+=1;
            uniqueTokens[tokens[_id].owner]-=1;
            tokens[_id].owner = msg.sender;
            ownership[_id] = msg.sender;
        }
    }

The events

The last thing we need to do now is to implement the events when a token transfer, creation or approval happens.

On the approve function, before the change of ownership, I have added the call for the event

emit Approval(ownership[_tokenId], _approved, _tokenId);

On the setApprovedForAll, the call for the event

emit ApprovalForAll(msg.sender, _operator, _approved);

On the createToken, before the currentId variable is automatically changed, I am calling the event

emit Transfer(msg.sender, msg.sender, currentId);

on the buyToken function, before the change of ownership

emit Transfer(tokens[_id].owner, msg.sender, _id);

And on all the transfer functions

emit Transfer(_from, _to, _id);

How the code looks

This is the end code, totally compliant with the ERC721 standard and with a few security features implemented.
Please, note that if you want to implement this in a real production environment I recommend you to study and implement your own security measures, ad the security features I have implemented are superficial and obvious, nothing fancy, and a real hacker that really wants to exploit your code would exploit more advanced techniques than the ones I have covered on my tutorial series

The code is too big and would look ugly pasted here, but you can read the full implementation here
https://github.com/igormuba/EthereumSolidityClasses/blob/master/class16/erc721.sol

Curriculum

Beneficiaries

This post has as beneficiaries
@utopian.pay with 5%
using the SteemPeak beneficiary tool
image.png

Sort:  

Thank you for your contribution. Another beautiful and useful piece of work.
The only suggestion I have is pertaining showing some outcome of implementing and running those functions, which would be pretty helpful to your readers.

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Chat with us on Discord.

[utopian-moderator]

Thank you for your review, @mcfarhat! Keep up the good work!

Hi @igormuba!

Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server

Hey, @igormuba!

Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Coin Marketplace

STEEM 0.20
TRX 0.15
JST 0.029
BTC 63362.14
ETH 2592.64
USDT 1.00
SBD 2.80