Assignment - Safetransfer Implementation

function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory _data) public {
    require(_to != address(0), "Reciever must not be dead address!");
    require(_isApprovedOrOwner(_from, _tokenId), "Must be owner or an approved operator to transfer the NFT!");

    _safeTransfer(_from, _to, _tokenId, _data);
  }
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external {
    safeTransferFrom(_from, _to, _tokenId, "");
}
3 Likes

Hi, this is my code:

...
bytes4 private constant MAGIC_VALUE =
        bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));

...

Function: safeTransferFrom with data parameter

    function safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId,
        bytes calldata data
    ) external payable transferFromCheck(_from, _to, _tokenId) {
        _safeTransfer(_from, _to, _tokenId, data);
    }

Function: safeTransferFrom without data parameter

    function safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) external payable transferFromCheck(_from, _to, _tokenId) {
        _safeTransfer(_from, _to, _tokenId, "");
    }

Modifier: that checks all the requirements before transfering a token from an owner

modifier transferFromCheck(
        address _from,
        address _to,
        uint256 _tokenId
    ) {
        require(
            _to != address(0),
            "Kitties: Can't transfer token to address 0"
        );
        require(
            _owns(msg.sender, _tokenId) ||
                _isApprovedForThis(msg.sender, _tokenId) ||
                isApprovedForAll(_from, msg.sender),
            "Kitties: You are not authorized to transfer this token"
        );
        require(
            _owns(_from, _tokenId),
            "Kitties: _from is not the owner of the token"
        );
        require(_tokenId < kitties.length, "Kitties: Token id is not valid");

        _;
    }

The other helper functions

    function _safeTransfer(
        address _from,
        address _to,
        uint256 _tokenId,
        bytes memory data
    ) internal {
        _transfer(_from, _to, _tokenId);
        require(_checkERC721Support(msg.sender, _from, _to, _tokenId, data));
    }

    function _checkERC721Support(
        address _operator,
        address _from,
        address _to,
        uint256 _tokenId,
        bytes memory _data
    ) private returns (bool) {
        if (!_isContract(_to)) {
            return true;
        }
        bytes4 returnedValue = ERC721TokenReceiver(_to).onERC721Received(
            _operator,
            _from,
            _tokenId,
            _data
        );

        return returnedValue == MAGIC_VALUE;
    }

    function _isContract(address _to) private view returns (bool) {
        uint256 size;
        assembly {
            size := extcodesize(_to)
        }
        return size > 0;
    }

    function _transfer(
        address from_,
        address to_,
        uint256 tokenId_
    ) internal {
        ownershipTokenCount[to_]++;

        if (from_ != address(0)) {
            ownershipTokenCount[from_]--;
            delete kittyIndexToApproved[tokenId_];
        }

        kittyIndexToOwner[tokenId_] = to_;
        emit Transfer(from_, to_, tokenId_);
    }

    function _owns(address claimant_, uint256 tokenId_)
        internal
        view
        returns (bool)
    {
        return kittyIndexToOwner[tokenId_] == claimant_;
    }
2 Likes

My code: Kittycontract.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.5.12;

import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./Ownable.sol";

