Basic ERC1155 Implementation. Derived from reference implementation, with bug fixes.

This commit is contained in:
Greg Hysen
2019-02-27 09:55:43 -08:00
parent 22af796302
commit 7c850cc082
22 changed files with 1249 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
pragma solidity ^0.5.3;
import "./lib/SafeMath.sol";
import "./lib/Address.sol";
import "./interfaces/IERC1155.sol";
import "./interfaces/IERC1155Receiver.sol";
// A sample implementation of core ERC1155 function.
contract ERC1155 is
IERC1155,
SafeMath
{
using Address for address;
bytes4 constant public ERC1155_RECEIVED = 0xf23a6e61;
bytes4 constant public ERC1155_BATCH_RECEIVED = 0xbc197c81;
// id => (owner => balance)
mapping (uint256 => mapping(address => uint256)) internal balances;
// owner => (operator => approved)
mapping (address => mapping(address => bool)) internal operatorApproval;
// 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) nfOwners;
// 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 has an index.
return (_id & TYPE_NF_BIT == TYPE_NF_BIT) && (_id & NF_INDEX_MASK != 0);
}
function ownerOf(uint256 _id) public view returns (address) {
return nfOwners[_id];
}
// overide
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external {
require(_to != address(0x0), "cannot send to zero address");
require(_from == msg.sender || operatorApproval[_from][msg.sender] == true, "Need operator approval for 3rd party transfers.");
if (isNonFungible(_id)) {
require(nfOwners[_id] == _from);
nfOwners[_id] = _to;
// You could keep balance of NF type in base type id like so:
// uint256 baseType = getNonFungibleBaseType(_id);
// balances[baseType][_from] = balances[baseType][_from].safeSub(_value);
// balances[baseType][_to] = balances[baseType][_to].safeAdd(_value);
} else {
balances[_id][_from] = safeSub(balances[_id][_from], _value);
balances[_id][_to] = safeAdd(balances[_id][_to], _value);
}
emit TransferSingle(msg.sender, _from, _to, _id, _value);
if (_to.isContract()) {
require(IERC1155Receiver(_to).onERC1155Received(msg.sender, _from, _id, _value, _data) == ERC1155_RECEIVED);
}
}
// overide
function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external {
require(_to != address(0x0), "cannot send to zero address");
require(_ids.length == _values.length, "Array length must match");
// Only supporting a global operator approval allows us to do only 1 check and not to touch storage to handle allowances.
require(_from == msg.sender || operatorApproval[_from][msg.sender] == true, "Need operator approval for 3rd party transfers.");
for (uint256 i = 0; i < _ids.length; ++i) {
// Cache value to local variable to reduce read costs.
uint256 id = _ids[i];
uint256 value = _values[i];
if (isNonFungible(id)) {
require(nfOwners[id] == _from);
nfOwners[id] = _to;
} else {
balances[id][_from] = safeSub(balances[id][_from], value);
balances[id][_to] = safeAdd(value, balances[id][_to]);
}
}
emit TransferBatch(msg.sender, _from, _to, _ids, _values);
if (_to.isContract()) {
require(IERC1155Receiver(_to).onERC1155BatchReceived(msg.sender, _from, _ids, _values, _data) == ERC1155_BATCH_RECEIVED);
}
}
function balanceOf(address _owner, uint256 _id) external view returns (uint256) {
if (isNonFungibleItem(_id))
return nfOwners[_id] == _owner ? 1 : 0;
return balances[_id][_owner];
}
function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory) {
require(_owners.length == _ids.length);
uint256[] memory balances_ = new uint256[](_owners.length);
for (uint256 i = 0; i < _owners.length; ++i) {
uint256 id = _ids[i];
if (isNonFungibleItem(id)) {
balances_[i] = nfOwners[id] == _owners[i] ? 1 : 0;
} else {
balances_[i] = balances[id][_owners[i]];
}
}
return balances_;
}
/**
@notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens.
@dev MUST emit the ApprovalForAll event on success.
@param _operator address to safeAdd 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 {
operatorApproval[msg.sender][_operator] = _approved;
emit ApprovalForAll(msg.sender, _operator, _approved);
}
/**
@notice Queries the approval status of an operator for a given owner.
@param _owner The owner of the Tokens
@param _operator address of authorized operator
@return True if the operator is approved, false if not
*/
function isApprovedForAll(address _owner, address _operator) external view returns (bool) {
return operatorApproval[_owner][_operator];
}
}

View File

