(追記)
ERC1155に関してまとめた記事を書きましたので、リンクを貼っておきます。
Ethereum Advent Calendar1日目として投稿した記事です。
https://lab.stir.network/2018/11/08/design-of-a-gaming-worldview-that-erc1155-enables/
背景
Ethereumでは、独自通貨を簡単に作れるERC20や、個体値を持つキャラクターを定義できるERC721など、便利なトークン規格が次々と登場しています。
最近では、ERC20とERC721を組み合わせた、ERC1155というハイブリッドなトークン規格が提唱されており、今後はERC1155ベースのトークン実装も盛り上がってくるのではないかと予想しております。
そこで、ERC20 / ERC721 / ERC1155 のソースコードを整理して、各コントラクトについての理解を深めていきたいと思います。
ERC20
2018/8/3時点にて、OpenZeppelinのソースコードを元に記載しています。
ERC20.sol
ERC20トークンの抽象コントラクトです。2018/8/3のマージでERC20Basic
などのContractが削除され、継承のないシンプルな抽象コントラクトになりました。
contract ERC20 {
function totalSupply() public view returns (uint256);
function balanceOf(address _who) public view returns (uint256);
function allowance(address _owner, address _spender)
public view returns (uint256);
function transfer(address _to, uint256 _value) public returns (bool);
function approve(address _spender, uint256 _value)
public returns (bool);
function transferFrom(address _from, address _to, uint256 _value)
public returns (bool);
event Transfer(
address indexed from,
address indexed to,
uint256 value
);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
StandardToken.sol
ERC20のコントラクトを継承したものがStandardToken
です。ERC20で定義したfunctionはStandardToken
で実装されているので、独自の通貨を作成する場合、このStandardToken
を継承して作成するケースが多いです。
contract StandardToken is ERC20 {
using SafeMath for uint256;
mapping(address => uint256) balances;
mapping (address => mapping (address => uint256)) internal allowed;
uint256 totalSupply_;
function totalSupply() public view returns (uint256) {
return totalSupply_;
}
function balanceOf(address _owner) public view returns (uint256) {
return balances[_owner];
}
function allowance(
address _owner,
address _spender
)
public
view
returns (uint256)
{
return allowed[_owner][_spender];
}
function transfer(address _to, uint256 _value) public returns (bool) {
require(_value <= balances[msg.sender]);
require(_to != address(0));
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
emit Transfer(msg.sender, _to, _value);
return true;
}
function approve(address _spender, uint256 _value) public returns (bool) {
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function transferFrom(
address _from,
address _to,
uint256 _value
)
public
returns (bool)
{
require(_value <= balances[_from]);
require(_value <= allowed[_from][msg.sender]);
require(_to != address(0));
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
emit Transfer(_from, _to, _value);
return true;
}
function increaseApproval(
address _spender,
uint256 _addedValue
)
public
returns (bool)
{
allowed[msg.sender][_spender] = (
allowed[msg.sender][_spender].add(_addedValue));
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
function decreaseApproval(
address _spender,
uint256 _subtractedValue
)
public
returns (bool)
{
uint256 oldValue = allowed[msg.sender][_spender];
if (_subtractedValue >= oldValue) {
allowed[msg.sender][_spender] = 0;
} else {
allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
}
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
}
MintableToken.sol
新たなTokenを生み出すという意味で、mint
というfunctionが実装されています。MintableToken
は、ERC20を継承したStandardToken
をさらに継承しています。
contract MintableToken is StandardToken, Ownable {
event Mint(address indexed to, uint256 amount);
event MintFinished();
bool public mintingFinished = false;
modifier canMint() {
require(!mintingFinished);
_;
}
modifier hasMintPermission() {
require(msg.sender == owner);
_;
}
/**
* @dev Function to mint tokens
* @param _to The address that will receive the minted tokens.
* @param _amount The amount of tokens to mint.
* @return A boolean that indicates if the operation was successful.
*/
function mint(
address _to,
uint256 _amount
)
public
hasMintPermission
canMint
returns (bool)
{
totalSupply_ = totalSupply_.add(_amount);
balances[_to] = balances[_to].add(_amount);
emit Mint(_to, _amount);
emit Transfer(address(0), _to, _amount);
return true;
}
/**
* @dev Function to stop minting new tokens.
* @return True if the operation was successful.
*/
function finishMinting() public onlyOwner canMint returns (bool) {
mintingFinished = true;
emit MintFinished();
return true;
}
}
DetailedERC20.sol
このコントラクトを直接使用するケースは個人的にあまり知りませんが、上記StandardToken
を継承した独自通貨Contractは、DetailedERC20
で定義している変数とコンストラクタを記述することが多いです。
contract DetailedERC20 is ERC20 {
string public name;
string public symbol;
uint8 public decimals;
constructor(string _name, string _symbol, uint8 _decimals) public {
name = _name;
symbol = _symbol;
decimals = _decimals;
}
}
OMGToken.sol
例えば、ERC20を実装していることで有名なOmiseGoのトークン(OMGToken)は、StandardToken
を継承したMintableToken
を継承しており、DetailedERC20
の形式に倣ってname
(トークン名)などを定義してます。(コンストラクタで上書きされないようにしている所はDetailedERC20
と異なります。)
contract OMGToken is PausableToken, MintableToken {
using SafeMath for uint256;
string public name = "OMGToken";
string public symbol = "OMG";
uint public decimals = 18;
/**
* @dev mint timelocked tokens
*/
function mintTimelocked(address _to, uint256 _amount, uint256 _releaseTime)
onlyOwner canMint returns (TokenTimelock) {
TokenTimelock timelock = new TokenTimelock(this, _to, _releaseTime);
mint(timelock, _amount);
return timelock;
}
}
Contractの全体像も記載しておきます。
ERC721
ERC20同様、2018/8/3時点にて、OpenZeppelinのソースコードを元に記載しています。
ERC721Basic.sol
NFTとして知られる、ERC721トークンのベースとなる抽象コントラクトです。ERC165を継承しています。ERC165は、コントラクトがどんなインターフェースを実装しているのかを確認できるfunctionを1つだけ定義しています。
contract ERC721Basic is ERC165 {
bytes4 internal constant InterfaceId_ERC721 = 0x80ac58cd;
bytes4 internal constant InterfaceId_ERC721Exists = 0x4f558e79;
bytes4 internal constant InterfaceId_ERC721Enumerable = 0x780e9d63;
bytes4 internal constant InterfaceId_ERC721Metadata = 0x5b5e139f;
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
);
function balanceOf(address _owner) public view returns (uint256 _balance);
function ownerOf(uint256 _tokenId) public view returns (address _owner);
function exists(uint256 _tokenId) public view returns (bool _exists);
function approve(address _to, uint256 _tokenId) public;
function getApproved(uint256 _tokenId)
public view returns (address _operator);
function setApprovalForAll(address _operator, bool _approved) public;
function isApprovedForAll(address _owner, address _operator)
public view returns (bool);
function transferFrom(address _from, address _to, uint256 _tokenId) public;
function safeTransferFrom(address _from, address _to, uint256 _tokenId)
public;
function safeTransferFrom(
address _from,
address _to,
uint256 _tokenId,
bytes _data
)
public;
}
ERC721Enumberable.sol
ERC721Basic
を継承した抽象コントラクトです。
contract ERC721Enumerable is ERC721Basic {
function totalSupply() public view returns (uint256);
function tokenOfOwnerByIndex(
address _owner,
uint256 _index
)
public
view
returns (uint256 _tokenId);
function tokenByIndex(uint256 _index) public view returns (uint256);
}
ERC721Metadata.sol
ERC721Basic
を継承した抽象コントラクトです。name
やsymbol
の取得など、どのNFTでも必須となりうる情報を取得できるfunctionを定義しています。
contract ERC721Metadata is ERC721Basic {
function name() external view returns (string _name);
function symbol() external view returns (string _symbol);
function tokenURI(uint256 _tokenId) public view returns (string);
}
ERC721.sol
ERC721
は、ERC721Enumerable
、ERC721Metadata
に加えて、ERC721Basic
も継承しています。
※ERC721Basic
はいらないのでは?と思いますが、この辺りはERC20のアップデートに合わせて徐々にシンプルな形へ修正されていくかもしれません。
contract ERC721 is ERC721Basic, ERC721Enumerable, ERC721Metadata {
}
KittyOwnership.sol
参考までに、ERC721を実装しているCryptoKittiesのCoreContractを掲載します。
/// @title The facet of the CryptoKitties core contract that manages ownership, ERC-721 (draft) compliant.
/// @author Axiom Zen (https://www.axiomzen.co)
/// @dev Ref: https://github.com/ethereum/EIPs/issues/721
/// See the KittyCore contract documentation to understand how the various contract facets are arranged.
contract KittyOwnership is KittyBase, ERC721 {
/// @notice Name and symbol of the non fungible token, as defined in ERC721.
string public constant name = "CryptoKitties";
string public constant symbol = "CK";
// The contract that will return kitty metadata
ERC721Metadata public erc721Metadata;
bytes4 constant InterfaceSignature_ERC165 =
bytes4(keccak256('supportsInterface(bytes4)'));
bytes4 constant InterfaceSignature_ERC721 =
bytes4(keccak256('name()')) ^
bytes4(keccak256('symbol()')) ^
bytes4(keccak256('totalSupply()')) ^
bytes4(keccak256('balanceOf(address)')) ^
bytes4(keccak256('ownerOf(uint256)')) ^
bytes4(keccak256('approve(address,uint256)')) ^
bytes4(keccak256('transfer(address,uint256)')) ^
bytes4(keccak256('transferFrom(address,address,uint256)')) ^
bytes4(keccak256('tokensOfOwner(address)')) ^
bytes4(keccak256('tokenMetadata(uint256,string)'));
/// @notice Introspection interface as per ERC-165 (https://github.com/ethereum/EIPs/issues/165).
/// Returns true for any standardized interfaces implemented by this contract. We implement
/// ERC-165 (obviously!) and ERC-721.
function supportsInterface(bytes4 _interfaceID) external view returns (bool)
{
// DEBUG ONLY
//require((InterfaceSignature_ERC165 == 0x01ffc9a7) && (InterfaceSignature_ERC721 == 0x9a20483d));
return ((_interfaceID == InterfaceSignature_ERC165) || (_interfaceID == InterfaceSignature_ERC721));
}
/// @dev Set the address of the sibling contract that tracks metadata.
/// CEO only.
function setMetadataAddress(address _contractAddress) public onlyCEO {
erc721Metadata = ERC721Metadata(_contractAddress);
}
// Internal utility functions: These functions all assume that their input arguments
// are valid. We leave it to public methods to sanitize their inputs and follow
// the required logic.
/// @dev Checks if a given address is the current owner of a particular Kitty.
/// @param _claimant the address we are validating against.
/// @param _tokenId kitten id, only valid when > 0
function _owns(address _claimant, uint256 _tokenId) internal view returns (bool) {
return kittyIndexToOwner[_tokenId] == _claimant;
}
/// @dev Checks if a given address currently has transferApproval for a particular Kitty.
/// @param _claimant the address we are confirming kitten is approved for.
/// @param _tokenId kitten id, only valid when > 0
function _approvedFor(address _claimant, uint256 _tokenId) internal view returns (bool) {
return kittyIndexToApproved[_tokenId] == _claimant;
}
/// @dev Marks an address as being approved for transferFrom(), overwriting any previous
/// approval. Setting _approved to address(0) clears all transfer approval.
/// NOTE: _approve() does NOT send the Approval event. This is intentional because
/// _approve() and transferFrom() are used together for putting Kitties on auction, and
/// there is no value in spamming the log with Approval events in that case.
function _approve(uint256 _tokenId, address _approved) internal {
kittyIndexToApproved[_tokenId] = _approved;
}
/// @notice Returns the number of Kitties owned by a specific address.
/// @param _owner The owner address to check.
/// @dev Required for ERC-721 compliance
function balanceOf(address _owner) public view returns (uint256 count) {
return ownershipTokenCount[_owner];
}
/// @notice Transfers a Kitty to another address. If transferring to a smart
/// contract be VERY CAREFUL to ensure that it is aware of ERC-721 (or
/// CryptoKitties specifically) or your Kitty may be lost forever. Seriously.
/// @param _to The address of the recipient, can be a user or contract.
/// @param _tokenId The ID of the Kitty to transfer.
/// @dev Required for ERC-721 compliance.
function transfer(
address _to,
uint256 _tokenId
)
external
whenNotPaused
{
// Safety check to prevent against an unexpected 0x0 default.
require(_to != address(0));
// Disallow transfers to this contract to prevent accidental misuse.
// The contract should never own any kitties (except very briefly
// after a gen0 cat is created and before it goes on auction).
require(_to != address(this));
// Disallow transfers to the auction contracts to prevent accidental
// misuse. Auction contracts should only take ownership of kitties
// through the allow + transferFrom flow.
require(_to != address(saleAuction));
require(_to != address(siringAuction));
// You can only send your own cat.
require(_owns(msg.sender, _tokenId));
// Reassign ownership, clear pending approvals, emit Transfer event.
_transfer(msg.sender, _to, _tokenId);
}
/// @notice Grant another address the right to transfer a specific Kitty via
/// transferFrom(). This is the preferred flow for transfering NFTs to contracts.
/// @param _to The address to be granted transfer approval. Pass address(0) to
/// clear all approvals.
/// @param _tokenId The ID of the Kitty that can be transferred if this call succeeds.
/// @dev Required for ERC-721 compliance.
function approve(
address _to,
uint256 _tokenId
)
external
whenNotPaused
{
// Only an owner can grant transfer approval.
require(_owns(msg.sender, _tokenId));
// Register the approval (replacing any previous approval).
_approve(_tokenId, _to);
// Emit approval event.
Approval(msg.sender, _to, _tokenId);
}
/// @notice Transfer a Kitty owned by another address, for which the calling address
/// has previously been granted transfer approval by the owner.
/// @param _from The address that owns the Kitty to be transfered.
/// @param _to The address that should take ownership of the Kitty. Can be any address,
/// including the caller.
/// @param _tokenId The ID of the Kitty to be transferred.
/// @dev Required for ERC-721 compliance.
function transferFrom(
address _from,
address _to,
uint256 _tokenId
)
external
whenNotPaused
{
// Safety check to prevent against an unexpected 0x0 default.
require(_to != address(0));
// Disallow transfers to this contract to prevent accidental misuse.
// The contract should never own any kitties (except very briefly
// after a gen0 cat is created and before it goes on auction).
require(_to != address(this));
// Check for approval and valid ownership
require(_approvedFor(msg.sender, _tokenId));
require(_owns(_from, _tokenId));
// Reassign ownership (also clears pending approvals and emits Transfer event).
_transfer(_from, _to, _tokenId);
}
/// @notice Returns the total number of Kitties currently in existence.
/// @dev Required for ERC-721 compliance.
function totalSupply() public view returns (uint) {
return kitties.length - 1;
}
/// @notice Returns the address currently assigned ownership of a given Kitty.
/// @dev Required for ERC-721 compliance.
function ownerOf(uint256 _tokenId)
external
view
returns (address owner)
{
owner = kittyIndexToOwner[_tokenId];
require(owner != address(0));
}
/// @notice Returns a list of all Kitty IDs assigned to an address.
/// @param _owner The owner whose Kitties we are interested in.
/// @dev This method MUST NEVER be called by smart contract code. First, it's fairly
/// expensive (it walks the entire Kitty array looking for cats belonging to owner),
/// but it also returns a dynamic array, which is only supported for web3 calls, and
/// not contract-to-contract calls.
function tokensOfOwner(address _owner) external view returns(uint256[] ownerTokens) {
uint256 tokenCount = balanceOf(_owner);
if (tokenCount == 0) {
// Return an empty array
return new uint256[](0);
} else {
uint256[] memory result = new uint256[](tokenCount);
uint256 totalCats = totalSupply();
uint256 resultIndex = 0;
// We count on the fact that all cats have IDs starting at 1 and increasing
// sequentially up to the totalCat count.
uint256 catId;
for (catId = 1; catId <= totalCats; catId++) {
if (kittyIndexToOwner[catId] == _owner) {
result[resultIndex] = catId;
resultIndex++;
}
}
return result;
}
}
/// @dev Adapted from memcpy() by @arachnid (Nick Johnson <arachnid@notdot.net>)
/// This method is licenced under the Apache License.
/// Ref: https://github.com/Arachnid/solidity-stringutils/blob/2f6ca9accb48ae14c66f1437ec50ed19a0616f78/strings.sol
function _memcpy(uint _dest, uint _src, uint _len) private view {
// Copy word-length chunks while possible
for(; _len >= 32; _len -= 32) {
assembly {
mstore(_dest, mload(_src))
}
_dest += 32;
_src += 32;
}
// Copy remaining bytes
uint256 mask = 256 ** (32 - _len) - 1;
assembly {
let srcpart := and(mload(_src), not(mask))
let destpart := and(mload(_dest), mask)
mstore(_dest, or(destpart, srcpart))
}
}
/// @dev Adapted from toString(slice) by @arachnid (Nick Johnson <arachnid@notdot.net>)
/// This method is licenced under the Apache License.
/// Ref: https://github.com/Arachnid/solidity-stringutils/blob/2f6ca9accb48ae14c66f1437ec50ed19a0616f78/strings.sol
function _toString(bytes32[4] _rawBytes, uint256 _stringLength) private view returns (string) {
var outputString = new string(_stringLength);
uint256 outputPtr;
uint256 bytesPtr;
assembly {
outputPtr := add(outputString, 32)
bytesPtr := _rawBytes
}
_memcpy(outputPtr, bytesPtr, _stringLength);
return outputString;
}
/// @notice Returns a URI pointing to a metadata package for this token conforming to
/// ERC-721 (https://github.com/ethereum/EIPs/issues/721)
/// @param _tokenId The ID number of the Kitty whose metadata should be returned.
function tokenMetadata(uint256 _tokenId, string _preferredTransport) external view returns (string infoUrl) {
require(erc721Metadata != address(0));
bytes32[4] memory buffer;
uint256 count;
(buffer, count) = erc721Metadata.getMetadata(_tokenId, _preferredTransport);
return _toString(buffer, count);
}
}
CryptoKittiesの全体像です。役割ごとにContractを作成し、多重継承されていることがわかります。
ERC1155
ERC1155は、2018/8/3時点でOpenZeppelinのソースコードが存在せず、現在GithubのIssueにて仕様を作成中です。今後アップデートされる可能性は大きいですが、現時点でのソースコードを整理します。
IERC1155.sol
ERC1155の標準インタフェースです。
Approval
,Transfer
といったイベントや、transferFrom
やbalanceOf
などのfunctionが定義されており、ERC721の定義と同様のインタフェースとなっています。
interface IERC1155 {
event Approval(address indexed _owner, address indexed _spender, uint256 indexed _id, uint256 _oldValue, uint256 _value);
event Transfer(address _spender, address indexed _from, address indexed _to, uint256 indexed _id, uint256 _value);
function transferFrom(address _from, address _to, uint256 _id, uint256 _value) external;
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes _data) external;
function approve(address _spender, uint256 _id, uint256 _currentValue, uint256 _value) external;
function balanceOf(uint256 _id, address _owner) external view returns (uint256);
function allowance(uint256 _id, address _owner, address _spender) external view returns (uint256);
}
IERC1155Extended.sol
ERC1155の拡張インタフェースです。
ERC721に含まれているfunctionと同様の定義が、IERC1155.solとは別インタフェースとして定義されています。
これもERC721で定義されているものと同様です。
interface IERC1155Extended {
function transfer(address _to, uint256 _id, uint256 _value) external;
function safeTransfer(address _to, uint256 _id, uint256 _value, bytes _data) external;
}
IERC1155BatchTransfer.sol
ERC1155の同時トランザクション制御を定義するインタフェースです。
ERC1155では複数のtokenIdを同時に受け渡すことができますが、これを可能にしているのがIERC1155BatchTransfer
のfunctionです。
interface IERC1155BatchTransfer {
function batchTransferFrom(address _from, address _to, uint256[] _ids, uint256[] _values) external;
function safeBatchTransferFrom(address _from, address _to, uint256[] _ids, uint256[] _values, bytes _data) external;
function batchApprove(address _spender, uint256[] _ids, uint256[] _currentValues, uint256[] _values) external;
}
IERC1155BatchTransferExtended.sol
IERC1155BatchTransferの拡張です。Fromの指定がなくなった、batchTransfer
,safeBatchTransfer
が定義されています。
interface IERC1155BatchTransferExtended {
function batchTransfer(address _to, uint256[] _ids, uint256[] _values) external;
function safeBatchTransfer(address _to, uint256[] _ids, uint256[] _values, bytes _data) external;
}
ERC1155.sol
上記4つのインタフェースを実装したものがERC1155になります。
ERC20に該当する``symbol,
decimal`等はmappingとして定義されており、`name`や`balances`をまとめた`Items`という構造体も、mappingとして宣言されています。
contract ERC1155 is IERC1155, IERC1155Extended, IERC1155BatchTransfer, IERC1155BatchTransferExtended {
using SafeMath for uint256;
// Variables
struct Items {
string name;
uint256 totalSupply;
mapping (address => uint256) balances;
}
mapping (uint256 => uint8) public decimals;
mapping (uint256 => string) public symbols;
mapping (uint256 => mapping(address => mapping(address => uint256))) allowances;
mapping (uint256 => Items) public items;
mapping (uint256 => string) metadataURIs;
/////////////////////////////////////////// IERC1155 //////////////////////////////////////////////
// Events
event Approval(address indexed _owner, address indexed _spender, uint256 indexed _id, uint256 _oldValue, uint256 _value);
event Transfer(address _spender, address indexed _from, address indexed _to, uint256 indexed _id, uint256 _value);
function transferFrom(address _from, address _to, uint256 _id, uint256 _value) external {
if(_from != msg.sender) {
require(allowances[_id][_from][msg.sender] >= _value);
allowances[_id][_from][msg.sender] = allowances[_id][_from][msg.sender].sub(_value);
}
items[_id].balances[_from] = items[_id].balances[_from].sub(_value);
items[_id].balances[_to] = _value.add(items[_id].balances[_to]);
emit Transfer(msg.sender, _from, _to, _id, _value);
}
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes _data) external {
revert('TBD');
}
function approve(address _spender, uint256 _id, uint256 _currentValue, uint256 _value) external {
// if the allowance isn't 0, it can only be updated to 0 to prevent an allowance change immediately after withdrawal
require(_value == 0 || allowances[_id][msg.sender][_spender] == _currentValue);
allowances[_id][msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _id, _currentValue, _value);
}
function balanceOf(uint256 _id, address _owner) external view returns (uint256) {
return items[_id].balances[_owner];
}
function allowance(uint256 _id, address _owner, address _spender) external view returns (uint256) {
return allowances[_id][_owner][_spender];
}
/////////////////////////////////////// IERC1155Extended //////////////////////////////////////////
function transfer(address _to, uint256 _id, uint256 _value) external {
// Not needed. SafeMath will do the same check on .sub(_value)
//require(_value <= items[_id].balances[msg.sender]);
items[_id].balances[msg.sender] = items[_id].balances[msg.sender].sub(_value);
items[_id].balances[_to] = _value.add(items[_id].balances[_to]);
emit Transfer(msg.sender, msg.sender, _to, _id, _value);
}
function safeTransfer(address _to, uint256 _id, uint256 _value, bytes _data) external {
revert('TBD');
}
//////////////////////////////////// IERC1155BatchTransfer ////////////////////////////////////////
function batchTransferFrom(address _from, address _to, uint256[] _ids, uint256[] _values) external {
uint256 _id;
uint256 _value;
if(_from == msg.sender) {
for (uint256 i = 0; i < _ids.length; ++i) {
_id = _ids[i];
_value = _values[i];
items[_id].balances[_from] = items[_id].balances[_from].sub(_value);
items[_id].balances[_to] = _value.add(items[_id].balances[_to]);
emit Transfer(msg.sender, _from, _to, _id, _value);
}
}
else {
for (i = 0; i < _ids.length; ++i) {
_id = _ids[i];
_value = _values[i];
allowances[_id][_from][msg.sender] = allowances[_id][_from][msg.sender].sub(_value);
items[_id].balances[_from] = items[_id].balances[_from].sub(_value);
items[_id].balances[_to] = _value.add(items[_id].balances[_to]);
emit Transfer(msg.sender, _from, _to, _id, _value);
}
}
}
function safeBatchTransferFrom(address _from, address _to, uint256[] _ids, uint256[] _values, bytes _data) external {
revert('TBD');
}
function batchApprove(address _spender, uint256[] _ids, uint256[] _currentValues, uint256[] _values) external {
uint256 _id;
uint256 _value;
for (uint256 i = 0; i < _ids.length; ++i) {
_id = _ids[i];
_value = _values[i];
require(_value == 0 || allowances[_id][msg.sender][_spender] == _currentValues[i]);
allowances[_id][msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _id, _currentValues[i], _value);
}
}
//////////////////////////////// IERC1155BatchTransferExtended ////////////////////////////////////
function batchTransfer(address _to, uint256[] _ids, uint256[] _values) external {
uint256 _id;
uint256 _value;
for (uint256 i = 0; i < _ids.length; ++i) {
_id = _ids[i];
_value = _values[i];
items[_id].balances[msg.sender] = items[_id].balances[msg.sender].sub(_value);
items[_id].balances[_to] = _value.add(items[_id].balances[_to]);
emit Transfer(msg.sender, msg.sender, _to, _id, _value);
}
}
function safeBatchTransfer(address _to, uint256[] _ids, uint256[] _values, bytes _data) external {
revert('TBD');
}
//////////////////////////////// IERC1155BatchTransferExtended ////////////////////////////////////
// Optional meta data view Functions
// consider multi-lingual support for name?
function name(uint256 _id) external view returns (string) {
return items[_id].name;
}
function symbol(uint256 _id) external view returns (string) {
return symbols[_id];
}
function decimals(uint256 _id) external view returns (uint8) {
return decimals[_id];
}
function totalSupply(uint256 _id) external view returns (uint256) {
return items[_id].totalSupply;
}
function uri(uint256 _id) external view returns (string) {
return metadataURIs[_id];
}
////////////////////////////////////////// OPTIONALS //////////////////////////////////////////////
function multicastTransfer(address[] _to, uint256[] _ids, uint256[] _values) external {
for (uint256 i = 0; i < _to.length; ++i) {
uint256 _id = _ids[i];
uint256 _value = _values[i];
address _dst = _to[i];
items[_id].balances[msg.sender] = items[_id].balances[msg.sender].sub(_value);
items[_id].balances[_dst] = _value.add(items[_id].balances[_dst]);
emit Transfer(msg.sender, msg.sender, _dst, _id, _value);
}
}
function safeMulticastTransfer(address[] _to, uint256[] _ids, uint256[] _values, bytes _data) external {
revert('TBD');
}
}
ERC1155NonFungibleToken.sol
ERC721に該当するNFTは、ERC1155.sol
ではなく、ERC1155NonFungibleToken.sol
にて実装されています。ERC1155NonFungibleToken.sol
はNFTのインタフェースであるIERC1155NonFungibleToken.sol
をなぜか実装しておりませんが、同じfunction名で実装されたものはあるようです。(nonFungibleByIndex
など)
contract ERC1155NonFungible is ERC1155 {
// Use a split bit implementation.
// Store the type in the upper 128 bits..
uint256 constant TYPE_MASK = uint256(uint128(~0)) << 128;
// ..and the non-fungible index in the lower 128
uint256 constant NF_INDEX_MASK = uint128(~0);
// The top bit is a flag to tell if this is a NFI.
uint256 constant TYPE_NF_BIT = 1 << 255;
mapping (uint256 => address) nfiOwners;
// Only to make code clearer. Should not be functions
function isNonFungible(uint256 _id) public pure returns(bool) {
return _id & TYPE_NF_BIT == TYPE_NF_BIT;
}
function isFungible(uint256 _id) public pure returns(bool) {
return _id & TYPE_NF_BIT == 0;
}
function getNonFungibleIndex(uint256 _id) public pure returns(uint256) {
return _id & NF_INDEX_MASK;
}
function getNonFungibleBaseType(uint256 _id) public pure returns(uint256) {
return _id & TYPE_MASK;
}
function isNonFungibleBaseType(uint256 _id) public pure returns(bool) {
// A base type has the NF bit but does not have an index.
return (_id & TYPE_NF_BIT == TYPE_NF_BIT) && (_id & NF_INDEX_MASK == 0);
}
function isNonFungibleItem(uint256 _id) public pure returns(bool) {
// A base type has the NF bit but does not have an index.
return (_id & TYPE_NF_BIT == TYPE_NF_BIT) && (_id & NF_INDEX_MASK != 0);
}
function ownerOf(uint256 _id) public view returns (address) {
return nfiOwners[_id];
}
// retrieves an nfi id for _nfiType with a 1 based index.
function nonFungibleByIndex(uint256 _nfiType, uint128 _index) external view returns (uint256) {
// Needs to be a valid NFI type, not an actual NFI item
require(isNonFungibleBaseType(_nfiType));
require(uint256(_index) <= items[_nfiType].totalSupply);
uint256 nfiId = _nfiType | uint256(_index);
return nfiId;
}
// Allows enumeration of items owned by a specific owner
// _index is from 0 to balanceOf(_nfiType, _owner) - 1
function nonFungibleOfOwnerByIndex(uint256 _nfiType, address _owner, uint128 _index) external view returns (uint256) {
// can't call this on a non-fungible item directly, only its underlying id
require(isNonFungibleBaseType(_nfiType));
require(_index < items[_nfiType].balances[_owner]);
uint256 _numToSkip = _index;
uint256 _maxIndex = items[_nfiType].totalSupply;
// rather than spending gas storing all this, loop the supply and find the item
for (uint256 i = 1; i <= _maxIndex; ++i) {
uint256 _nfiId = _nfiType | i;
address _nfiOwner = nfiOwners[_nfiId];
if (_nfiOwner == _owner) {
if (_numToSkip == 0) {
return _nfiId;
} else {
_numToSkip = _numToSkip.sub(1);
}
}
}
return 0;
}
// overrides
function transfer(address _to, uint256[] _ids, uint256[] _values) external {
uint256 _id;
uint256 _value;
for (uint256 i = 0; i < _ids.length; ++i) {
_id = _ids[i];
_value = _values[i];
if (isNonFungible(_id)) {
require(_value == 1);
require(nfiOwners[_id] == msg.sender);
nfiOwners[_id] = _to;
}
uint256 _type = _id & TYPE_MASK;
items[_type].balances[msg.sender] = items[_type].balances[msg.sender].sub(_value);
items[_type].balances[_to] = _value.add(items[_type].balances[_to]);
emit Transfer(msg.sender, msg.sender, _to, _id, _value);
}
}
function transferFrom(address _from, address _to, uint256[] _ids, uint256[] _values) external {
uint256 _id;
uint256 _value;
for (uint256 i = 0; i < _ids.length; ++i) {
_id = _ids[i];
_value = _values[i];
if (isNonFungible(_id)) {
require(_value == 1);
require(nfiOwners[_id] == _from);
nfiOwners[_id] = _to;
}
if (_from != msg.sender) {
allowances[_id][_from][msg.sender] = allowances[_id][_from][msg.sender].sub(_value);
}
uint256 _type = _id & TYPE_MASK;
items[_type].balances[_from] = items[_type].balances[_from].sub(_value);
items[_type].balances[_to] = _value.add(items[_type].balances[_to]);
emit Transfer(msg.sender, _from, _to, _id, _value);
}
}
function balanceOf(uint256 _id, address _owner) external view returns (uint256) {
if (isNonFungibleItem(_id))
return ownerOf(_id) == _owner ? 1 : 0;
uint256 _type = _id & TYPE_MASK;
return items[_type].balances[_owner];
}
function totalSupply(uint256 _id) external view returns (uint256) {
// return 1 for a specific nfi, totalSupply otherwise.
if (isNonFungibleItem(_id)) {
// Make sure this is a valid index for the type.
require(getNonFungibleIndex(_id) <= items[_id & TYPE_MASK].totalSupply);
return 1;
} else {
return items[_id].totalSupply;
}
}
}
まとめ
ENJINに実装されていると言われているERC1155ですが、今はまだ企画途中でまだまだ議論は続きそうです。
次回はERC1155規格ベースに独自トークンを発行してみようと思います。