contract Kittycontract is IERC721, Ownable {
  string public constant Name = "DemoKitties";
  string public constant Symbol = "DK";
  uint256 public constant CREATE_LIMIT_GEN0 = 10;
  
  bytes4 internal constant MAGIC_ERC721_RECEIVED = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));

  event Birth(address owner, uint256 kittenId, uint256 mumId, uint256 dadId, uint256 genes, uint256 generation, uint256 birthTime);

  struct Kitty {
    uint256 genes;
    uint64 birthTime;
    uint32 mumId;
    uint32 dadId;
    uint16 generation;
  }

  Kitty[] private allKitties; // index => kitty id (token id)

  mapping (address => uint256) ownerTokenBalance; // get uint256 amount (token kitty count) of owner address
  mapping (uint256 => address) public kittyIndexToOwner; // get owner address of uint256 kitty id (token id)
  mapping (uint256 => address) public kittyIndexToApproved; // address which is approved (to tx it) for a kitty id

  // MYADDRESS => OPERATORADDRESS => True|False : give an addess tx rights of all my Kitties 
  // Ex.: _operatorApprovals[MYADDRESS][OPERATORADDRESS] -> True|False 
  mapping (address => mapping (address => bool)) private _operatorApprovals; 

  uint256 public gen0Counter;

  // ==============

  function createKittenGen0(uint256 _genes) public onlyOwner {
    require(gen0Counter < CREATE_LIMIT_GEN0, "Max number of Generation0 kittens reached");
    gen0Counter++;

    _createKitty(0, 0, 0, _genes, msg.sender); // no parents, owned by the contract
  }

  function _createKitty(
    uint256 _mumId, uint256 _dadId,
    uint256 _generation, uint256 _genes,
    address _owner
  ) private returns (uint256) { // cat Id
    uint64 _now = uint64(now);
    Kitty memory _kitty = Kitty({
      genes: _genes, birthTime: _now,
      mumId: uint32(_mumId), dadId: uint32(_dadId),
      generation: uint16(_generation)
    });

    uint256 newKittenId = allKitties.push(_kitty) -1; // zero based Id

    emit Birth(_owner, newKittenId, _mumId, _dadId, _genes, _generation, uint256(_now));

    _transfer(address(0), _owner, newKittenId); // address(0): birth of new kitten to owner

    return newKittenId;
  }

  function getKitty(uint256 _id) external view returns(
    uint256 genes, uint256 birthTime,
    uint256 mumId, uint256 dadId,
    uint256 generation
  ) {
    Kitty storage kitty = allKitties[_id]; // storage is a pointer to the original mapping, memory creates a local copy

    genes = kitty.genes;
    birthTime = uint256(kitty.birthTime);
    mumId = uint256(kitty.mumId);
    dadId = uint256(kitty.dadId);
    generation = uint256(kitty.generation);
  }

  // =================

  function balanceOf(address _owner) external view returns (uint256) {
    return ownerTokenBalance[_owner];
  }

  function totalSupply() public view returns (uint256) {
    return allKitties.length;
  }

  function name() public view returns (string memory) {
    return Name;
  }

  function symbol() public view returns (string memory tokenSymbol) {  
    return Symbol;
  }

  function ownerOf(uint256 _tokenId) external view returns (address) {
    address owner = kittyIndexToOwner[_tokenId];
    require(owner != address(0), "No owner of not existing address");
    return owner;
  }

  function transfer(address _to, uint256 _tokenId) external {
    require(_to != address(0), "TO address must be defined.");
    require(_to != address(this), "Cannot transfer to the contract itself");
    require(_to != msg.sender, "Cannot send to yourselves");
    require(_owns(msg.sender, _tokenId), "Cannot send token you not own");

    _transfer(msg.sender, _to, _tokenId);
  }

  function _transfer(address _from, address _to, uint256 _tokenId) internal {
    ownerTokenBalance[_to]++;
    if (_from != address(0)) {
      ownerTokenBalance[_from]--;
      delete kittyIndexToApproved[_tokenId]; // also delete current approved addresses when transferred
    }
    kittyIndexToOwner[_tokenId] = _to;

    emit Transfer(_from, _to, _tokenId);
  }

  function _owns(address claimant, uint256 tokenId) internal view returns (bool) {
    return kittyIndexToOwner[tokenId] == claimant;
  }

  // ---- done for 2nd IERC721.sol.

  function approve(address _toApprove, uint256 _tokenId) external {
    require(_owns(msg.sender, _tokenId), "Sender must be owner of token Id");
    require( ! _owns(_toApprove, _tokenId), "Address already is approved");

    _approve(_tokenId, _toApprove);
    emit Approval(msg.sender, _toApprove, _tokenId);
  }
  
  function setApprovalForAll(address _toAddOperator, bool _approved) external {
    require(_toAddOperator != msg.sender, "Cannot add myself (owner) again");

    _operatorApprovals[msg.sender][_toAddOperator] = _approved;
    emit ApprovalForAll(msg.sender, _toAddOperator, _approved);
  }
  
  function getApproved(uint256 _tokenId) external view returns (address) {
    require(_tokenId < totalSupply(), "token Id must exist");

    return kittyIndexToApproved[_tokenId]; // address(0) if no kitties
  }
  
  function isApprovedForAll(address _owner, address _operator) public view returns (bool) {
    return _operatorApprovals[_owner][_operator]; // true if _operator is approved for _owner
  }

  function _approve(uint256 _tokenId, address _approved) internal {
    kittyIndexToApproved[_tokenId] = _approved;
  }

  // done for 3th IERC721.sol.

  function safeTransferFrom(address _from, address _to, uint256 _tokenId) external {
    safeTransferFrom(_from, _to, _tokenId, "");
  }

  function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory _data) public {
    require( _isApprovedOrOwner(msg.sender, _from, _to, _tokenId), "Must be approved or the owner of token Id");
    _safeTransfer(_from, _to, _tokenId, _data);
  }

  function _safeTransfer(address _from, address _to, uint256 _tokenId, bytes memory _data) internal {
    _transfer(_from, _to, _tokenId);
    require( _checkERC721Support(_from, _to, _tokenId, _data) );
  }

  function transferFrom(address _from, address _to, uint256 _tokenId) external {
    require( _isApprovedOrOwner(msg.sender, _from, _to, _tokenId), "Must be approved or the owner of token Id");
    /* replaces:
    require(_owns(_from, _tokenId), "Must be owner the token Id");
    require(_owns(msg.sender, _tokenId)
          || _approvedFor(msg.sender, _tokenId)
          || isApprovedForAll( _from, msg.sender),
          "Must be owner, be operator or approved of the token Id");
    require(_to != address(0), "Cannot transfer to zero address");
    require(_tokenId < totalSupply(), "token Id must exist");
    */

    _transfer(_from, _to, _tokenId);
  }

  function _approvedFor(address _claimant, uint256 _tokenId) internal view returns (bool) {
    return kittyIndexToApproved[_tokenId] == _claimant; // is claimant approved for token Id?
  }

  function _checkERC721Support(address _from, address _to, uint256 _tokenId, bytes memory _data) internal returns (bool) {
    if( !_isContract(_to) ) {
      return true;
    }

    // call onERC721Received in _to contract and check returned value
    bytes4 returnData = IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data);
    return returnData == MAGIC_ERC721_RECEIVED;
  }

  function _isContract(address _to) internal view returns (bool) {
    uint32 size;
    assembly{
      size := extcodesize(_to) // get _to address contract size
    }
    return size > 0;
  }

  function _isApprovedOrOwner(address _spender, address _from, address _to, uint256 _tokenId) internal view returns (bool) {
    require(_owns(_from, _tokenId), "Must be owner the token Id");
    require(_to != address(0), "Cannot transfer to zero address");
    require(_tokenId < totalSupply(), "token Id must exist");

    // spender is from || spender is approved for token Id || spender is operator for from
    return (_spender == _from
          || _approvedFor(_spender, _tokenId)
          || isApprovedForAll( _from, _spender) );
  }
}