@@ -0,0 +1,98 @@
pragma solidity ^0.5.3;
import "./lib/SafeMath.sol";
import "./ERC1155.sol";
/**
@dev Mintable form of ERC1155
Shows how easy it is to mint new items
*/
contract ERC1155Mintable is
ERC1155
{
uint256 nonce;
mapping (uint256 => address) public creators;
mapping (uint256 => uint256) public maxIndex;
modifier creatorOnly(uint256 _id) {
require(creators[_id] == msg.sender);
_;
}
// This function only creates the type.
function create(
string calldata _uri,
bool _isNF
)
external
returns (uint256 _type)
{
// Store the type in the upper 128 bits
_type = (++nonce << 128);
// Set a flag if this is an NFI.
if (_isNF)
_type = _type | TYPE_NF_BIT;
// This will allow restricted access to creators.
creators[_type] = msg.sender;
// emit a Transfer event with Create semantic to help with discovery.
emit TransferSingle(msg.sender, address(0x0), address(0x0), _type, 0);
if (bytes(_uri).length > 0)
emit URI(_uri, _type);
}
function mintNonFungible(uint256 _type, address[] calldata _to) external creatorOnly(_type) {
// No need to check this is a nf type rather than an id since
// creatorOnly() will only let a type pass through.
require(isNonFungible(_type));
// Index are 1-based.
uint256 index = maxIndex[_type] + 1;
for (uint256 i = 0; i < _to.length; ++i) {
address dst = _to[i];
uint256 id = _type | index + i;
nfOwners[id] = dst;
// You could use base-type id to store NF type balances if you wish.
// balances[_type][dst] = quantity.safeAdd(balances[_type][dst]);
emit TransferSingle(msg.sender, address(0x0), dst, id, 1);
if (dst.isContract()) {
require(IERC1155Receiver(dst).onERC1155Received(msg.sender, msg.sender, id, 1, '') == ERC1155_RECEIVED);
}
}
maxIndex[_type] = safeAdd(_to.length, maxIndex[_type]);
}
function mintFungible(uint256 _id, address[] calldata _to, uint256[] calldata _quantities) external creatorOnly(_id) {
require(isFungible(_id));
for (uint256 i = 0; i < _to.length; ++i) {
address to = _to[i];
uint256 quantity = _quantities[i];
// Grant the items to the caller
balances[_id][to] = safeAdd(quantity, balances[_id][to]);
// Emit the Transfer/Mint event.
// the 0x0 source address implies a mint
// It will also provide the circulating supply info.
emit TransferSingle(msg.sender, address(0x0), to, _id, quantity);
if (to.isContract()) {
require(IERC1155Receiver(to).onERC1155Received(msg.sender, msg.sender, _id, quantity, '') == ERC1155_RECEIVED);
}
}
}
}

View File

@@ -0,0 +1,124 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.3;
/**
@title ERC-1155 Multi Token Standard
@dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md
Note: The ERC-165 identifier for this interface is 0xd9b67a26.
*/
interface IERC1155 {
/**
@dev Either TransferSingle or TransferBatch MUST emit when tokens are transferred, including zero value transfers as well as minting or burning.
Operator will always be msg.sender.
Either event from address `0x0` signifies a minting operation.
An event to address `0x0` signifies a burning or melting operation.
The total value transferred from address 0x0 minus the total value transferred to 0x0 may be used by clients and exchanges to be added to the "circulating supply" for a given token ID.
To define a token ID with no initial balance, the contract SHOULD emit the TransferSingle event from `0x0` to `0x0`, with the token creator as `_operator`.
*/
event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value);
/**
@dev Either TransferSingle or TransferBatch MUST emit when tokens are transferred, including zero value transfers as well as minting or burning.
Operator will always be msg.sender.
Either event from address `0x0` signifies a minting operation.
An event to address `0x0` signifies a burning or melting operation.
The total value transferred from address 0x0 minus the total value transferred to 0x0 may be used by clients and exchanges to be added to the "circulating supply" for a given token ID.
To define multiple token IDs with no initial balance, this SHOULD emit the TransferBatch event from `0x0` to `0x0`, with the token creator as `_operator`.
*/
event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values);
/**
@dev MUST emit when an approval is updated.
*/
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
/**
@dev MUST emit when the URI is updated for a token ID.
URIs are defined in RFC 3986.
The URI MUST point a JSON file that conforms to the "ERC-1155 Metadata JSON Schema".
*/
event URI(string _value, uint256 indexed _id);
/**
@notice Transfers value amount of an _id from the _from address to the _to address specified.
@dev MUST emit TransferSingle event on success.
Caller must be approved to manage the _from account's tokens (see isApprovedForAll).
MUST throw if `_to` is the zero address.
MUST throw if balance of sender for token `_id` is lower than the `_value` sent.
MUST throw on any other error.
When transfer is complete, this function MUST check if `_to` is a smart contract (code size > 0). If so, it MUST call `onERC1155Received` on `_to` and revert if the return value is not `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`.
@param _from Source address
@param _to Target address
@param _id ID of the token type
@param _value Transfer amount
@param _data Additional data with no specified format, sent in call to `_to`
*/
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
/**
@notice Send multiple types of Tokens from a 3rd party in one transfer (with safety call).
@dev MUST emit TransferBatch event on success.
Caller must be approved to manage the _from account's tokens (see isApprovedForAll).
MUST throw if `_to` is the zero address.
MUST throw if length of `_ids` is not the same as length of `_values`.
MUST throw if any of the balance of sender for token `_ids` is lower than the respective `_values` sent.
MUST throw on any other error.
When transfer is complete, this function MUST check if `_to` is a smart contract (code size > 0). If so, it MUST call `onERC1155BatchReceived` on `_to` and revert if the return value is not `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`.
@param _from Source addresses
@param _to Target addresses
@param _ids IDs of each token type
@param _values Transfer amounts per token type
@param _data Additional data with no specified format, sent in call to `_to`
*/
function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;
/**
@notice Get the balance of an account's Tokens.
@param _owner The address of the token holder
@param _id ID of the Token
@return The _owner's balance of the Token type requested
*/
function balanceOf(address _owner, uint256 _id) external view returns (uint256);
/**
@notice Get the balance of multiple account/token pairs
@param _owners The addresses of the token holders
@param _ids ID of the Tokens
@return The _owner's balance of the Token types requested
*/
function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory);
/**
@notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens.
@dev MUST emit the ApprovalForAll event on success.
@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;
/**
@notice Queries the approval status of an operator for a given owner.
@param _owner The owner of the Tokens
@param _operator Address of authorized operator
@return True if the operator is approved, false if not
*/
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

