-
Notifications
You must be signed in to change notification settings - Fork 12k
Initial ERC1155 implementation with some tests #1803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
16bcf7f
a41d23b
2d757b3
2621a8b
b9023d7
ce57e2d
4e6845b
05bb0c6
75cc9e9
85a50b7
d8d848f
eb52901
660090e
ae3725b
77b9627
dc86dba
7f7d9eb
b7125f2
077f8e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
pragma solidity ^0.5.0; | ||
|
||
import "../token/ERC1155/ERC1155.sol"; | ||
|
||
/** | ||
* @title ERC1155Mock | ||
* This mock just publicizes internal functions for testing purposes | ||
*/ | ||
contract ERC1155Mock is ERC1155 { | ||
function mint(address to, uint256 id, uint256 value, bytes memory data) public { | ||
_mint(to, id, value, data); | ||
} | ||
|
||
function mintBatch(address to, uint256[] memory ids, uint256[] memory values, bytes memory data) public { | ||
_mintBatch(to, ids, values, data); | ||
} | ||
|
||
function burn(address owner, uint256 id, uint256 value) public { | ||
_burn(owner, id, value); | ||
} | ||
|
||
function burnBatch(address owner, uint256[] memory ids, uint256[] memory values) public { | ||
_burnBatch(owner, ids, values); | ||
} | ||
|
||
function doSafeTransferAcceptanceCheck(address operator, address from, address to, uint256 id, uint256 value, bytes memory data) public { | ||
_doSafeTransferAcceptanceCheck(operator, from, to, id, value, data); | ||
} | ||
|
||
function doSafeBatchTransferAcceptanceCheck(address operator, address from, address to, uint256[] memory ids, uint256[] memory values, bytes memory data) public { | ||
_doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, values, data); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
pragma solidity ^0.5.0; | ||
|
||
import "../token/ERC1155/IERC1155Receiver.sol"; | ||
import "./ERC165Mock.sol"; | ||
|
||
contract ERC1155ReceiverMock is IERC1155Receiver, ERC165Mock { | ||
bytes4 private _recRetval; | ||
bool private _recReverts; | ||
bytes4 private _batRetval; | ||
bool private _batReverts; | ||
|
||
event Received(address operator, address from, uint256 id, uint256 value, bytes data, uint256 gas); | ||
event BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data, uint256 gas); | ||
|
||
constructor ( | ||
bytes4 recRetval, | ||
bool recReverts, | ||
bytes4 batRetval, | ||
bool batReverts | ||
) | ||
public | ||
{ | ||
_recRetval = recRetval; | ||
_recReverts = recReverts; | ||
_batRetval = batRetval; | ||
_batReverts = batReverts; | ||
} | ||
|
||
function onERC1155Received( | ||
address operator, | ||
address from, | ||
uint256 id, | ||
uint256 value, | ||
bytes calldata data | ||
) | ||
external | ||
returns(bytes4) | ||
{ | ||
require(!_recReverts, "ERC1155ReceiverMock: reverting on receive"); | ||
emit Received(operator, from, id, value, data, gasleft()); | ||
return _recRetval; | ||
} | ||
|
||
function onERC1155BatchReceived( | ||
address operator, | ||
address from, | ||
uint256[] calldata ids, | ||
uint256[] calldata values, | ||
bytes calldata data | ||
) | ||
external | ||
returns(bytes4) | ||
{ | ||
require(!_batReverts, "ERC1155ReceiverMock: reverting on batch receive"); | ||
emit BatchReceived(operator, from, ids, values, data, gasleft()); | ||
return _batRetval; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,295 @@ | ||
pragma solidity ^0.5.0; | ||
|
||
import "./IERC1155.sol"; | ||
import "./IERC1155Receiver.sol"; | ||
import "../../math/SafeMath.sol"; | ||
import "../../utils/Address.sol"; | ||
import "../../introspection/ERC165.sol"; | ||
|
||
/** | ||
* @title Standard ERC1155 token | ||
* | ||
* @dev Implementation of the basic standard multi-token. | ||
* See https://eips.ethereum.org/EIPS/eip-1155 | ||
* Originally based on code by Enjin: https://github.com/enjin/erc-1155 | ||
*/ | ||
contract ERC1155 is ERC165, IERC1155 | ||
nventuro marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some of the contracts are missing, such as the one for the metadata and the one for the URI functions. Are those going to be implemented in another stage?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did not seek to implement those contracts, since we don't know how these features really should work because we don't use them, and didn't plan on using them. These should be added by somebody with more knowledge/use for those features. |
||
{ | ||
using SafeMath for uint256; | ||
using Address for address; | ||
|
||
// Mapping from token ID to account balances | ||
mapping (uint256 => mapping(address => uint256)) private _balances; | ||
|
||
// Mapping from account to operator approvals | ||
mapping (address => mapping(address => bool)) private _operatorApprovals; | ||
|
||
constructor() | ||
public | ||
{ | ||
_registerInterface( | ||
nventuro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ERC1155(0).safeTransferFrom.selector ^ | ||
ERC1155(0).safeBatchTransferFrom.selector ^ | ||
ERC1155(0).balanceOf.selector ^ | ||
ERC1155(0).balanceOfBatch.selector ^ | ||
ERC1155(0).setApprovalForAll.selector ^ | ||
ERC1155(0).isApprovedForAll.selector | ||
); | ||
} | ||
|
||
/** | ||
@dev Get the specified address' balance for token with specified ID. | ||
|
||
Attempting to query the zero account for a balance will result in a revert. | ||
|
||
@param account The address of the token holder | ||
@param id ID of the token | ||
@return The account's balance of the token type requested | ||
*/ | ||
function balanceOf(address account, uint256 id) public view returns (uint256) { | ||
require(account != address(0), "ERC1155: balance query for the zero address"); | ||
return _balances[id][account]; | ||
} | ||
|
||
/** | ||
@dev Get the balance of multiple account/token pairs. | ||
|
||
If any of the query accounts is the zero account, this query will revert. | ||
|
||
@param accounts The addresses of the token holders | ||
@param ids IDs of the tokens | ||
@return Balances for each account and token id pair | ||
*/ | ||
function balanceOfBatch( | ||
address[] memory accounts, | ||
uint256[] memory ids | ||
) | ||
public | ||
view | ||
returns (uint256[] memory) | ||
{ | ||
require(accounts.length == ids.length, "ERC1155: accounts and IDs must have same lengths"); | ||
|
||
uint256[] memory batchBalances = new uint256[](accounts.length); | ||
|
||
for (uint256 i = 0; i < accounts.length; ++i) { | ||
require(accounts[i] != address(0), "ERC1155: some address in batch balance query is zero"); | ||
batchBalances[i] = _balances[ids[i]][accounts[i]]; | ||
} | ||
|
||
return batchBalances; | ||
} | ||
|
||
/** | ||
* @dev Sets or unsets the approval of a given operator. | ||
* | ||
* An operator is allowed to transfer all tokens of the sender on their behalf. | ||
* | ||
* Because an account already has operator privileges for itself, this function will revert | ||
* if the account attempts to set the approval status for itself. | ||
* | ||
* @param operator address to set the approval | ||
* @param approved representing the status of the approval to be set | ||
*/ | ||
function setApprovalForAll(address operator, bool approved) external { | ||
cag marked this conversation as resolved.
Show resolved
Hide resolved
|
||
require(msg.sender != operator, "ERC1155: cannot set approval status for self"); | ||
_operatorApprovals[msg.sender][operator] = approved; | ||
emit ApprovalForAll(msg.sender, operator, approved); | ||
} | ||
|
||
/** | ||
@notice Queries the approval status of an operator for a given account. | ||
@param account The account of the Tokens | ||
@param operator Address of authorized operator | ||
@return True if the operator is approved, false if not | ||
*/ | ||
function isApprovedForAll(address account, address operator) public view returns (bool) { | ||
return _operatorApprovals[account][operator]; | ||
} | ||
|
||
/** | ||
@dev Transfers `value` amount of an `id` from the `from` address to the `to` address specified. | ||
Caller must be approved to manage the tokens being transferred out of the `from` account. | ||
If `to` is a smart contract, will call `onERC1155Received` on `to` and act appropriately. | ||
@param from Source address | ||
@param to Target address | ||
@param id ID of the token type | ||
@param value Transfer amount | ||
@param data Data forwarded to `onERC1155Received` if `to` is a contract receiver | ||
*/ | ||
function safeTransferFrom( | ||
cag marked this conversation as resolved.
Show resolved
Hide resolved
|
||
address from, | ||
address to, | ||
uint256 id, | ||
uint256 value, | ||
bytes calldata data | ||
) | ||
external | ||
{ | ||
require(to != address(0), "ERC1155: target address must be non-zero"); | ||
require( | ||
from == msg.sender || isApprovedForAll(from, msg.sender) == true, | ||
"ERC1155: need operator approval for 3rd party transfers" | ||
); | ||
|
||
_balances[id][from] = _balances[id][from].sub(value, "ERC1155: insufficient balance for transfer"); | ||
_balances[id][to] = _balances[id][to].add(value); | ||
|
||
emit TransferSingle(msg.sender, from, to, id, value); | ||
|
||
_doSafeTransferAcceptanceCheck(msg.sender, from, to, id, value, data); | ||
} | ||
|
||
/** | ||
@dev Transfers `values` amount(s) of `ids` from the `from` address to the | ||
`to` address specified. Caller must be approved to manage the tokens being | ||
transferred out of the `from` account. If `to` is a smart contract, will | ||
call `onERC1155BatchReceived` on `to` and act appropriately. | ||
@param from Source address | ||
@param to Target address | ||
@param ids IDs of each token type | ||
@param values Transfer amounts per token type | ||
@param data Data forwarded to `onERC1155Received` if `to` is a contract receiver | ||
*/ | ||
function safeBatchTransferFrom( | ||
address from, | ||
address to, | ||
uint256[] calldata ids, | ||
uint256[] calldata values, | ||
bytes calldata data | ||
) | ||
external | ||
{ | ||
require(ids.length == values.length, "ERC1155: IDs and values must have same lengths"); | ||
require(to != address(0), "ERC1155: target address must be non-zero"); | ||
require( | ||
from == msg.sender || isApprovedForAll(from, msg.sender) == true, | ||
"ERC1155: need operator approval for 3rd party transfers" | ||
); | ||
|
||
for (uint256 i = 0; i < ids.length; ++i) { | ||
uint256 id = ids[i]; | ||
uint256 value = values[i]; | ||
|
||
_balances[id][from] = _balances[id][from].sub( | ||
value, | ||
"ERC1155: insufficient balance of some token type for transfer" | ||
); | ||
_balances[id][to] = _balances[id][to].add(value); | ||
} | ||
|
||
emit TransferBatch(msg.sender, from, to, ids, values); | ||
|
||
_doSafeBatchTransferAcceptanceCheck(msg.sender, from, to, ids, values, data); | ||
} | ||
|
||
/** | ||
* @dev Internal function to mint an amount of a token with the given ID | ||
* @param to The address that will own the minted token | ||
* @param id ID of the token to be minted | ||
* @param value Amount of the token to be minted | ||
* @param data Data forwarded to `onERC1155Received` if `to` is a contract receiver | ||
*/ | ||
function _mint(address to, uint256 id, uint256 value, bytes memory data) internal { | ||
cag marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There isn't a method to broadcast a new token Consider adding a new function for creating a new type of token. Take into account that if it is used a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If possible, I'd like to punt on this, because this will require additional storage in the contract, and if implemented, probably should prevent the use of counterfactual IDs (as you've mentioned below via reverting transactions on unminted IDs). Since we (Gnosis) actually need to use counterfactual IDs in our implementation, we have not implemented this. I can see how this may be useful for "game item" scenarios though: something that this standard was originally designed for, but because we don't want to exclude our own use case, I think this functionality would fit better in an derived contract. |
||
require(to != address(0), "ERC1155: mint to the zero address"); | ||
|
||
_balances[id][to] = _balances[id][to].add(value); | ||
emit TransferSingle(msg.sender, address(0), to, id, value); | ||
|
||
_doSafeTransferAcceptanceCheck(msg.sender, address(0), to, id, value, data); | ||
} | ||
|
||
/** | ||
* @dev Internal function to batch mint amounts of tokens with the given IDs | ||
* @param to The address that will own the minted token | ||
* @param ids IDs of the tokens to be minted | ||
* @param values Amounts of the tokens to be minted | ||
* @param data Data forwarded to `onERC1155Received` if `to` is a contract receiver | ||
*/ | ||
function _mintBatch(address to, uint256[] memory ids, uint256[] memory values, bytes memory data) internal { | ||
require(to != address(0), "ERC1155: batch mint to the zero address"); | ||
require(ids.length == values.length, "ERC1155: minted IDs and values must have same lengths"); | ||
|
||
for(uint i = 0; i < ids.length; i++) { | ||
_balances[ids[i]][to] = values[i].add(_balances[ids[i]][to]); | ||
} | ||
|
||
emit TransferBatch(msg.sender, address(0), to, ids, values); | ||
|
||
_doSafeBatchTransferAcceptanceCheck(msg.sender, address(0), to, ids, values, data); | ||
} | ||
|
||
/** | ||
* @dev Internal function to burn an amount of a token with the given ID | ||
* @param account Account which owns the token to be burnt | ||
* @param id ID of the token to be burnt | ||
* @param value Amount of the token to be burnt | ||
*/ | ||
function _burn(address account, uint256 id, uint256 value) internal { | ||
require(account != address(0), "ERC1155: attempting to burn tokens on zero account"); | ||
|
||
_balances[id][account] = _balances[id][account].sub( | ||
value, | ||
"ERC1155: attempting to burn more than balance" | ||
); | ||
emit TransferSingle(msg.sender, account, address(0), id, value); | ||
} | ||
|
||
/** | ||
* @dev Internal function to batch burn an amounts of tokens with the given IDs | ||
* @param account Account which owns the token to be burnt | ||
* @param ids IDs of the tokens to be burnt | ||
* @param values Amounts of the tokens to be burnt | ||
*/ | ||
function _burnBatch(address account, uint256[] memory ids, uint256[] memory values) internal { | ||
require(account != address(0), "ERC1155: attempting to burn batch of tokens on zero account"); | ||
require(ids.length == values.length, "ERC1155: burnt IDs and values must have same lengths"); | ||
|
||
for(uint i = 0; i < ids.length; i++) { | ||
_balances[ids[i]][account] = _balances[ids[i]][account].sub( | ||
values[i], | ||
"ERC1155: attempting to burn more than balance for some token" | ||
); | ||
} | ||
|
||
emit TransferBatch(msg.sender, account, address(0), ids, values); | ||
} | ||
|
||
function _doSafeTransferAcceptanceCheck( | ||
address operator, | ||
address from, | ||
address to, | ||
uint256 id, | ||
uint256 value, | ||
bytes memory data | ||
) | ||
internal | ||
{ | ||
if(to.isContract()) { | ||
require( | ||
IERC1155Receiver(to).onERC1155Received(operator, from, id, value, data) == | ||
IERC1155Receiver(to).onERC1155Received.selector, | ||
"ERC1155: got unknown value from onERC1155Received" | ||
); | ||
} | ||
} | ||
|
||
function _doSafeBatchTransferAcceptanceCheck( | ||
address operator, | ||
address from, | ||
address to, | ||
uint256[] memory ids, | ||
uint256[] memory values, | ||
bytes memory data | ||
) | ||
internal | ||
{ | ||
if(to.isContract()) { | ||
require( | ||
IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, values, data) == | ||
IERC1155Receiver(to).onERC1155BatchReceived.selector, | ||
"ERC1155: got unknown value from onERC1155BatchReceived" | ||
); | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.