IERC721Receiver.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.5.12;

interface IERC721Receiver {
  function onERC721Received(address operator, address from, uint tokenId, bytes calldata data) external returns (bytes4);
}
2 Likes

CatContract.sol:

//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "./IERC721.sol";
import "./IERC721Reciever.sol";
import "./Ownable.sol";

abstract contract CatContract is IERC721, IERC721Reciever, Ownable {

    uint256 public constant CREATION_LIMIT_GEN0 = 5; //this provides us scarcity for the gen0 cats, we don't want to just give everyone the ability to create gen0 cats willy nilly.
    string public constant _name = "ZKitties";
    string public constant _symbol = "ZK";

    bytes4 internal constant MAGIC_ERC721_RECIEVED = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")); //in EIP721

    /* 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);
    */
    event Birth(
        address owner,
        uint256 kittenId, 
        uint256 momId, 
        uint256 dadId, 
        uint256 genes
    );
    
    struct Kitty {
        uint256 genes;
        uint64 birthTime;
        uint32 fatherID;
        uint32 motherID;
        uint16 generation;
    }

    Kitty [] kitties; //array

    mapping(uint256 => address) public kittyIndexToOwner;
    mapping(address => uint256) ownerTokenCount;

    mapping(uint256 => address) public kittyIndexToApproved; //kitty id points to address that has been granted permission to transfer kitty id somewhere else on behalf of kitty id owner
    //getApproval and setApproval

    //MYADDRESS => OPPERATORADDRESS => TRUE/FALSE
    mapping(address => mapping(address => bool)) private _operatorApprovals;
    //setApprovalForAll and getApprovalForAll functions

    uint256 public gen0Counter;

    function createKittyGen0(uint256 _genes) public onlyOwner returns (uint256) {
        require(gen0Counter < CREATION_LIMIT_GEN0);

        gen0Counter++;
        
        return _createKitty(0,0,0, _genes, msg.sender);
    }

    function _createKitty(
        uint256 _momId,
        uint256 _dadId,
        uint256 _generation,
        uint256 _genes,
        address _owner
    ) private returns (uint256) {
        Kitty memory _kitty = Kitty({
            genes: uint256(_genes),
            birthTime: uint64(block.timestamp),
            motherID: uint32(_momId),
            fatherID: uint32(_dadId),
            generation: uint16(_generation)
        });
        kitties.push(_kitty); //adds an element to the array, will return the size of the array

        uint256 newKittenId = kitties.length - 1; //Will continue to count upwards from 0 being the first in the array.

        emit Birth(_owner, newKittenId, _momId, _dadId, _genes); //new birth event

        _transfer(address(0), _owner, newKittenId); //the new kitten will be transferred to the owner from address 0.  This is the birth of the cat

        return newKittenId;

    }

    function getKitty(uint256 id) external view returns (
        uint256 birthTime,
        uint256 motherID,
        uint256 fatherID,
        uint256 generation,
        uint256 genes
        ){
        Kitty storage kitty = kitties[id]; //save as a pointer

        birthTime = uint256(kitty.birthTime);
        motherID = uint256(kitty.motherID);
        fatherID = uint256(kitty.fatherID);
        generation = uint256(kitty.generation);
        genes = kitty.genes;
    }
    

    function balanceOf(address owner) external override view returns (uint256 balance){
        return ownerTokenCount[owner];
    }

    function totalSupply() external override view returns (uint256 total) {
        return kitties.length; //length of the array of cats
    }

    
    function name() external override pure returns (string memory) {
        return _name;
    }

    function symbol() external override pure returns (string memory) {
        return _symbol;
    }
 

    function ownerOf(uint256 _tokenId) external override view returns (address) { //external functions tend to be cheaper gas
        return kittyIndexToOwner[_tokenId];
    }

    function transfer(address _to, uint256 _tokenId) external override { //only sends from msg.sender to a recipient
        require(_to != address(0), "Cannot send to this address");
        require(_to != address(this), "Cannot send to this contract");
        require(_owns(msg.sender,_tokenId), "Owner is msg.sender");

        emit Transfer(msg.sender, _to, _tokenId);
    }

    function _transfer(address _from, address _to, uint256 _tokenId) internal {
        ownerTokenCount[_to]++; //increase token count of the recipient

        kittyIndexToOwner[_tokenId] = _to; //set ownership of kittyIndexToOwner so that the owner of _tokenId is _to

        if(_from != address(0)) {
            ownerTokenCount[_from]--; //if the from address isn't the 0 address we subtract the senders token count
            delete kittyIndexToApproved[_tokenId];
        }
        //emit the transfer event
        emit Transfer(_from, _to, _tokenId); //ERC721 Standard

    }

    function _owns(address _claimant, uint256 _tokenId) internal view returns(bool) {
        return kittyIndexToOwner[_tokenId] == _claimant;
    }
    function _approvedFor(address _claimant, uint256 _tokenId) internal view returns(bool) {
        return kittyIndexToApproved[_tokenId] == _claimant;
    }

    /*function _approve(uint256 _tokenId, address _approved) internal {
        return kittyIndexToApproved[_tokenId] = _approved;
    }
    */
    /// @notice Change or reaffirm the approved address for an NFT
    /// @dev The zero address indicates there is no approved address.
    ///  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 override { //one address will be able to approve for a tokenId
        require(_owns(msg.sender, _tokenId), "Msg.sender owns the tokenId");
        
        kittyIndexToApproved[_tokenId] = _approved;
        //_approve(_tokenId, _to); include if implementing the _approve function.

        emit Approval(msg.sender, _approved, _tokenId);
    }
    /// @notice Enable or disable approval for a third party ("operator") to manage
    ///  all of `msg.sender`'s assets
    /// @dev Emits the ApprovalForAll event. The contract MUST allow
    ///  multiple operators per owner.
    /// @param _operator Address to add to the set of authorized operators
    /// @param _approved True if the operator is approved, false to revoke approval
    function setApprovalForAll(address _operator, bool _approved) external override {
        require(msg.sender != _operator);
        
        _operatorApprovals[msg.sender][_operator] = _approved;

        emit ApprovalForAll(owner, _operator, _approved);
    }

    /// @notice Get the approved address for a single NFT
    /// @dev Throws if `_tokenId` is not a valid NFT.
    /// @param _tokenId The NFT to find the approved address for
    /// @return The approved address for this NFT, or the zero address if there is none
    function getApproved(uint256 _tokenId, address _approved) external view returns (address) {
       require(_tokenId > kitties.length);
       kittyIndexToApproved[_tokenId];
       return _approved;
    }

    /// @notice Query if an address is an authorized operator for another address
    /// @param _owner The address that owns the NFTs
    /// @param _operator The address that acts on behalf of the owner
    /// @return True if `_operator` is an approved operator for `_owner`, false otherwise
    function isApprovedForAll(address _owner, address _operator) public view override returns (bool) {
        return _operatorApprovals[_owner][_operator];
    }
    /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
    ///  TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
    ///  THEY MAY BE PERMANENTLY LOST
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function transferFrom(address _from, address _to, uint256 _tokenId) external override {
        require(msg.sender == _from || _approvedFor(msg.sender,_tokenId) ||  isApprovedForAll(_from, msg.sender), "msg.sender is current owner, or the authorized operator, or the approved address for this NFT");
        require(_owns(_from,_tokenId), "_from is not the current owner");
        require(_to != address(0), "_to is the zero address");
        require(_tokenId > kitties.length, "_tokenId is not a valid NFT");
        //Implement transfer function
        _transfer(_from,_to,_tokenId);
    }
    
    function _safeTransfer(address _from, address _to, uint256 _tokenId, bytes memory _data) internal {
        _transfer(_from,_to,_tokenId);
        require(_checkERC721Support(_from, _to, _tokenId, _data) ); //_checkERC721Support function needs to return as true, if it does not - it should throw an error
    }
    function _checkERC721Support(address _from, address _to, uint256 _tokenId, bytes memory _data) internal returns (bool) {
        if( !_isContract(_to) ) {
            return true;
        }
        //Call _onERC721Recieved in the _to contract
        bytes4 returnData = IERC721Reciever(_from).onERC721Recieved(msg.sender, _from, _tokenId, _data);
        //Check return value
        return returnData == MAGIC_ERC721_RECIEVED;
    }
    function _isContract(address _to) internal view returns (bool) {
        uint32 size;
        assembly {
            size := extcodesize(_to)
        }
        return size > 0;
    }

    /// @notice Transfers the ownership of an NFT from one address to another address
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT. When transfer is complete, this function
    ///  checks if `_to` is a smart contract (code size > 0). If so, it calls
    ///  `onERC721Received` on `_to` and throws if the return value is not
    ///  `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    /// @param data Additional data with no specified format, sent in call to `_to`
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external {
        require(msg.sender == _from || _approvedFor(msg.sender,_tokenId) ||  isApprovedForAll(_from, msg.sender), "msg.sender is current owner, or the authorized operator, or the approved address for this NFT");
        require(_owns(_from,_tokenId), "_from is not the current owner");
        require(_to != address(0), "_to is the zero address");
        require(_tokenId > kitties.length, "_tokenId is not a valid NFT");

        _safeTransfer(_from, _to, _tokenId, data);
    }

    /// @notice Transfers the ownership of an NFT from one address to another address
    /// @dev This works identically to the other function with an extra data parameter,
    ///  except this function just sets data to "".
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external {
        _safeTransfer(_from, _to, _tokenId, "");
    }
}

