2023-01-29 18:29:54 +00:00
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^ 0 . 8 . 17 ;
2022-02-11 04:17:00 +00:00
import " ./Address.sol " ;
import " ./Common.sol " ;
import " ./IERC1155TokenReceiver.sol " ;
import " ./IERC1155.sol " ;
contract ERC1155 is IERC1155 , ERC165 , CommonConstants
{
using Address for address ;
// id => (owner => balance)
mapping ( uint256 => mapping ( address => uint256 ) ) internal balances ;
// owner => (operator => approved)
mapping ( address => mapping ( address => bool ) ) internal operatorApproval ;
/////////////////////////////////////////// ERC165 //////////////////////////////////////////////
/*
bytes4 ( keccak256 ( ' supportsInterface(bytes4) ' ) ) ;
* /
bytes4 constant private INTERFACE_SIGNATURE_ERC165 = 0x01ffc9a7 ;
/*
bytes4 ( keccak256 ( " safeTransferFrom(address,address,uint256,uint256,bytes) " ) ) ^
bytes4 ( keccak256 ( " safeBatchTransferFrom(address,address,uint256[],uint256[],bytes) " ) ) ^
bytes4 ( keccak256 ( " balanceOf(address,uint256) " ) ) ^
bytes4 ( keccak256 ( " balanceOfBatch(address[],uint256[]) " ) ) ^
bytes4 ( keccak256 ( " setApprovalForAll(address,bool) " ) ) ^
bytes4 ( keccak256 ( " isApprovedForAll(address,address) " ) ) ;
* /
bytes4 constant private INTERFACE_SIGNATURE_ERC1155 = 0xd9b67a26 ;
function supportsInterface ( bytes4 _interfaceId )
public
2023-01-29 18:29:54 +00:00
pure
2023-01-29 22:22:15 +00:00
virtual
2022-02-11 04:17:00 +00:00
returns ( bool ) {
if ( _interfaceId == INTERFACE_SIGNATURE_ERC165 ||
_interfaceId == INTERFACE_SIGNATURE_ERC1155 ) {
return true ;
}
return false ;
}
/////////////////////////////////////////// ERC1155 //////////////////////////////////////////////
/**
@ notice Transfers ` _value ` amount of an ` _id ` from the ` _from ` address to the ` _to ` address specified ( with safety call ) .
@ dev Caller must be approved to manage the tokens being transferred out of the ` _from ` account ( see " Approval " section of the standard ) .
MUST revert if ` _to ` is the zero address .
MUST revert if balance of holder for token ` _id ` is lower than the ` _value ` sent .
MUST revert on any other error .
MUST emit the ` TransferSingle ` event to reflect the balance change ( see " Safe Transfer Rules " section of the standard ) .
After the above conditions are met , this function MUST check if ` _to ` is a smart contract ( e . g . code size > 0 ) . If so , it MUST call ` onERC1155Received ` on ` _to ` and act appropriately ( see " Safe Transfer Rules " section of the standard ) .
@ 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 , MUST be sent unaltered in call to ` onERC1155Received ` on ` _to `
* /
function safeTransferFrom ( address _from , address _to , uint256 _id , uint256 _value , bytes calldata _data ) external {
require ( _to != address ( 0x0 ) , " _to must be non-zero. " ) ;
require ( _from == msg . sender || operatorApproval [ _from ] [ msg . sender ] == true , " Need operator approval for 3rd party transfers. " ) ;
2023-01-29 21:55:26 +00:00
balances [ _id ] [ _from ] -= _value ;
balances [ _id ] [ _to ] += _value ;
2022-02-11 04:17:00 +00:00
// MUST emit event
emit TransferSingle ( msg . sender , _from , _to , _id , _value ) ;
// Now that the balance is updated and the event was emitted,
// call onERC1155Received if the destination is a contract.
if ( _to . isContract ( ) ) {
_doSafeTransferAcceptanceCheck ( msg . sender , _from , _to , _id , _value , _data ) ;
}
}
/**
@ notice Transfers ` _values ` amount ( s ) of ` _ids ` from the ` _from ` address to the ` _to ` address specified ( with safety call ) .
@ dev Caller must be approved to manage the tokens being transferred out of the ` _from ` account ( see " Approval " section of the standard ) .
MUST revert if ` _to ` is the zero address .
MUST revert if length of ` _ids ` is not the same as length of ` _values ` .
MUST revert if any of the balance ( s ) of the holder ( s ) for token ( s ) in ` _ids ` is lower than the respective amount ( s ) in ` _values ` sent to the recipient .
MUST revert on any other error .
MUST emit ` TransferSingle ` or ` TransferBatch ` event ( s ) such that all the balance changes are reflected ( see " Safe Transfer Rules " section of the standard ) .
Balance changes and events MUST follow the ordering of the arrays ( _ids [ 0 ] / _values [ 0 ] before _ids [ 1 ] / _values [ 1 ] , etc ) .
After the above conditions for the transfer ( s ) in the batch are met , this function MUST check if ` _to ` is a smart contract ( e . g . code size > 0 ) . If so , it MUST call the relevant ` ERC1155TokenReceiver ` hook ( s ) on ` _to ` and act appropriately ( see " Safe Transfer Rules " section of the standard ) .
@ param _from Source address
@ param _to Target address
@ param _ids IDs of each token type ( order and length must match _values array )
@ param _values Transfer amounts per token type ( order and length must match _ids array )
@ param _data Additional data with no specified format , MUST be sent unaltered in call to the ` ERC1155TokenReceiver ` hook ( s ) on ` _to `
* /
function safeBatchTransferFrom ( address _from , address _to , uint256 [ ] calldata _ids , uint256 [ ] calldata _values , bytes calldata _data ) external {
// MUST Throw on errors
require ( _to != address ( 0x0 ) , " destination address must be non-zero. " ) ;
require ( _ids . length == _values . length , " _ids and _values array length must match. " ) ;
require ( _from == msg . sender || operatorApproval [ _from ] [ msg . sender ] == true , " Need operator approval for 3rd party transfers. " ) ;
for ( uint256 i = 0 ; i < _ids . length ; ++ i ) {
uint256 id = _ids [ i ] ;
uint256 value = _values [ i ] ;
2023-01-29 21:55:26 +00:00
balances [ id ] [ _from ] -= value ;
balances [ id ] [ _to ] += value ;
2022-02-11 04:17:00 +00:00
}
// Note: instead of the below batch versions of event and acceptance check you MAY have emitted a TransferSingle
// event and a subsequent call to _doSafeTransferAcceptanceCheck in above loop for each balance change instead.
// Or emitted a TransferSingle event for each in the loop and then the single _doSafeBatchTransferAcceptanceCheck below.
// However it is implemented the balance changes and events MUST match when a check (i.e. calling an external contract) is done.
// MUST emit event
emit TransferBatch ( msg . sender , _from , _to , _ids , _values ) ;
// Now that the balances are updated and the events are emitted,
// call onERC1155BatchReceived if the destination is a contract.
if ( _to . isContract ( ) ) {
_doSafeBatchTransferAcceptanceCheck ( msg . sender , _from , _to , _ids , _values , _data ) ;
}
}
/**
@ notice Get the balance of an account ' s Tokens.
@ param _owner The address of the token holder
@ param _id ID of the Token
2023-01-29 21:58:16 +00:00
@ return balance The _owner ' s balance of the Token type requested
2022-02-11 04:17:00 +00:00
* /
function balanceOf ( address _owner , uint256 _id ) external view returns ( uint256 ) {
// The balance of any account can be calculated from the Transfer events history.
// However, since we need to keep the balances to validate transfer request,
// there is no extra cost to also privide a querry function.
return balances [ _id ] [ _owner ] ;
}
/**
@ notice Get the balance of multiple account / token pairs
@ param _owners The addresses of the token holders
@ param _ids ID of the Tokens
2023-01-29 21:58:16 +00:00
@ return balance The _owner ' s balance of the Token types requested (i.e. balance for each (owner, id) pair)
2022-02-11 04:17:00 +00:00
* /
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 ) {
balances_ [ i ] = balances [ _ids [ i ] ] [ _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 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 {
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 ) public view returns ( bool ) {
return operatorApproval [ _owner ] [ _operator ] ;
}
/////////////////////////////////////////// Internal //////////////////////////////////////////////
function _doSafeTransferAcceptanceCheck ( address _operator , address _from , address _to , uint256 _id , uint256 _value , bytes memory _data ) internal {
// If this was a hybrid standards solution you would have to check ERC165(_to).supportsInterface(0x4e2312e0) here but as this is a pure implementation of an ERC-1155 token set as recommended by
// the standard, it is not necessary. The below should revert in all failure cases i.e. _to isn't a receiver, or it is and either returns an unknown value or it reverts in the call to indicate non-acceptance.
// Note: if the below reverts in the onERC1155Received function of the _to address you will have an undefined revert reason returned rather than the one in the require test.
// If you want predictable revert reasons consider using low level _to.call() style instead so the revert does not bubble up and you can revert yourself on the ERC1155_ACCEPTED test.
require ( ERC1155TokenReceiver ( _to ) . onERC1155Received ( _operator , _from , _id , _value , _data ) == ERC1155_ACCEPTED , " contract returned an unknown value from onERC1155Received " ) ;
}
function _doSafeBatchTransferAcceptanceCheck ( address _operator , address _from , address _to , uint256 [ ] memory _ids , uint256 [ ] memory _values , bytes memory _data ) internal {
// If this was a hybrid standards solution you would have to check ERC165(_to).supportsInterface(0x4e2312e0) here but as this is a pure implementation of an ERC-1155 token set as recommended by
// the standard, it is not necessary. The below should revert in all failure cases i.e. _to isn't a receiver, or it is and either returns an unknown value or it reverts in the call to indicate non-acceptance.
// Note: if the below reverts in the onERC1155BatchReceived function of the _to address you will have an undefined revert reason returned rather than the one in the require test.
// If you want predictable revert reasons consider using low level _to.call() style instead so the revert does not bubble up and you can revert yourself on the ERC1155_BATCH_ACCEPTED test.
require ( ERC1155TokenReceiver ( _to ) . onERC1155BatchReceived ( _operator , _from , _ids , _values , _data ) == ERC1155_BATCH_ACCEPTED , " contract returned an unknown value from onERC1155BatchReceived " ) ;
}
}