View File

@@ -0,0 +1,55 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.3;
interface IERC1155Receiver {
/**
@notice Handle the receipt of a single ERC1155 token type
@dev The smart contract calls this function on the recipient
after a `safeTransferFrom`. This function MAY throw to revert and reject the
transfer. Return of other than the magic value MUST result in the
transaction being reverted
Note: the contract address is always the message sender
@param _operator The address which called `safeTransferFrom` function
@param _from The address which previously owned the token
@param _id An array containing the ids of the token being transferred
@param _value An array containing the amount of tokens being transferred
@param _data Additional data with no specified format
@return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
*/
function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _value, bytes calldata _data) external returns(bytes4);
/**
@notice Handle the receipt of multiple ERC1155 token types
@dev The smart contract calls this function on the recipient
after a `safeTransferFrom`. This function MAY throw to revert and reject the
transfer. Return of other than the magic value MUST result in the
transaction being reverted
Note: the contract address is always the message sender
@param _operator The address which called `safeTransferFrom` function
@param _from The address which previously owned the token
@param _ids An array containing ids of each token being transferred
@param _values An array containing amounts of each token being transferred
@param _data Additional data with no specified format
@return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
*/
function onERC1155BatchReceived(address _operator, address _from, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external returns(bytes4);
}

View File

@@ -0,0 +1,75 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.3;
import "../src/interfaces/IERC1155Receiver.sol";
contract DummyERC1155Receiver is
IERC1155Receiver
{
bytes4 constant public ERC1155_RECEIVED = 0xf23a6e61;
bytes4 constant public ERC1155_BATCH_RECEIVED = 0xbc197c81;
constructor () public {}
event TokenReceived(
address operator,
address from,
uint256 tokenId,
uint256 tokenValue,
bytes data
);
event BatchTokenReceived(
address operator,
address from,
uint256[] tokenIds,
uint256[] tokenValues,
bytes data
);
function onERC1155Received(
address _operator,
address _from,
uint256 _id,
uint256 _value,
bytes calldata _data
)
external
returns (bytes4)
{
emit TokenReceived(_operator, _from, _id, _value, _data);
return ERC1155_RECEIVED;
}
function onERC1155BatchReceived(
address _operator,
address _from,
uint256[] calldata _ids,
uint256[] calldata _values,
bytes calldata _data
)
external
returns (bytes4)
{
emit BatchTokenReceived(_operator, _from, _ids, _values, _data);
return ERC1155_BATCH_RECEIVED;
}
}

View File

@@ -0,0 +1,40 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.3;
import "../src/ERC1155Mintable.sol";
// solhint-disable no-empty-blocks
contract DummyERC1155Token is
ERC1155Mintable
{
string public name;
string public symbol;
constructor (
string memory _name,
string memory _symbol
)
public
{
name = _name;
symbol = _symbol;
}
}

View File

@@ -0,0 +1,68 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.3;
contract InvalidERC1155Receiver //is
//IERC1155Receiver
{
/*
// Actual function signature is `onERC1155Received(address,address,uint256,bytes)`
bytes4 constant internal INVALID_ERC1155_RECEIVED = bytes4(keccak256("onERC1155Received(address,uint256,bytes)"));
event TokenReceived(
address operator,
address from,
uint256 tokenId,
bytes data
);
/// @notice Handle the receipt of an NFT
/// @dev The ERC1155 smart contract calls this function on the recipient
/// after a `transfer`. This function MAY throw to revert and reject the
/// transfer. Return of other than the magic value MUST result in the
/// transaction being reverted.
/// Note: the contract address is always the message sender.
/// @param _operator The address which called `safeTransferFrom` function
/// @param _from The address which previously owned the token
/// @param _tokenId The NFT identifier which is being transferred
/// @param _data Additional data with no specified format
/// @return `bytes4(keccak256("onERC1155Received(address,address,uint256,bytes)"))`
/// unless throwing
function onERC1155Received(
address _operator,
address _from,
uint256 _tokenId,
bytes _data
)
external
returns (bytes4)
{
emit TokenReceived(
_operator,
_from,
_tokenId,
_data
);
return INVALID_ERC1155_RECEIVED;
}
*/
}