IERCReciever.sol:

pragma solidity ^0.8.0;

interface IERC721Reciever {
    function onERC721Recieved(address operator, address from, uint tokenId, bytes calldata data) external returns (bytes4);
}
2 Likes

Kittycontract.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

import "./IERC721.sol";
import "./IERC721Receiver";
import "./Ownable.sol";

contract Kittycontract is IERC721, Ownable {

    uint256 public constant CREATION_LIMIT_GEN0 = 10;
    string public constant name = "KittyCripto";
    string public constant symbol = "KC";

    bytes4 internal constant MAGIC_ERC721_RECEIVED = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));

    event Birth(
    address owner, 
    uint256 kittenId,
    uint256 mumId, 
    uint256 dadId, 
    uint256 genes
    );
}

    struct Kitty {
        uint256 genes;
        uint64 birthTime;
        uint32 mumId;
        uint32 dadId;
        uint16 generation;
    }

   //Arrays of token Ids
   Kitty[] kitties;

    mapping (uint256 => address) public kittyIndexToOwner;
    mapping (address => uint256) ownershipTokenCount;

    mapping (uint256 => address) public kittyIndexApproved;
    mapping (address => mapping (address => bool)) private _operatorApprovals;

    uint256 public gen0Counter;

    function _safetransfer (address _from, address _to, uint256 _tokenId, bytes memory _data) internal {
        _transfer(_from, _to, _tokenId);
        require(_checkERC721Support(_from, _to, _tokenId, _data));

    } 

    function transferFrom(address _from, address _to, uint256 _tokenId) public {
        require(_to != address(0));
        require(msg.sender == _from || _approvedFor(msg.sender, _tokenId) || isApprovedForAll(_from, msg.sender));
        require(_owns(_from, _tokenId));
        require(tokenId < Kitties.length);

        _transfer(_from, _to, _tokenId);

    }

    function approve(address _to, uint256 _tokenId) public {
        require(_owns(msg.sender, _tokenId));

        _approve(_tokenId, _to);
        emit Approval(msg.sender, _to, _tokenId);
    }

    function setApprovalForAll(address operator, bool approved) public {
        require(operator != msg.sender);

        _operatorApprovals[msg.sender] [operator] = approved;
        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function getApproved(uint256 tokenId) public view returns (address) {
        require(tokenId < kitties.length); //token must exist 

        return kittyIndexApproved[tokenId];

    }

    function isApprovedForAll(address owner, address operator) public view returns (bool) {
        return _operatorApprovals[owner] [operator];
    }

    function createKittyGen0(uint256 _genes) public onlyOwner returns (uint256) {
        require(gen0Counter < CREATION_LIMIT_GEN0);

        gen0Counter++;
        
        return _createKitty(0, 0, 0, _genes, msg.sender);
    }

    function _createKitty(
        uint256 _mumId,
        uint256 _dadId,
        uint256 _generation,
        uint256 _genes,
        address owner 
        
    ) private returns (uint256) {
        Kitty memory _kitty = Kitty({ 
            genes: uint64(_genes),
            birthTime: uint64(block.timestamp),
            mumId: uint32(_mumId),
            dadId: uint32(_dadId),
            generation: uint16(_generation)
        });
     
        kitties.push(_kitty);
        uint256 newKittenId = kitties.length -1; 

        emit Birth(owner, newKittenId, _mumId, _dadId, _genes);
 
        _transfer(address(0), owner, newKittenId);
    
        return newKittenId; 
    }
    function getKitty(uint256 kittyId) public view returns(uint256, uint64, uint32, uint32, uint16) {
        return 
        (
        kitties[kittyId].genes, 
        kitties[kittyId].birthTime, 
        kitties[kittyId].mumId, 
        kitties[kittyId].dadId,
        kitties[kittyId].generation
        );
    }
    function balanceOf(address owner) external  override view returns (uint256 balance) {
    return ownershipTokenCount[owner];
        
    }

    function totalSupply() external view returns (uint) {
        return kitties.length;
    }
  

    function ownerOf(uint256 _tokenId) external view returns (address) {
        require(kittyIndexToOwner[_tokenId] != address(0), "ownerOf: zero address");
           return kittyIndexToOwner[_tokenId];

      
    }

    function transfer(address _to, uint256 _tokenId) external override {
    

        require (_to != address(0));
        require (_to != address(this));
        require (_owns(msg.sender,_tokenId));

        _transfer(msg.sender, _to, _tokenId);
    }
    function _transfer(address _from, address _to, uint256 _tokenId) internal {
        ownershipTokenCount[_to]++;
    
    kittyIndexToOwner[_tokenId] = _to;

    if (_from != address (0)) {
        ownershipTokenCount[_from]--;
        delete kittyIndexApproved[_tokenId];
    }

    //Emit the transfer events
    emit Transfer(_from, _to, _tokenId); 

    }

    function _owns(address _claimant, uint256 _tokenId) internal view returns (bool) {
        return kittyIndexToOwner[_tokenId] == _claimant;
    }
    
    function _approve(uint256 _tokenId, address _approved) internal {
        kittyIndexToApproved[_tokenId] = _approved;
    }
    function _approvedFor(address _claimnat, uint256 _tokenId) internal view returns (bool) {
        return kittyIndexToApproved[_tokenId] == _claimant;
    }
    function _checkERC721Support(address _from, address _to, uint256 _tokenId, bytes memory _data) internal returns (bool) {
        if (!_isContract(_to)){
            return true;
        }
        
        //Call onERC721Received in the _to contract
        bytes4 returnData =IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data);
        return returnData == MAGIC_ERC721_RECEIVED;

        //check return value

    }
    function _isContract(address _to) view internal returns (bool) {
        uint32 size;
        assembly{
            size := extcodesize(_to)
        }
        return size > 0;

    }
}

