forked from OpenZeppelin/openzeppelin-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial ERC1155 implementation with some tests (OpenZeppelin#1803)
* Initial ERC1155 implementation with some tests * Remove mocked isERC1155TokenReceiver * Revert reason edit nit * Remove parameters associated with isERC1155TokenReceiver call * Add tests for approvals and single transfers * Add tests for transferring to contracts * Add tests for batch transfers * Make expectEvent.inTransaction tests async * Renamed "owner" to "account" and "holder" * Document unspecified balanceOfBatch reversion on zero behavior * Ensure accounts can't set their own operator status * Specify descriptive messages for underflow errors * Bring SafeMath.add calls in line with OZ style * Explicitly prevent _burn on the zero account * Implement batch minting/burning * Refactored operator approval check into isApprovedForAll calls * Renamed ERC1155TokenReceiver to ERC1155Receiver * Added ERC1155Holder * Fix lint issues
- Loading branch information
Showing
12 changed files
with
1,483 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
{ | ||
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( | ||
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 { | ||
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( | ||
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 { | ||
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" | ||
); | ||
} | ||
} | ||
} |
Oops, something went wrong.