Jumping into Solidity — The ERC721 Standard (Part 3)

“Birds flying around a half-built building and construction site” by 贝莉儿 NG on Unsplash

Design choices, scalability and security

mapping(uint256 => address) internal owners;
c = a + b 
c = a.add(b);

The Contract

pragma solidity ^0.4.22;import "./CheckERC165.sol";
import "./standard/ERC721.sol";
import "./standard/ERC721TokenReceiver.sol";
import "./libraries/SafeMath.sol";
contract TokenERC721 is ERC721, CheckERC165{
using SafeMath for uint256;
// The address of the contract creator
address internal
creator;
// The highest valid tokenId, for checking if a tokenId is valid
uint256 internal
maxId;
// A mapping storing the balance of each address
mapping
(address => uint256) internal balances;
// A mapping of burnt tokens, for checking if a tokenId is valid
// Not needed if your token can't be burnt
mapping
(uint256 => bool) internal burned;
// A mapping of token owners
mapping
(uint256 => address) internal owners;
// A mapping of the "approved" address for each token
mapping
(uint256 => address) internal allowance;
// A nested mapping for managing "operators"
mapping
(address => mapping (address => bool)) internal authorised;
constructor(uint _initialSupply) public CheckERC165(){
// Store the address of the creator
creator = msg.sender;

// All initial tokens belong to creator, so set the balance
balances[msg.sender] = _initialSupply;
// Set maxId to number of tokens
maxId = _initialSupply;
//Add to ERC165 Interface Check
supportedInterfaces[
this.balanceOf.selector ^
this.ownerOf.selector ^
bytes4(keccak256("safeTransferFrom(address,address,uint256"))^
bytes4(keccak256("safeTransferFrom(address,address,uint256,bytes"))^
this.transferFrom.selector ^
this.approve.selector ^
this.setApprovalForAll.selector ^
this.getApproved.selector ^
this.isApprovedForAll.selector
] = true;

}

“While some ERC-721 smart contracts may find it convenient to start with ID 0 and simply increment by one for each new NFT, callers SHALL NOT assume that ID numbers have any specific pattern to them, and MUST treat the ID as a ‘black box’.”

function isValidToken(uint256 _tokenId) internal view returns(bool){
return _tokenId != 0 && _tokenId <= maxId && !burned[_tokenId];
}
function balanceOf(address _owner) external view returns (uint256){
return balances[_owner];
}
function ownerOf(uint256 _tokenId) public view returns(address){
require(isValidToken(_tokenId));
if(owners[_tokenId] != 0x0 ){
return owners[_tokenId];
}else{
return creator;
}
}
function issueTokens(uint256 _extraTokens) public{ 
// Make sure only the contract creator can call this
require(msg.sender == creator);
balances[msg.sender] = balances[msg.sender].add(_extraTokens);

//We have to emit an event for each token that gets created
for(uint i = maxId.add(1); i <= maxId.add(_extraTokens); i++){
emit Transfer(0x0, creator, i);
}

maxId += _extraTokens; //<- SafeMath for this operation
// was done in for loop above
}
function burnToken(uint256 _tokenId) external{
address owner = ownerOf(_tokenId);
require ( owner == msg.sender
|| allowance[_tokenId] == msg.sender
|| authorised[owner][msg.sender]
);
burned[_tokenId] = true;
balances[owner]--;
emit Transfer(owner, 0x0, _tokenId);
}

Wrapping up

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store