IERC721Receiver.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

interface IERC721Receiver {
    function onERC721Received(address operator, address from, uint tokenId, bytes calldata data) external returns (bytes4);

    }
2 Likes

nice work. to make your code more readable you could abstract those requirs to a modifier to reduce duplicate coe and to tidy up the readability of your functions buisness logic

Kittycontract.sol

pragma solidity 0.8.0;
pragma abicoder v2;

import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./Ownable.sol";

contract Kittycontract is IERC721, Ownable {
    
    mapping(address => uint) OwnershipTokenCout;
    mapping(uint => address) public TokenIdOwner;
    mapping(uint => address) public kittyIndexToApproved;
    mapping(address => mapping(address => bool)) private _operatorApprovals;
    
    uint public constant Gen0SupplyLimit = 10;
    string public constant projectName = "CreativeKitties";
    string public constant projectSymbol = "CK";

    bytes4 internal constant MAGIC_ERC721_RECEIVED= bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));

    event Birth(address owner, uint kittenId, uint momId, uint dadId, uint genes);

    struct Kitty {
        uint genes;
        uint64 birthTime;
        uint32 momId;
        uint32 dadId;
        uint16 generation;
    }

    Kitty[] kitties;

    uint gen0Counter = 0;

    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external override {
        safeTransferFrom(_from, _to, _tokenId, "");
    }

    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory _data) public override {
        require( msg.sender == _from || isApprovedForAll(_from, msg.sender) || kittyIndexToApproved[_tokenId] == msg.sender);
        require(_owns(_from, _tokenId) && _to != address(0) && _tokenId < kitties.length);

        _safeTransfer(_from, _to, _tokenId, _data);
    }

    function _safeTransfer(address _from, address _to, uint _tokenId, bytes memory _data) internal {
        _transfer(_from, _to, _tokenId);
        require( _checkERC721Support(_from, _to, _tokenId, _data) );

    }

    function transferFrom(address _from, address _to, uint256 _tokenId) external override {
        require( msg.sender == _from || isApprovedForAll(_from, msg.sender) || kittyIndexToApproved[_tokenId] == msg.sender);
        require(_owns(_from, _tokenId) && _to != address(0) && _tokenId < kitties.length);
        
        _transfer(_from, _to, _tokenId);
    }
    
    function approve(address _approved, uint256 _tokenId) external override {
        require(_owns(msg.sender, _tokenId) || kittyIndexToApproved[_tokenId] == msg.sender, "Unable to set approval");

        kittyIndexToApproved[_tokenId] = _approved;
        emit Approval(msg.sender, _approved, _tokenId);
    }

    function setApprovalForAll(address _operator, bool _approved) external override {
        require(_operator != msg.sender);

        _operatorApprovals[msg.sender][_operator] = _approved;
        emit ApprovalForAll(msg.sender, _operator, _approved);
    }

    function getApproved(uint256 _tokenId) external override view returns (address) {
        require(_tokenId < kitties.length, "TokenID doesn't exist");

        return kittyIndexToApproved[_tokenId];
    }

    function isApprovedForAll(address _owner, address _operator) public override view returns (bool) {
        return _operatorApprovals[_owner][_operator];
    }

    function getKittyInfo(uint catId) external view returns (Kitty memory , address owner) {
        Kitty storage kitty = kitties[catId];
        return (kitty, TokenIdOwner[catId]);
    }

    function createKittyGen0(uint _genes) public onlyOwner returns (uint) {
        require(gen0Counter < Gen0SupplyLimit, "Gen 0 supply fully minted");
        gen0Counter++;
        return _createKitty(_genes, 0, 0, 0, msg.sender);

    }

    function _createKitty (uint _genes, uint _momId, uint _dadId, uint _generation, address _owner) private returns (uint) {
        Kitty memory _newKitty = Kitty({
            genes: _genes,
            birthTime: uint64(block.timestamp),
            momId: uint32(_momId),
            dadId: uint32(_dadId),
            generation: uint16(_generation)
        });
        kitties.push(_newKitty);
        uint newCatId = kitties.length - 1;

        emit Birth(_owner, newCatId, _momId, _dadId, _genes);

        _transfer(address(0), _owner, newCatId);
       
        return newCatId;
    }

    function balanceOf(address owner) external override view returns (uint256 balance) { 
        return OwnershipTokenCout[owner];
    }

    function totalSupply() external override view  returns (uint256 total) {
        return kitties.length;
    }

    function name() external override view returns (string memory tokenName) {
        return projectName;
    }
  
    function symbol() external override view returns (string memory tokenSymbol) {
        return projectSymbol;
    }

    function ownerOf(uint256 tokenId) external override view returns (address owner) {
        return TokenIdOwner[tokenId];
    }
  
    function transfer(address _to, uint256 _tokenId) external override {
        require(_to != address(0), 'Cant send to 0 address');
        require(_to != address(this), 'Cant send to this contract');
        require(_owns(msg.sender, _tokenId), 'You dont own this token'); 

        _transfer(msg.sender, _to, _tokenId);
    }

    function _transfer(address _from, address _to, uint _tokenId) internal {
        OwnershipTokenCout[_to] ++;
        TokenIdOwner[_tokenId] = _to;

        if (_from != address(0)){
            OwnershipTokenCout[_from] --;
            delete kittyIndexToApproved[_tokenId];
        }
        
        emit Transfer(_from, _to, _tokenId);
    }

    function _owns(address _claimant, uint _tokenId) internal view returns(bool) {
        return TokenIdOwner[_tokenId] == _claimant;
    }

    function _checkERC721Support(address _from, address _to, uint _tokenId, bytes memory _data) internal returns(bool) {
        if( !_isContract(_to)) {
            return true;
        }
        
        bytes4 returnData = IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data);
        return returnData == MAGIC_ERC721_RECEIVED;

    }

    function _isContract(address _to) internal view returns(bool) {
        uint32 size;
        assembly{
            size := extcodesize(_to)
        }
        return size > 0;
    }
    

}

IERC721Receiver.sol

pragma solidity 0.8.0;

interface IERC721Receiver {
    function onERC721Received(address operator, address from, uint tokenId, bytes calldata data) external returns (bytes4);
}
2 Likes

Here are those two functions

function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata _data) external{
        require(_owns(_from, _tokenId));
        require(_to != address(0));
        require(msg.sender == _from || _approvedFor(msg.sender, _tokenId) || isApprovedForAll(_from, msg.sender));
        require(_tokenId < kittens.length);

        _safeTransfer(_from, _to, _tokenId, _data);
    }

    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external{
        require(_owns(_from, _tokenId));
        require(_to != address(0));
        require(msg.sender == _from || _approvedFor(msg.sender, _tokenId) || isApprovedForAll(_from, msg.sender));
        require(_tokenId < kittens.length);

        _safeTransfer(_from, _to, _tokenId, "");
    }
1 Like
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) public {
        require(to != address(0), "Must not be sent to 0 address");
        require(msg.sender == from || isApprovedForAll(from, msg.sender) == true || getApproved(tokenId) == msg.sender, "Not authorized to send transaction");
        require(_owns(from, tokenId), "From-Address is not the token owner!");
        require(tokenId < bears.length, "tokenId does not exist");

        _safeTransfer(from, to, tokenId, data);
    }


    function safeTransferFrom(address from, address to, uint256 tokenId) public {
        require(to != address(0), "Must not be sent to 0 address");
        require(msg.sender == from || isApprovedForAll(from, msg.sender) == true || getApproved(tokenId) == msg.sender, "Not authorized to send transaction");
        require(_owns(from, tokenId), "From-Address is not the token owner!");
        require(tokenId < bears.length, "tokenId does not exist");

        _safeTransfer(from, to, tokenId, "");
    }
1 Like

Hello coders :grinning:

This is my attempt at implementing safeTransferFrom().

I hesitated encapsulating the redundant checks inside a private function because the compiler was forcing me to use ‘memory’ instead of ‘calldata’ storage for the data parameter. Then the data array would have been copied from calldata to memory (I assumed this would increase gas cost)

Extract from Kittycontract.sol
    function onERC721Received(address,address,uint256,bytes calldata) external pure returns (bytes4){
        return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
    }


    /// @notice Transfers the ownership of an NFT from one address to another address
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT. When transfer is complete, this function
    ///  checks if `_to` is a smart contract (code size > 0). If so, it calls
    ///  `onERC721Received` on `_to` and throws if the return value is not
    ///  `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    /// @param data Additional data with no specified format, sent in call to `_to`
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external{

        if (_to.code.length > 0 ){

            bytes4 result = ISupportsERC721(_to).onERC721Received(msg.sender, _from, _tokenId, data);

            bytes4 expectedResult = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));

            require(result == expectedResult, "_to address does not support ERC721 contract");
        }        

        _transferFrom(_from, _to, _tokenId);

    }

    /// @notice Transfers the ownership of an NFT from one address to another address
    /// @dev This works identically to the other function with an extra data parameter,
    ///  except this function just sets data to "".
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external{

        if (_to.code.length > 0 ){

            bytes4 result = ISupportsERC721(_to).onERC721Received(msg.sender, _from, _tokenId, "");

            bytes4 expectedResult = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));

            require(result == expectedResult, "_to address does not support ERC721 contract");
        }

        _transferFrom(_from, _to, _tokenId);

    }


    /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
    ///  TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
    ///  THEY MAY BE PERMANENTLY LOST
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function transferFrom(address _from, address _to, uint256 _tokenId) external{


        _transferFrom(_from, _to, _tokenId);
    }    

    function _transferFrom(address _from, address _to, uint256 _tokenId) private{
        //msg.sender must be the owner or an existing operator of tokenId
        require(_to != address(0), "invalid _to address");
        require(_tokenId < kitties.length, "Invalid token id");

        address tokenOwner = tokenowners[_tokenId];
        require(_from == tokenOwner, "_from should be the token owner");

        address approver = tokenApprovers[_tokenId];    
        require( (tokenOwner == msg.sender 
        || ownerApprovers[tokenOwner][msg.sender] == true
        || approver == msg.sender) , 'Caller is not owner of the token or an operator of the token owner or an approver of the token'); 

        _transfer(_from, _to, _tokenId);

    }

Unit tests
const Kitty = artifacts.require('Kittycontract');
const truffleAssert = require('truffle-assertions');

const NotERC721 = artifacts.require('Migrations');


contract("Test safeTansferFrom function", accounts =>{

    it("should throw if _to is a contract that does not support ERC721", async()=>{
        let kitty = await Kitty.deployed()

        await kitty.createKittyGen0(123)
        let tokenid = await kitty.totalSupply()-1

        let notERC721 = await NotERC721.deployed()

        await truffleAssert.reverts(
            kitty.safeTransferFrom(accounts[0], notERC721.address, tokenid)
        )


        await truffleAssert.reverts(
            kitty.safeTransferFrom(accounts[0], notERC721.address, tokenid, 0)
        )

    })

    it("should pass if _to is an EOA ", async()=>{
        let kitty = await Kitty.deployed()

        await kitty.createKittyGen0(123)
        let tokenid = await kitty.totalSupply()-1

        await truffleAssert.passes(
            kitty.safeTransferFrom(accounts[0], accounts[1], tokenid, 0)
        )

        await truffleAssert.passes(
            kitty.safeTransferFrom(accounts[1], accounts[2], tokenid, {from: accounts[1]})
        )

    })

    it("should pass if _to is a contract that supports ERC721 ", async()=>{
        let kitty = await Kitty.deployed()

        await kitty.createKittyGen0(123)
        let tokenid = await kitty.totalSupply()-1


        await truffleAssert.passes(
            kitty.safeTransferFrom(accounts[0], kitty.address, tokenid)
        )

        await kitty.createKittyGen0(123)
        tokenid = await kitty.totalSupply()-1

        await truffleAssert.passes(
            kitty.safeTransferFrom(accounts[0], kitty.address, tokenid, 0)
        )


    })



})


Github repo: https://github.com/CodingInLondon/moralisacademy-nftmarketplace

Cheers,
Matt

1 Like

Kittycontract.sol

/**
	* @dev Checks whether a transfer of a token is allowed, based on the specified parameters.
	* @param _from The address of the current owner of the token.
	* @param _to The address of the new owner of the token.
	* @param _tokenId The ID of the token to be transferred.
	*/
	function _checkTransfer(address _from, address _to, uint256 _tokenId) private view {
		require(_to != address(0), "Invalid address");
		require(msg.sender == _from || isApprovedFor(_to, _tokenId) || isApprovedForAll(_from, _to), "Transfert not allowed.");
		require(_owns(payable(_from), _tokenId));
		require(_tokenId < kitties.length, "Invalid token"); // token must exist
	}

	/**
	* @dev Transfers a token from one address to another.
	* @param _from The address of the current owner of the token.
	* @param _to The address of the new owner of the token.
	* @param _tokenId The ID of the token to be transferred.
	*/
	function transferFrom(address _from, address _to, uint256 _tokenId) external {
		_checkTransfer(_from, _to, _tokenId);
		_transfer(msg.sender, _to, _tokenId);
	}

	/**
	* @dev Safely transfers a token from one address to another.
	* @param _from The address of the current owner of the token.
	* @param _to The address of the new owner of the token.
	* @param _tokenId The ID of the token to be transferred.
	*/
	function safeTransferFrom(address _from, address _to, uint256 _tokenId) external {
		_checkTransfer(_from, _to, _tokenId);
		_safeTransfer(_from, _to, _tokenId, "");
	}

	/**
	* @dev Safely transfers a token from one address to another, with additional data.
	* @param _from The address of the current owner of the token.
	* @param _to The address of the new owner of the token.
	* @param _tokenId The ID of the token to be transferred.
	* @param _data Additional data to pass to the receiving contract.
	*/
	function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata _data) external {
		_checkTransfer(_from, _to, _tokenId);
		_safeTransfer(_from, _to, _tokenId, _data);
	}

	
	function _safeTransfer(address _from, address _to, uint256 _tokenId, bytes memory _data) internal {
		_transfer(_from, _to, _tokenId);

		require(_checkERC721Support(_from, _to, _tokenId, _data));
	}