Transaction Hash:
Block:
21751598 at Feb-01-2025 12:13:59 PM +UTC
Transaction Fee:
0.0003280925 ETH
$0.62
Gas Used:
131,237 Gas / 2.5 Gwei
Emitted Events:
183 |
RaribleUserToken.TransferSingle( _operator=Conduit, _from=[Sender] 0x20fcacfe52f0453b6924fb651205bf99362af72f, _to=0xEcaC1be9...0734cBc13, _id=10351, _value=1 )
|
184 |
RaribleUserToken.TransferSingle( _operator=Conduit, _from=[Sender] 0x20fcacfe52f0453b6924fb651205bf99362af72f, _to=0xEcaC1be9...0734cBc13, _id=10179, _value=1 )
|
185 |
RaribleUserToken.TransferSingle( _operator=Conduit, _from=[Sender] 0x20fcacfe52f0453b6924fb651205bf99362af72f, _to=0xEcaC1be9...0734cBc13, _id=10950, _value=1 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x20FCACFE...9362AF72f |
0.002837869345023612 Eth
Nonce: 23
|
0.002509776845023612 Eth
Nonce: 24
| 0.0003280925 | ||
0x4838B106...B0BAD5f97
Miner
| (Titan Builder) | 11.24298300983786643 Eth | 11.242998149947519821 Eth | 0.000015140109653391 | |
0x76BE3b62...1754E8E77 |
Execution Trace
TransferHelper.bulkTransfer( items=, conduitKey=0000007B02230091A7ED01230072F7006A004D60A8D4E71D599B8104250F0000 ) => ( items=, conduitKey= )
Conduit.execute( transfers= ) => ( transfers= )
-
RaribleUserToken.safeTransferFrom( _from=0x20FCACFE52f0453b6924FB651205bf99362AF72f, _to=0xEcaC1be916368C28D443736A332303E0734cBc13, _id=10351, _value=1, _data=0x )
-
RaribleUserToken.safeTransferFrom( _from=0x20FCACFE52f0453b6924FB651205bf99362AF72f, _to=0xEcaC1be916368C28D443736A332303E0734cBc13, _id=10179, _value=1, _data=0x )
-
RaribleUserToken.safeTransferFrom( _from=0x20FCACFE52f0453b6924FB651205bf99362AF72f, _to=0xEcaC1be916368C28D443736A332303E0734cBc13, _id=10950, _value=1, _data=0x )
-
bulkTransfer[TransferHelper (ln:57)]
InvalidConduit[TransferHelper (ln:63)]
_performTransfersWithConduit[TransferHelper (ln:66)]
_checkRecipientIsNotZeroAddress[TransferHelper (ln:132)]
RecipientCannotBeZeroAddress[TransferHelper (ln:292)]
InvalidERC20Identifier[TransferHelper (ln:148)]
_checkERC721Receiver[TransferHelper (ln:157)]
onERC721Received[TransferHelper (ln:252)]
InvalidERC721Recipient[TransferHelper (ln:263)]
ERC721ReceiverErrorRevertBytes[TransferHelper (ln:267)]
ERC721ReceiverErrorRevertString[TransferHelper (ln:275)]
ConduitTransfer[TransferHelper (ln:166)]
execute[TransferHelper (ln:180)]
InvalidConduit[TransferHelper (ln:188)]
ConduitErrorRevertString[TransferHelper (ln:193)]
ConduitErrorRevertBytes[TransferHelper (ln:227)]
File 1 of 3: TransferHelper
File 2 of 3: RaribleUserToken
File 3 of 3: Conduit
// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import { IERC721Receiver } from "../interfaces/IERC721Receiver.sol"; import "./TransferHelperStructs.sol"; import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; import { ConduitControllerInterface } from "../interfaces/ConduitControllerInterface.sol"; import { Conduit } from "../conduit/Conduit.sol"; import { ConduitTransfer } from "../conduit/lib/ConduitStructs.sol"; import { TransferHelperInterface } from "../interfaces/TransferHelperInterface.sol"; import { TransferHelperErrors } from "../interfaces/TransferHelperErrors.sol"; /** * @title TransferHelper * @author stephankmin, stuckinaboot, ryanio * @notice TransferHelper is a utility contract for transferring * ERC20/ERC721/ERC1155 items in bulk to specific recipients. */ contract TransferHelper is TransferHelperInterface, TransferHelperErrors { // Allow for interaction with the conduit controller. ConduitControllerInterface internal immutable _CONDUIT_CONTROLLER; // Set conduit creation code and runtime code hashes as immutable arguments. bytes32 internal immutable _CONDUIT_CREATION_CODE_HASH; bytes32 internal immutable _CONDUIT_RUNTIME_CODE_HASH; /** * @dev Set the supplied conduit controller and retrieve its * conduit creation code hash. * * * @param conduitController A contract that deploys conduits, or proxies * that may optionally be used to transfer approved * ERC20/721/1155 tokens. */ constructor(address conduitController) { // Get the conduit creation code and runtime code hashes from the // supplied conduit controller and set them as an immutable. ConduitControllerInterface controller = ConduitControllerInterface( conduitController ); (_CONDUIT_CREATION_CODE_HASH, _CONDUIT_RUNTIME_CODE_HASH) = controller .getConduitCodeHashes(); // Set the supplied conduit controller as an immutable. _CONDUIT_CONTROLLER = controller; } /** * @notice Transfer multiple ERC20/ERC721/ERC1155 items to * specified recipients. * * @param items The items to transfer to an intended recipient. * @param conduitKey An optional conduit key referring to a conduit through * which the bulk transfer should occur. * * @return magicValue A value indicating that the transfers were successful. */ function bulkTransfer( TransferHelperItemsWithRecipient[] calldata items, bytes32 conduitKey ) external override returns (bytes4 magicValue) { // Ensure that a conduit key has been supplied. if (conduitKey == bytes32(0)) { revert InvalidConduit(conduitKey, address(0)); } // Use conduit derived from supplied conduit key to perform transfers. _performTransfersWithConduit(items, conduitKey); // Return a magic value indicating that the transfers were performed. magicValue = this.bulkTransfer.selector; } /** * @notice Perform multiple transfers to specified recipients via the * conduit derived from the provided conduit key. * * @param transfers The items to transfer. * @param conduitKey The conduit key referring to the conduit through * which the bulk transfer should occur. */ function _performTransfersWithConduit( TransferHelperItemsWithRecipient[] calldata transfers, bytes32 conduitKey ) internal { // Retrieve total number of transfers and place on stack. uint256 numTransfers = transfers.length; // Derive the conduit address from the deployer, conduit key // and creation code hash. address conduit = address( uint160( uint256( keccak256( abi.encodePacked( bytes1(0xff), address(_CONDUIT_CONTROLLER), conduitKey, _CONDUIT_CREATION_CODE_HASH ) ) ) ) ); // Declare a variable to store the sum of all items across transfers. uint256 sumOfItemsAcrossAllTransfers; // Skip overflow checks: all for loops are indexed starting at zero. unchecked { // Iterate over each transfer. for (uint256 i = 0; i < numTransfers; ++i) { // Retrieve the transfer in question. TransferHelperItemsWithRecipient calldata transfer = transfers[ i ]; // Increment totalItems by the number of items in the transfer. sumOfItemsAcrossAllTransfers += transfer.items.length; } } // Declare a new array in memory with length totalItems to populate with // each conduit transfer. ConduitTransfer[] memory conduitTransfers = new ConduitTransfer[]( sumOfItemsAcrossAllTransfers ); // Declare an index for storing ConduitTransfers in conduitTransfers. uint256 itemIndex; // Skip overflow checks: all for loops are indexed starting at zero. unchecked { // Iterate over each transfer. for (uint256 i = 0; i < numTransfers; ++i) { // Retrieve the transfer in question. TransferHelperItemsWithRecipient calldata transfer = transfers[ i ]; // Retrieve the items of the transfer in question. TransferHelperItem[] calldata transferItems = transfer.items; // Ensure recipient is not the zero address. _checkRecipientIsNotZeroAddress(transfer.recipient); // Create a boolean indicating whether validateERC721Receiver // is true and recipient is a contract. bool callERC721Receiver = transfer.validateERC721Receiver && transfer.recipient.code.length != 0; // Retrieve the total number of items in the transfer and // place on stack. uint256 numItemsInTransfer = transferItems.length; // Iterate over each item in the transfer to create a // corresponding ConduitTransfer. for (uint256 j = 0; j < numItemsInTransfer; ++j) { // Retrieve the item from the transfer. TransferHelperItem calldata item = transferItems[j]; if (item.itemType == ConduitItemType.ERC20) { // Ensure that the identifier of an ERC20 token is 0. if (item.identifier != 0) { revert InvalidERC20Identifier(); } } // If the item is an ERC721 token and // callERC721Receiver is true... if (item.itemType == ConduitItemType.ERC721) { if (callERC721Receiver) { // Check if the recipient implements // onERC721Received for the given tokenId. _checkERC721Receiver( conduit, transfer.recipient, item.identifier ); } } // Create a ConduitTransfer corresponding to each // TransferHelperItem. conduitTransfers[itemIndex] = ConduitTransfer( item.itemType, item.token, msg.sender, transfer.recipient, item.identifier, item.amount ); // Increment the index for storing ConduitTransfers. ++itemIndex; } } } // Attempt the external call to transfer tokens via the derived conduit. try ConduitInterface(conduit).execute(conduitTransfers) returns ( bytes4 conduitMagicValue ) { // Check if the value returned from the external call matches // the conduit `execute` selector. if (conduitMagicValue != ConduitInterface.execute.selector) { // If the external call fails, revert with the conduit key // and conduit address. revert InvalidConduit(conduitKey, conduit); } } catch Error(string memory reason) { // Catch reverts with a provided reason string and // revert with the reason, conduit key and conduit address. revert ConduitErrorRevertString(reason, conduitKey, conduit); } catch (bytes memory data) { // Conduits will throw a custom error when attempting to transfer // native token item types or an ERC721 item amount other than 1. // Bubble up these custom errors when encountered. Note that the // conduit itself will bubble up revert reasons from transfers as // well, meaning that these errors are not necessarily indicative of // an issue with the item type or amount in cases where the same // custom error signature is encountered during a conduit transfer. // Set initial value of first four bytes of revert data to the mask. bytes4 customErrorSelector = bytes4(0xffffffff); // Utilize assembly to read first four bytes (if present) directly. assembly { // Combine original mask with first four bytes of revert data. customErrorSelector := and( mload(add(data, 0x20)), // Data begins after length offset. customErrorSelector ) } // Pass through the custom error in question if the revert data is // the correct length and matches an expected custom error selector. if ( data.length == 4 && (customErrorSelector == InvalidItemType.selector || customErrorSelector == InvalidERC721TransferAmount.selector) ) { // "Bubble up" the revert reason. assembly { revert(add(data, 0x20), 0x04) } } // Catch all other reverts from the external call to the conduit and // include the conduit's raw revert reason as a data argument to a // new custom error. revert ConduitErrorRevertBytes(data, conduitKey, conduit); } } /** * @notice An internal function to check if a recipient address implements * onERC721Received for a given tokenId. Note that this check does * not adhere to the safe transfer specification and is only meant * to provide an additional layer of assurance that the recipient * can receive the tokens — any hooks or post-transfer checks will * fail and the caller will be the transfer helper rather than the * ERC721 contract. Note that the conduit is set as the operator, as * it will be the caller once the transfer is performed. * * @param conduit The conduit to provide as the operator when calling * onERC721Received. * @param recipient The ERC721 recipient on which to call onERC721Received. * @param tokenId The ERC721 tokenId of the token being transferred. */ function _checkERC721Receiver( address conduit, address recipient, uint256 tokenId ) internal { // Check if recipient can receive ERC721 tokens. try IERC721Receiver(recipient).onERC721Received( conduit, msg.sender, tokenId, "" ) returns (bytes4 selector) { // Check if onERC721Received selector is valid. if (selector != IERC721Receiver.onERC721Received.selector) { // Revert if recipient cannot accept // ERC721 tokens. revert InvalidERC721Recipient(recipient); } } catch (bytes memory data) { // "Bubble up" recipient's revert reason. revert ERC721ReceiverErrorRevertBytes( data, recipient, msg.sender, tokenId ); } catch Error(string memory reason) { // "Bubble up" recipient's revert reason. revert ERC721ReceiverErrorRevertString( reason, recipient, msg.sender, tokenId ); } } /** * @notice An internal function that reverts if the passed-in recipient * is the zero address. * * @param recipient The recipient on which to perform the check. */ function _checkRecipientIsNotZeroAddress(address recipient) internal pure { // Revert if the recipient is the zero address. if (recipient == address(0x0)) { revert RecipientCannotBeZeroAddress(); } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; interface IERC721Receiver { function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import { ConduitItemType } from "../conduit/lib/ConduitEnums.sol"; /** * @dev A TransferHelperItem specifies the itemType (ERC20/ERC721/ERC1155), * token address, token identifier, and amount of the token to be * transferred via the TransferHelper. For ERC20 tokens, identifier * must be 0. For ERC721 tokens, amount must be 1. */ struct TransferHelperItem { ConduitItemType itemType; address token; uint256 identifier; uint256 amount; } /** * @dev A TransferHelperItemsWithRecipient specifies the tokens to transfer * via the TransferHelper, their intended recipient, and a boolean flag * indicating whether onERC721Received should be called on a recipient * contract. */ struct TransferHelperItemsWithRecipient { TransferHelperItem[] items; address recipient; bool validateERC721Receiver; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import { ConduitTransfer, ConduitBatch1155Transfer } from "../conduit/lib/ConduitStructs.sol"; /** * @title ConduitInterface * @author 0age * @notice ConduitInterface contains all external function interfaces, events, * and errors for conduit contracts. */ interface ConduitInterface { /** * @dev Revert with an error when attempting to execute transfers using a * caller that does not have an open channel. */ error ChannelClosed(address channel); /** * @dev Revert with an error when attempting to update a channel to the * current status of that channel. */ error ChannelStatusAlreadySet(address channel, bool isOpen); /** * @dev Revert with an error when attempting to execute a transfer for an * item that does not have an ERC20/721/1155 item type. */ error InvalidItemType(); /** * @dev Revert with an error when attempting to update the status of a * channel from a caller that is not the conduit controller. */ error InvalidController(); /** * @dev Emit an event whenever a channel is opened or closed. * * @param channel The channel that has been updated. * @param open A boolean indicating whether the conduit is open or not. */ event ChannelUpdated(address indexed channel, bool open); /** * @notice Execute a sequence of ERC20/721/1155 transfers. Only a caller * with an open channel can call this function. * * @param transfers The ERC20/721/1155 transfers to perform. * * @return magicValue A magic value indicating that the transfers were * performed successfully. */ function execute(ConduitTransfer[] calldata transfers) external returns (bytes4 magicValue); /** * @notice Execute a sequence of batch 1155 transfers. Only a caller with an * open channel can call this function. * * @param batch1155Transfers The 1155 batch transfers to perform. * * @return magicValue A magic value indicating that the transfers were * performed successfully. */ function executeBatch1155( ConduitBatch1155Transfer[] calldata batch1155Transfers ) external returns (bytes4 magicValue); /** * @notice Execute a sequence of transfers, both single and batch 1155. Only * a caller with an open channel can call this function. * * @param standardTransfers The ERC20/721/1155 transfers to perform. * @param batch1155Transfers The 1155 batch transfers to perform. * * @return magicValue A magic value indicating that the transfers were * performed successfully. */ function executeWithBatch1155( ConduitTransfer[] calldata standardTransfers, ConduitBatch1155Transfer[] calldata batch1155Transfers ) external returns (bytes4 magicValue); /** * @notice Open or close a given channel. Only callable by the controller. * * @param channel The channel to open or close. * @param isOpen The status of the channel (either open or closed). */ function updateChannel(address channel, bool isOpen) external; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; /** * @title ConduitControllerInterface * @author 0age * @notice ConduitControllerInterface contains all external function interfaces, * structs, events, and errors for the conduit controller. */ interface ConduitControllerInterface { /** * @dev Track the conduit key, current owner, new potential owner, and open * channels for each deployed conduit. */ struct ConduitProperties { bytes32 key; address owner; address potentialOwner; address[] channels; mapping(address => uint256) channelIndexesPlusOne; } /** * @dev Emit an event whenever a new conduit is created. * * @param conduit The newly created conduit. * @param conduitKey The conduit key used to create the new conduit. */ event NewConduit(address conduit, bytes32 conduitKey); /** * @dev Emit an event whenever conduit ownership is transferred. * * @param conduit The conduit for which ownership has been * transferred. * @param previousOwner The previous owner of the conduit. * @param newOwner The new owner of the conduit. */ event OwnershipTransferred( address indexed conduit, address indexed previousOwner, address indexed newOwner ); /** * @dev Emit an event whenever a conduit owner registers a new potential * owner for that conduit. * * @param newPotentialOwner The new potential owner of the conduit. */ event PotentialOwnerUpdated(address indexed newPotentialOwner); /** * @dev Revert with an error when attempting to create a new conduit using a * conduit key where the first twenty bytes of the key do not match the * address of the caller. */ error InvalidCreator(); /** * @dev Revert with an error when attempting to create a new conduit when no * initial owner address is supplied. */ error InvalidInitialOwner(); /** * @dev Revert with an error when attempting to set a new potential owner * that is already set. */ error NewPotentialOwnerAlreadySet( address conduit, address newPotentialOwner ); /** * @dev Revert with an error when attempting to cancel ownership transfer * when no new potential owner is currently set. */ error NoPotentialOwnerCurrentlySet(address conduit); /** * @dev Revert with an error when attempting to interact with a conduit that * does not yet exist. */ error NoConduit(); /** * @dev Revert with an error when attempting to create a conduit that * already exists. */ error ConduitAlreadyExists(address conduit); /** * @dev Revert with an error when attempting to update channels or transfer * ownership of a conduit when the caller is not the owner of the * conduit in question. */ error CallerIsNotOwner(address conduit); /** * @dev Revert with an error when attempting to register a new potential * owner and supplying the null address. */ error NewPotentialOwnerIsZeroAddress(address conduit); /** * @dev Revert with an error when attempting to claim ownership of a conduit * with a caller that is not the current potential owner for the * conduit in question. */ error CallerIsNotNewPotentialOwner(address conduit); /** * @dev Revert with an error when attempting to retrieve a channel using an * index that is out of range. */ error ChannelOutOfRange(address conduit); /** * @notice Deploy a new conduit using a supplied conduit key and assigning * an initial owner for the deployed conduit. Note that the first * twenty bytes of the supplied conduit key must match the caller * and that a new conduit cannot be created if one has already been * deployed using the same conduit key. * * @param conduitKey The conduit key used to deploy the conduit. Note that * the first twenty bytes of the conduit key must match * the caller of this contract. * @param initialOwner The initial owner to set for the new conduit. * * @return conduit The address of the newly deployed conduit. */ function createConduit(bytes32 conduitKey, address initialOwner) external returns (address conduit); /** * @notice Open or close a channel on a given conduit, thereby allowing the * specified account to execute transfers against that conduit. * Extreme care must be taken when updating channels, as malicious * or vulnerable channels can transfer any ERC20, ERC721 and ERC1155 * tokens where the token holder has granted the conduit approval. * Only the owner of the conduit in question may call this function. * * @param conduit The conduit for which to open or close the channel. * @param channel The channel to open or close on the conduit. * @param isOpen A boolean indicating whether to open or close the channel. */ function updateChannel( address conduit, address channel, bool isOpen ) external; /** * @notice Initiate conduit ownership transfer by assigning a new potential * owner for the given conduit. Once set, the new potential owner * may call `acceptOwnership` to claim ownership of the conduit. * Only the owner of the conduit in question may call this function. * * @param conduit The conduit for which to initiate ownership transfer. * @param newPotentialOwner The new potential owner of the conduit. */ function transferOwnership(address conduit, address newPotentialOwner) external; /** * @notice Clear the currently set potential owner, if any, from a conduit. * Only the owner of the conduit in question may call this function. * * @param conduit The conduit for which to cancel ownership transfer. */ function cancelOwnershipTransfer(address conduit) external; /** * @notice Accept ownership of a supplied conduit. Only accounts that the * current owner has set as the new potential owner may call this * function. * * @param conduit The conduit for which to accept ownership. */ function acceptOwnership(address conduit) external; /** * @notice Retrieve the current owner of a deployed conduit. * * @param conduit The conduit for which to retrieve the associated owner. * * @return owner The owner of the supplied conduit. */ function ownerOf(address conduit) external view returns (address owner); /** * @notice Retrieve the conduit key for a deployed conduit via reverse * lookup. * * @param conduit The conduit for which to retrieve the associated conduit * key. * * @return conduitKey The conduit key used to deploy the supplied conduit. */ function getKey(address conduit) external view returns (bytes32 conduitKey); /** * @notice Derive the conduit associated with a given conduit key and * determine whether that conduit exists (i.e. whether it has been * deployed). * * @param conduitKey The conduit key used to derive the conduit. * * @return conduit The derived address of the conduit. * @return exists A boolean indicating whether the derived conduit has been * deployed or not. */ function getConduit(bytes32 conduitKey) external view returns (address conduit, bool exists); /** * @notice Retrieve the potential owner, if any, for a given conduit. The * current owner may set a new potential owner via * `transferOwnership` and that owner may then accept ownership of * the conduit in question via `acceptOwnership`. * * @param conduit The conduit for which to retrieve the potential owner. * * @return potentialOwner The potential owner, if any, for the conduit. */ function getPotentialOwner(address conduit) external view returns (address potentialOwner); /** * @notice Retrieve the status (either open or closed) of a given channel on * a conduit. * * @param conduit The conduit for which to retrieve the channel status. * @param channel The channel for which to retrieve the status. * * @return isOpen The status of the channel on the given conduit. */ function getChannelStatus(address conduit, address channel) external view returns (bool isOpen); /** * @notice Retrieve the total number of open channels for a given conduit. * * @param conduit The conduit for which to retrieve the total channel count. * * @return totalChannels The total number of open channels for the conduit. */ function getTotalChannels(address conduit) external view returns (uint256 totalChannels); /** * @notice Retrieve an open channel at a specific index for a given conduit. * Note that the index of a channel can change as a result of other * channels being closed on the conduit. * * @param conduit The conduit for which to retrieve the open channel. * @param channelIndex The index of the channel in question. * * @return channel The open channel, if any, at the specified channel index. */ function getChannel(address conduit, uint256 channelIndex) external view returns (address channel); /** * @notice Retrieve all open channels for a given conduit. Note that calling * this function for a conduit with many channels will revert with * an out-of-gas error. * * @param conduit The conduit for which to retrieve open channels. * * @return channels An array of open channels on the given conduit. */ function getChannels(address conduit) external view returns (address[] memory channels); /** * @dev Retrieve the conduit creation code and runtime code hashes. */ function getConduitCodeHashes() external view returns (bytes32 creationCodeHash, bytes32 runtimeCodeHash); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; import { ConduitItemType } from "./lib/ConduitEnums.sol"; import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; import { ConduitTransfer, ConduitBatch1155Transfer } from "./lib/ConduitStructs.sol"; import "./lib/ConduitConstants.sol"; /** * @title Conduit * @author 0age * @notice This contract serves as an originator for "proxied" transfers. Each * conduit is deployed and controlled by a "conduit controller" that can * add and remove "channels" or contracts that can instruct the conduit * to transfer approved ERC20/721/1155 tokens. *IMPORTANT NOTE: each * conduit has an owner that can arbitrarily add or remove channels, and * a malicious or negligent owner can add a channel that allows for any * approved ERC20/721/1155 tokens to be taken immediately — be extremely * cautious with what conduits you give token approvals to!* */ contract Conduit is ConduitInterface, TokenTransferrer { // Set deployer as an immutable controller that can update channel statuses. address private immutable _controller; // Track the status of each channel. mapping(address => bool) private _channels; /** * @notice Ensure that the caller is currently registered as an open channel * on the conduit. */ modifier onlyOpenChannel() { // Utilize assembly to access channel storage mapping directly. assembly { // Write the caller to scratch space. mstore(ChannelKey_channel_ptr, caller()) // Write the storage slot for _channels to scratch space. mstore(ChannelKey_slot_ptr, _channels.slot) // Derive the position in storage of _channels[msg.sender] // and check if the stored value is zero. if iszero( sload(keccak256(ChannelKey_channel_ptr, ChannelKey_length)) ) { // The caller is not an open channel; revert with // ChannelClosed(caller). First, set error signature in memory. mstore(ChannelClosed_error_ptr, ChannelClosed_error_signature) // Next, set the caller as the argument. mstore(ChannelClosed_channel_ptr, caller()) // Finally, revert, returning full custom error with argument. revert(ChannelClosed_error_ptr, ChannelClosed_error_length) } } // Continue with function execution. _; } /** * @notice In the constructor, set the deployer as the controller. */ constructor() { // Set the deployer as the controller. _controller = msg.sender; } /** * @notice Execute a sequence of ERC20/721/1155 transfers. Only a caller * with an open channel can call this function. Note that channels * are expected to implement reentrancy protection if desired, and * that cross-channel reentrancy may be possible if the conduit has * multiple open channels at once. Also note that channels are * expected to implement checks against transferring any zero-amount * items if that constraint is desired. * * @param transfers The ERC20/721/1155 transfers to perform. * * @return magicValue A magic value indicating that the transfers were * performed successfully. */ function execute(ConduitTransfer[] calldata transfers) external override onlyOpenChannel returns (bytes4 magicValue) { // Retrieve the total number of transfers and place on the stack. uint256 totalStandardTransfers = transfers.length; // Iterate over each transfer. for (uint256 i = 0; i < totalStandardTransfers; ) { // Retrieve the transfer in question and perform the transfer. _transfer(transfers[i]); // Skip overflow check as for loop is indexed starting at zero. unchecked { ++i; } } // Return a magic value indicating that the transfers were performed. magicValue = this.execute.selector; } /** * @notice Execute a sequence of batch 1155 item transfers. Only a caller * with an open channel can call this function. Note that channels * are expected to implement reentrancy protection if desired, and * that cross-channel reentrancy may be possible if the conduit has * multiple open channels at once. Also note that channels are * expected to implement checks against transferring any zero-amount * items if that constraint is desired. * * @param batchTransfers The 1155 batch item transfers to perform. * * @return magicValue A magic value indicating that the item transfers were * performed successfully. */ function executeBatch1155( ConduitBatch1155Transfer[] calldata batchTransfers ) external override onlyOpenChannel returns (bytes4 magicValue) { // Perform 1155 batch transfers. Note that memory should be considered // entirely corrupted from this point forward. _performERC1155BatchTransfers(batchTransfers); // Return a magic value indicating that the transfers were performed. magicValue = this.executeBatch1155.selector; } /** * @notice Execute a sequence of transfers, both single ERC20/721/1155 item * transfers as well as batch 1155 item transfers. Only a caller * with an open channel can call this function. Note that channels * are expected to implement reentrancy protection if desired, and * that cross-channel reentrancy may be possible if the conduit has * multiple open channels at once. Also note that channels are * expected to implement checks against transferring any zero-amount * items if that constraint is desired. * * @param standardTransfers The ERC20/721/1155 item transfers to perform. * @param batchTransfers The 1155 batch item transfers to perform. * * @return magicValue A magic value indicating that the item transfers were * performed successfully. */ function executeWithBatch1155( ConduitTransfer[] calldata standardTransfers, ConduitBatch1155Transfer[] calldata batchTransfers ) external override onlyOpenChannel returns (bytes4 magicValue) { // Retrieve the total number of transfers and place on the stack. uint256 totalStandardTransfers = standardTransfers.length; // Iterate over each standard transfer. for (uint256 i = 0; i < totalStandardTransfers; ) { // Retrieve the transfer in question and perform the transfer. _transfer(standardTransfers[i]); // Skip overflow check as for loop is indexed starting at zero. unchecked { ++i; } } // Perform 1155 batch transfers. Note that memory should be considered // entirely corrupted from this point forward aside from the free memory // pointer having the default value. _performERC1155BatchTransfers(batchTransfers); // Return a magic value indicating that the transfers were performed. magicValue = this.executeWithBatch1155.selector; } /** * @notice Open or close a given channel. Only callable by the controller. * * @param channel The channel to open or close. * @param isOpen The status of the channel (either open or closed). */ function updateChannel(address channel, bool isOpen) external override { // Ensure that the caller is the controller of this contract. if (msg.sender != _controller) { revert InvalidController(); } // Ensure that the channel does not already have the indicated status. if (_channels[channel] == isOpen) { revert ChannelStatusAlreadySet(channel, isOpen); } // Update the status of the channel. _channels[channel] = isOpen; // Emit a corresponding event. emit ChannelUpdated(channel, isOpen); } /** * @dev Internal function to transfer a given ERC20/721/1155 item. Note that * channels are expected to implement checks against transferring any * zero-amount items if that constraint is desired. * * @param item The ERC20/721/1155 item to transfer. */ function _transfer(ConduitTransfer calldata item) internal { // Determine the transfer method based on the respective item type. if (item.itemType == ConduitItemType.ERC20) { // Transfer ERC20 token. Note that item.identifier is ignored and // therefore ERC20 transfer items are potentially malleable — this // check should be performed by the calling channel if a constraint // on item malleability is desired. _performERC20Transfer(item.token, item.from, item.to, item.amount); } else if (item.itemType == ConduitItemType.ERC721) { // Ensure that exactly one 721 item is being transferred. if (item.amount != 1) { revert InvalidERC721TransferAmount(); } // Transfer ERC721 token. _performERC721Transfer( item.token, item.from, item.to, item.identifier ); } else if (item.itemType == ConduitItemType.ERC1155) { // Transfer ERC1155 token. _performERC1155Transfer( item.token, item.from, item.to, item.identifier, item.amount ); } else { // Throw with an error. revert InvalidItemType(); } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import { ConduitItemType } from "./ConduitEnums.sol"; struct ConduitTransfer { ConduitItemType itemType; address token; address from; address to; uint256 identifier; uint256 amount; } struct ConduitBatch1155Transfer { address token; address from; address to; uint256[] ids; uint256[] amounts; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import { TransferHelperItem, TransferHelperItemsWithRecipient } from "../helpers/TransferHelperStructs.sol"; interface TransferHelperInterface { /** * @notice Transfer multiple items to a single recipient. * * @param items The items to transfer. * @param conduitKey The key of the conduit performing the bulk transfer. */ function bulkTransfer( TransferHelperItemsWithRecipient[] calldata items, bytes32 conduitKey ) external returns (bytes4); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; /** * @title TransferHelperErrors */ interface TransferHelperErrors { /** * @dev Revert with an error when attempting to execute transfers with a * NATIVE itemType. */ error InvalidItemType(); /** * @dev Revert with an error when an ERC721 transfer with amount other than * one is attempted. */ error InvalidERC721TransferAmount(); /** * @dev Revert with an error when attempting to execute an ERC721 transfer * to an invalid recipient. */ error InvalidERC721Recipient(address recipient); /** * @dev Revert with an error when a call to a ERC721 receiver reverts with * bytes data. */ error ERC721ReceiverErrorRevertBytes( bytes reason, address receiver, address sender, uint256 identifier ); /** * @dev Revert with an error when a call to a ERC721 receiver reverts with * string reason. */ error ERC721ReceiverErrorRevertString( string reason, address receiver, address sender, uint256 identifier ); /** * @dev Revert with an error when an ERC20 token has an invalid identifier. */ error InvalidERC20Identifier(); /** * @dev Revert with an error if the recipient is the zero address. */ error RecipientCannotBeZeroAddress(); /** * @dev Revert with an error when attempting to fill an order referencing an * invalid conduit (i.e. one that has not been deployed). */ error InvalidConduit(bytes32 conduitKey, address conduit); /** * @dev Revert with an error when a call to a conduit reverts with a * reason string. */ error ConduitErrorRevertString( string reason, bytes32 conduitKey, address conduit ); /** * @dev Revert with an error when a call to a conduit reverts with bytes * data. */ error ConduitErrorRevertBytes( bytes reason, bytes32 conduitKey, address conduit ); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; enum ConduitItemType { NATIVE, // unused ERC20, ERC721, ERC1155 } // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import "./TokenTransferrerConstants.sol"; import { TokenTransferrerErrors } from "../interfaces/TokenTransferrerErrors.sol"; import { ConduitBatch1155Transfer } from "../conduit/lib/ConduitStructs.sol"; /** * @title TokenTransferrer * @author 0age * @custom:coauthor d1ll0n * @custom:coauthor transmissions11 * @notice TokenTransferrer is a library for performing optimized ERC20, ERC721, * ERC1155, and batch ERC1155 transfers, used by both Seaport as well as * by conduits deployed by the ConduitController. Use great caution when * considering these functions for use in other codebases, as there are * significant side effects and edge cases that need to be thoroughly * understood and carefully addressed. */ contract TokenTransferrer is TokenTransferrerErrors { /** * @dev Internal function to transfer ERC20 tokens from a given originator * to a given recipient. Sufficient approvals must be set on the * contract performing the transfer. * * @param token The ERC20 token to transfer. * @param from The originator of the transfer. * @param to The recipient of the transfer. * @param amount The amount to transfer. */ function _performERC20Transfer( address token, address from, address to, uint256 amount ) internal { // Utilize assembly to perform an optimized ERC20 token transfer. assembly { // The free memory pointer memory slot will be used when populating // call data for the transfer; read the value and restore it later. let memPointer := mload(FreeMemoryPointerSlot) // Write call data into memory, starting with function selector. mstore(ERC20_transferFrom_sig_ptr, ERC20_transferFrom_signature) mstore(ERC20_transferFrom_from_ptr, from) mstore(ERC20_transferFrom_to_ptr, to) mstore(ERC20_transferFrom_amount_ptr, amount) // Make call & copy up to 32 bytes of return data to scratch space. // Scratch space does not need to be cleared ahead of time, as the // subsequent check will ensure that either at least a full word of // return data is received (in which case it will be overwritten) or // that no data is received (in which case scratch space will be // ignored) on a successful call to the given token. let callStatus := call( gas(), token, 0, ERC20_transferFrom_sig_ptr, ERC20_transferFrom_length, 0, OneWord ) // Determine whether transfer was successful using status & result. let success := and( // Set success to whether the call reverted, if not check it // either returned exactly 1 (can't just be non-zero data), or // had no return data. or( and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize()) ), callStatus ) // Handle cases where either the transfer failed or no data was // returned. Group these, as most transfers will succeed with data. // Equivalent to `or(iszero(success), iszero(returndatasize()))` // but after it's inverted for JUMPI this expression is cheaper. if iszero(and(success, iszero(iszero(returndatasize())))) { // If the token has no code or the transfer failed: Equivalent // to `or(iszero(success), iszero(extcodesize(token)))` but // after it's inverted for JUMPI this expression is cheaper. if iszero(and(iszero(iszero(extcodesize(token))), success)) { // If the transfer failed: if iszero(success) { // If it was due to a revert: if iszero(callStatus) { // If it returned a message, bubble it up as long as // sufficient gas remains to do so: if returndatasize() { // Ensure that sufficient gas is available to // copy returndata while expanding memory where // necessary. Start by computing the word size // of returndata and allocated memory. Round up // to the nearest full word. let returnDataWords := div( add(returndatasize(), AlmostOneWord), OneWord ) // Note: use the free memory pointer in place of // msize() to work around a Yul warning that // prevents accessing msize directly when the IR // pipeline is activated. let msizeWords := div(memPointer, OneWord) // Next, compute the cost of the returndatacopy. let cost := mul(CostPerWord, returnDataWords) // Then, compute cost of new memory allocation. if gt(returnDataWords, msizeWords) { cost := add( cost, add( mul( sub( returnDataWords, msizeWords ), CostPerWord ), div( sub( mul( returnDataWords, returnDataWords ), mul(msizeWords, msizeWords) ), MemoryExpansionCoefficient ) ) ) } // Finally, add a small constant and compare to // gas remaining; bubble up the revert data if // enough gas is still available. if lt(add(cost, ExtraGasBuffer), gas()) { // Copy returndata to memory; overwrite // existing memory. returndatacopy(0, 0, returndatasize()) // Revert, specifying memory region with // copied returndata. revert(0, returndatasize()) } } // Otherwise revert with a generic error message. mstore( TokenTransferGenericFailure_error_sig_ptr, TokenTransferGenericFailure_error_signature ) mstore( TokenTransferGenericFailure_error_token_ptr, token ) mstore( TokenTransferGenericFailure_error_from_ptr, from ) mstore(TokenTransferGenericFailure_error_to_ptr, to) mstore(TokenTransferGenericFailure_error_id_ptr, 0) mstore( TokenTransferGenericFailure_error_amount_ptr, amount ) revert( TokenTransferGenericFailure_error_sig_ptr, TokenTransferGenericFailure_error_length ) } // Otherwise revert with a message about the token // returning false or non-compliant return values. mstore( BadReturnValueFromERC20OnTransfer_error_sig_ptr, BadReturnValueFromERC20OnTransfer_error_signature ) mstore( BadReturnValueFromERC20OnTransfer_error_token_ptr, token ) mstore( BadReturnValueFromERC20OnTransfer_error_from_ptr, from ) mstore( BadReturnValueFromERC20OnTransfer_error_to_ptr, to ) mstore( BadReturnValueFromERC20OnTransfer_error_amount_ptr, amount ) revert( BadReturnValueFromERC20OnTransfer_error_sig_ptr, BadReturnValueFromERC20OnTransfer_error_length ) } // Otherwise, revert with error about token not having code: mstore(NoContract_error_sig_ptr, NoContract_error_signature) mstore(NoContract_error_token_ptr, token) revert(NoContract_error_sig_ptr, NoContract_error_length) } // Otherwise, the token just returned no data despite the call // having succeeded; no need to optimize for this as it's not // technically ERC20 compliant. } // Restore the original free memory pointer. mstore(FreeMemoryPointerSlot, memPointer) // Restore the zero slot to zero. mstore(ZeroSlot, 0) } } /** * @dev Internal function to transfer an ERC721 token from a given * originator to a given recipient. Sufficient approvals must be set on * the contract performing the transfer. Note that this function does * not check whether the receiver can accept the ERC721 token (i.e. it * does not use `safeTransferFrom`). * * @param token The ERC721 token to transfer. * @param from The originator of the transfer. * @param to The recipient of the transfer. * @param identifier The tokenId to transfer. */ function _performERC721Transfer( address token, address from, address to, uint256 identifier ) internal { // Utilize assembly to perform an optimized ERC721 token transfer. assembly { // If the token has no code, revert. if iszero(extcodesize(token)) { mstore(NoContract_error_sig_ptr, NoContract_error_signature) mstore(NoContract_error_token_ptr, token) revert(NoContract_error_sig_ptr, NoContract_error_length) } // The free memory pointer memory slot will be used when populating // call data for the transfer; read the value and restore it later. let memPointer := mload(FreeMemoryPointerSlot) // Write call data to memory starting with function selector. mstore(ERC721_transferFrom_sig_ptr, ERC721_transferFrom_signature) mstore(ERC721_transferFrom_from_ptr, from) mstore(ERC721_transferFrom_to_ptr, to) mstore(ERC721_transferFrom_id_ptr, identifier) // Perform the call, ignoring return data. let success := call( gas(), token, 0, ERC721_transferFrom_sig_ptr, ERC721_transferFrom_length, 0, 0 ) // If the transfer reverted: if iszero(success) { // If it returned a message, bubble it up as long as sufficient // gas remains to do so: if returndatasize() { // Ensure that sufficient gas is available to copy // returndata while expanding memory where necessary. Start // by computing word size of returndata & allocated memory. // Round up to the nearest full word. let returnDataWords := div( add(returndatasize(), AlmostOneWord), OneWord ) // Note: use the free memory pointer in place of msize() to // work around a Yul warning that prevents accessing msize // directly when the IR pipeline is activated. let msizeWords := div(memPointer, OneWord) // Next, compute the cost of the returndatacopy. let cost := mul(CostPerWord, returnDataWords) // Then, compute cost of new memory allocation. if gt(returnDataWords, msizeWords) { cost := add( cost, add( mul( sub(returnDataWords, msizeWords), CostPerWord ), div( sub( mul(returnDataWords, returnDataWords), mul(msizeWords, msizeWords) ), MemoryExpansionCoefficient ) ) ) } // Finally, add a small constant and compare to gas // remaining; bubble up the revert data if enough gas is // still available. if lt(add(cost, ExtraGasBuffer), gas()) { // Copy returndata to memory; overwrite existing memory. returndatacopy(0, 0, returndatasize()) // Revert, giving memory region with copied returndata. revert(0, returndatasize()) } } // Otherwise revert with a generic error message. mstore( TokenTransferGenericFailure_error_sig_ptr, TokenTransferGenericFailure_error_signature ) mstore(TokenTransferGenericFailure_error_token_ptr, token) mstore(TokenTransferGenericFailure_error_from_ptr, from) mstore(TokenTransferGenericFailure_error_to_ptr, to) mstore(TokenTransferGenericFailure_error_id_ptr, identifier) mstore(TokenTransferGenericFailure_error_amount_ptr, 1) revert( TokenTransferGenericFailure_error_sig_ptr, TokenTransferGenericFailure_error_length ) } // Restore the original free memory pointer. mstore(FreeMemoryPointerSlot, memPointer) // Restore the zero slot to zero. mstore(ZeroSlot, 0) } } /** * @dev Internal function to transfer ERC1155 tokens from a given * originator to a given recipient. Sufficient approvals must be set on * the contract performing the transfer and contract recipients must * implement the ERC1155TokenReceiver interface to indicate that they * are willing to accept the transfer. * * @param token The ERC1155 token to transfer. * @param from The originator of the transfer. * @param to The recipient of the transfer. * @param identifier The id to transfer. * @param amount The amount to transfer. */ function _performERC1155Transfer( address token, address from, address to, uint256 identifier, uint256 amount ) internal { // Utilize assembly to perform an optimized ERC1155 token transfer. assembly { // If the token has no code, revert. if iszero(extcodesize(token)) { mstore(NoContract_error_sig_ptr, NoContract_error_signature) mstore(NoContract_error_token_ptr, token) revert(NoContract_error_sig_ptr, NoContract_error_length) } // The following memory slots will be used when populating call data // for the transfer; read the values and restore them later. let memPointer := mload(FreeMemoryPointerSlot) let slot0x80 := mload(Slot0x80) let slot0xA0 := mload(Slot0xA0) let slot0xC0 := mload(Slot0xC0) // Write call data into memory, beginning with function selector. mstore( ERC1155_safeTransferFrom_sig_ptr, ERC1155_safeTransferFrom_signature ) mstore(ERC1155_safeTransferFrom_from_ptr, from) mstore(ERC1155_safeTransferFrom_to_ptr, to) mstore(ERC1155_safeTransferFrom_id_ptr, identifier) mstore(ERC1155_safeTransferFrom_amount_ptr, amount) mstore( ERC1155_safeTransferFrom_data_offset_ptr, ERC1155_safeTransferFrom_data_length_offset ) mstore(ERC1155_safeTransferFrom_data_length_ptr, 0) // Perform the call, ignoring return data. let success := call( gas(), token, 0, ERC1155_safeTransferFrom_sig_ptr, ERC1155_safeTransferFrom_length, 0, 0 ) // If the transfer reverted: if iszero(success) { // If it returned a message, bubble it up as long as sufficient // gas remains to do so: if returndatasize() { // Ensure that sufficient gas is available to copy // returndata while expanding memory where necessary. Start // by computing word size of returndata & allocated memory. // Round up to the nearest full word. let returnDataWords := div( add(returndatasize(), AlmostOneWord), OneWord ) // Note: use the free memory pointer in place of msize() to // work around a Yul warning that prevents accessing msize // directly when the IR pipeline is activated. let msizeWords := div(memPointer, OneWord) // Next, compute the cost of the returndatacopy. let cost := mul(CostPerWord, returnDataWords) // Then, compute cost of new memory allocation. if gt(returnDataWords, msizeWords) { cost := add( cost, add( mul( sub(returnDataWords, msizeWords), CostPerWord ), div( sub( mul(returnDataWords, returnDataWords), mul(msizeWords, msizeWords) ), MemoryExpansionCoefficient ) ) ) } // Finally, add a small constant and compare to gas // remaining; bubble up the revert data if enough gas is // still available. if lt(add(cost, ExtraGasBuffer), gas()) { // Copy returndata to memory; overwrite existing memory. returndatacopy(0, 0, returndatasize()) // Revert, giving memory region with copied returndata. revert(0, returndatasize()) } } // Otherwise revert with a generic error message. mstore( TokenTransferGenericFailure_error_sig_ptr, TokenTransferGenericFailure_error_signature ) mstore(TokenTransferGenericFailure_error_token_ptr, token) mstore(TokenTransferGenericFailure_error_from_ptr, from) mstore(TokenTransferGenericFailure_error_to_ptr, to) mstore(TokenTransferGenericFailure_error_id_ptr, identifier) mstore(TokenTransferGenericFailure_error_amount_ptr, amount) revert( TokenTransferGenericFailure_error_sig_ptr, TokenTransferGenericFailure_error_length ) } mstore(Slot0x80, slot0x80) // Restore slot 0x80. mstore(Slot0xA0, slot0xA0) // Restore slot 0xA0. mstore(Slot0xC0, slot0xC0) // Restore slot 0xC0. // Restore the original free memory pointer. mstore(FreeMemoryPointerSlot, memPointer) // Restore the zero slot to zero. mstore(ZeroSlot, 0) } } /** * @dev Internal function to transfer ERC1155 tokens from a given * originator to a given recipient. Sufficient approvals must be set on * the contract performing the transfer and contract recipients must * implement the ERC1155TokenReceiver interface to indicate that they * are willing to accept the transfer. NOTE: this function is not * memory-safe; it will overwrite existing memory, restore the free * memory pointer to the default value, and overwrite the zero slot. * This function should only be called once memory is no longer * required and when uninitialized arrays are not utilized, and memory * should be considered fully corrupted (aside from the existence of a * default-value free memory pointer) after calling this function. * * @param batchTransfers The group of 1155 batch transfers to perform. */ function _performERC1155BatchTransfers( ConduitBatch1155Transfer[] calldata batchTransfers ) internal { // Utilize assembly to perform optimized batch 1155 transfers. assembly { let len := batchTransfers.length // Pointer to first head in the array, which is offset to the struct // at each index. This gets incremented after each loop to avoid // multiplying by 32 to get the offset for each element. let nextElementHeadPtr := batchTransfers.offset // Pointer to beginning of the head of the array. This is the // reference position each offset references. It's held static to // let each loop calculate the data position for an element. let arrayHeadPtr := nextElementHeadPtr // Write the function selector, which will be reused for each call: // safeBatchTransferFrom(address,address,uint256[],uint256[],bytes) mstore( ConduitBatch1155Transfer_from_offset, ERC1155_safeBatchTransferFrom_signature ) // Iterate over each batch transfer. for { let i := 0 } lt(i, len) { i := add(i, 1) } { // Read the offset to the beginning of the element and add // it to pointer to the beginning of the array head to get // the absolute position of the element in calldata. let elementPtr := add( arrayHeadPtr, calldataload(nextElementHeadPtr) ) // Retrieve the token from calldata. let token := calldataload(elementPtr) // If the token has no code, revert. if iszero(extcodesize(token)) { mstore(NoContract_error_sig_ptr, NoContract_error_signature) mstore(NoContract_error_token_ptr, token) revert(NoContract_error_sig_ptr, NoContract_error_length) } // Get the total number of supplied ids. let idsLength := calldataload( add(elementPtr, ConduitBatch1155Transfer_ids_length_offset) ) // Determine the expected offset for the amounts array. let expectedAmountsOffset := add( ConduitBatch1155Transfer_amounts_length_baseOffset, mul(idsLength, OneWord) ) // Validate struct encoding. let invalidEncoding := iszero( and( // ids.length == amounts.length eq( idsLength, calldataload(add(elementPtr, expectedAmountsOffset)) ), and( // ids_offset == 0xa0 eq( calldataload( add( elementPtr, ConduitBatch1155Transfer_ids_head_offset ) ), ConduitBatch1155Transfer_ids_length_offset ), // amounts_offset == 0xc0 + ids.length*32 eq( calldataload( add( elementPtr, ConduitBatchTransfer_amounts_head_offset ) ), expectedAmountsOffset ) ) ) ) // Revert with an error if the encoding is not valid. if invalidEncoding { mstore( Invalid1155BatchTransferEncoding_ptr, Invalid1155BatchTransferEncoding_selector ) revert( Invalid1155BatchTransferEncoding_ptr, Invalid1155BatchTransferEncoding_length ) } // Update the offset position for the next loop nextElementHeadPtr := add(nextElementHeadPtr, OneWord) // Copy the first section of calldata (before dynamic values). calldatacopy( BatchTransfer1155Params_ptr, add(elementPtr, ConduitBatch1155Transfer_from_offset), ConduitBatch1155Transfer_usable_head_size ) // Determine size of calldata required for ids and amounts. Note // that the size includes both lengths as well as the data. let idsAndAmountsSize := add(TwoWords, mul(idsLength, TwoWords)) // Update the offset for the data array in memory. mstore( BatchTransfer1155Params_data_head_ptr, add( BatchTransfer1155Params_ids_length_offset, idsAndAmountsSize ) ) // Set the length of the data array in memory to zero. mstore( add( BatchTransfer1155Params_data_length_basePtr, idsAndAmountsSize ), 0 ) // Determine the total calldata size for the call to transfer. let transferDataSize := add( BatchTransfer1155Params_calldata_baseSize, idsAndAmountsSize ) // Copy second section of calldata (including dynamic values). calldatacopy( BatchTransfer1155Params_ids_length_ptr, add(elementPtr, ConduitBatch1155Transfer_ids_length_offset), idsAndAmountsSize ) // Perform the call to transfer 1155 tokens. let success := call( gas(), token, 0, ConduitBatch1155Transfer_from_offset, // Data portion start. transferDataSize, // Location of the length of callData. 0, 0 ) // If the transfer reverted: if iszero(success) { // If it returned a message, bubble it up as long as // sufficient gas remains to do so: if returndatasize() { // Ensure that sufficient gas is available to copy // returndata while expanding memory where necessary. // Start by computing word size of returndata and // allocated memory. Round up to the nearest full word. let returnDataWords := div( add(returndatasize(), AlmostOneWord), OneWord ) // Note: use transferDataSize in place of msize() to // work around a Yul warning that prevents accessing // msize directly when the IR pipeline is activated. // The free memory pointer is not used here because // this function does almost all memory management // manually and does not update it, and transferDataSize // should be the largest memory value used (unless a // previous batch was larger). let msizeWords := div(transferDataSize, OneWord) // Next, compute the cost of the returndatacopy. let cost := mul(CostPerWord, returnDataWords) // Then, compute cost of new memory allocation. if gt(returnDataWords, msizeWords) { cost := add( cost, add( mul( sub(returnDataWords, msizeWords), CostPerWord ), div( sub( mul( returnDataWords, returnDataWords ), mul(msizeWords, msizeWords) ), MemoryExpansionCoefficient ) ) ) } // Finally, add a small constant and compare to gas // remaining; bubble up the revert data if enough gas is // still available. if lt(add(cost, ExtraGasBuffer), gas()) { // Copy returndata to memory; overwrite existing. returndatacopy(0, 0, returndatasize()) // Revert with memory region containing returndata. revert(0, returndatasize()) } } // Set the error signature. mstore( 0, ERC1155BatchTransferGenericFailure_error_signature ) // Write the token. mstore(ERC1155BatchTransferGenericFailure_token_ptr, token) // Increase the offset to ids by 32. mstore( BatchTransfer1155Params_ids_head_ptr, ERC1155BatchTransferGenericFailure_ids_offset ) // Increase the offset to amounts by 32. mstore( BatchTransfer1155Params_amounts_head_ptr, add( OneWord, mload(BatchTransfer1155Params_amounts_head_ptr) ) ) // Return modified region. The total size stays the same as // `token` uses the same number of bytes as `data.length`. revert(0, transferDataSize) } } // Reset the free memory pointer to the default value; memory must // be assumed to be dirtied and not reused from this point forward. // Also note that the zero slot is not reset to zero, meaning empty // arrays cannot be safely created or utilized until it is restored. mstore(FreeMemoryPointerSlot, DefaultFreeMemoryPointer) } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; // error ChannelClosed(address channel) uint256 constant ChannelClosed_error_signature = ( 0x93daadf200000000000000000000000000000000000000000000000000000000 ); uint256 constant ChannelClosed_error_ptr = 0x00; uint256 constant ChannelClosed_channel_ptr = 0x4; uint256 constant ChannelClosed_error_length = 0x24; // For the mapping: // mapping(address => bool) channels // The position in storage for a particular account is: // keccak256(abi.encode(account, channels.slot)) uint256 constant ChannelKey_channel_ptr = 0x00; uint256 constant ChannelKey_slot_ptr = 0x20; uint256 constant ChannelKey_length = 0x40; // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; /* * -------------------------- Disambiguation & Other Notes --------------------- * - The term "head" is used as it is in the documentation for ABI encoding, * but only in reference to dynamic types, i.e. it always refers to the * offset or pointer to the body of a dynamic type. In calldata, the head * is always an offset (relative to the parent object), while in memory, * the head is always the pointer to the body. More information found here: * https://docs.soliditylang.org/en/v0.8.14/abi-spec.html#argument-encoding * - Note that the length of an array is separate from and precedes the * head of the array. * * - The term "body" is used in place of the term "head" used in the ABI * documentation. It refers to the start of the data for a dynamic type, * e.g. the first word of a struct or the first word of the first element * in an array. * * - The term "pointer" is used to describe the absolute position of a value * and never an offset relative to another value. * - The suffix "_ptr" refers to a memory pointer. * - The suffix "_cdPtr" refers to a calldata pointer. * * - The term "offset" is used to describe the position of a value relative * to some parent value. For example, OrderParameters_conduit_offset is the * offset to the "conduit" value in the OrderParameters struct relative to * the start of the body. * - Note: Offsets are used to derive pointers. * * - Some structs have pointers defined for all of their fields in this file. * Lines which are commented out are fields that are not used in the * codebase but have been left in for readability. */ uint256 constant AlmostOneWord = 0x1f; uint256 constant OneWord = 0x20; uint256 constant TwoWords = 0x40; uint256 constant ThreeWords = 0x60; uint256 constant FreeMemoryPointerSlot = 0x40; uint256 constant ZeroSlot = 0x60; uint256 constant DefaultFreeMemoryPointer = 0x80; uint256 constant Slot0x80 = 0x80; uint256 constant Slot0xA0 = 0xa0; uint256 constant Slot0xC0 = 0xc0; // abi.encodeWithSignature("transferFrom(address,address,uint256)") uint256 constant ERC20_transferFrom_signature = ( 0x23b872dd00000000000000000000000000000000000000000000000000000000 ); uint256 constant ERC20_transferFrom_sig_ptr = 0x0; uint256 constant ERC20_transferFrom_from_ptr = 0x04; uint256 constant ERC20_transferFrom_to_ptr = 0x24; uint256 constant ERC20_transferFrom_amount_ptr = 0x44; uint256 constant ERC20_transferFrom_length = 0x64; // 4 + 32 * 3 == 100 // abi.encodeWithSignature( // "safeTransferFrom(address,address,uint256,uint256,bytes)" // ) uint256 constant ERC1155_safeTransferFrom_signature = ( 0xf242432a00000000000000000000000000000000000000000000000000000000 ); uint256 constant ERC1155_safeTransferFrom_sig_ptr = 0x0; uint256 constant ERC1155_safeTransferFrom_from_ptr = 0x04; uint256 constant ERC1155_safeTransferFrom_to_ptr = 0x24; uint256 constant ERC1155_safeTransferFrom_id_ptr = 0x44; uint256 constant ERC1155_safeTransferFrom_amount_ptr = 0x64; uint256 constant ERC1155_safeTransferFrom_data_offset_ptr = 0x84; uint256 constant ERC1155_safeTransferFrom_data_length_ptr = 0xa4; uint256 constant ERC1155_safeTransferFrom_length = 0xc4; // 4 + 32 * 6 == 196 uint256 constant ERC1155_safeTransferFrom_data_length_offset = 0xa0; // abi.encodeWithSignature( // "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)" // ) uint256 constant ERC1155_safeBatchTransferFrom_signature = ( 0x2eb2c2d600000000000000000000000000000000000000000000000000000000 ); bytes4 constant ERC1155_safeBatchTransferFrom_selector = bytes4( bytes32(ERC1155_safeBatchTransferFrom_signature) ); uint256 constant ERC721_transferFrom_signature = ERC20_transferFrom_signature; uint256 constant ERC721_transferFrom_sig_ptr = 0x0; uint256 constant ERC721_transferFrom_from_ptr = 0x04; uint256 constant ERC721_transferFrom_to_ptr = 0x24; uint256 constant ERC721_transferFrom_id_ptr = 0x44; uint256 constant ERC721_transferFrom_length = 0x64; // 4 + 32 * 3 == 100 // abi.encodeWithSignature("NoContract(address)") uint256 constant NoContract_error_signature = ( 0x5f15d67200000000000000000000000000000000000000000000000000000000 ); uint256 constant NoContract_error_sig_ptr = 0x0; uint256 constant NoContract_error_token_ptr = 0x4; uint256 constant NoContract_error_length = 0x24; // 4 + 32 == 36 // abi.encodeWithSignature( // "TokenTransferGenericFailure(address,address,address,uint256,uint256)" // ) uint256 constant TokenTransferGenericFailure_error_signature = ( 0xf486bc8700000000000000000000000000000000000000000000000000000000 ); uint256 constant TokenTransferGenericFailure_error_sig_ptr = 0x0; uint256 constant TokenTransferGenericFailure_error_token_ptr = 0x4; uint256 constant TokenTransferGenericFailure_error_from_ptr = 0x24; uint256 constant TokenTransferGenericFailure_error_to_ptr = 0x44; uint256 constant TokenTransferGenericFailure_error_id_ptr = 0x64; uint256 constant TokenTransferGenericFailure_error_amount_ptr = 0x84; // 4 + 32 * 5 == 164 uint256 constant TokenTransferGenericFailure_error_length = 0xa4; // abi.encodeWithSignature( // "BadReturnValueFromERC20OnTransfer(address,address,address,uint256)" // ) uint256 constant BadReturnValueFromERC20OnTransfer_error_signature = ( 0x9889192300000000000000000000000000000000000000000000000000000000 ); uint256 constant BadReturnValueFromERC20OnTransfer_error_sig_ptr = 0x0; uint256 constant BadReturnValueFromERC20OnTransfer_error_token_ptr = 0x4; uint256 constant BadReturnValueFromERC20OnTransfer_error_from_ptr = 0x24; uint256 constant BadReturnValueFromERC20OnTransfer_error_to_ptr = 0x44; uint256 constant BadReturnValueFromERC20OnTransfer_error_amount_ptr = 0x64; // 4 + 32 * 4 == 132 uint256 constant BadReturnValueFromERC20OnTransfer_error_length = 0x84; uint256 constant ExtraGasBuffer = 0x20; uint256 constant CostPerWord = 3; uint256 constant MemoryExpansionCoefficient = 0x200; // Values are offset by 32 bytes in order to write the token to the beginning // in the event of a revert uint256 constant BatchTransfer1155Params_ptr = 0x24; uint256 constant BatchTransfer1155Params_ids_head_ptr = 0x64; uint256 constant BatchTransfer1155Params_amounts_head_ptr = 0x84; uint256 constant BatchTransfer1155Params_data_head_ptr = 0xa4; uint256 constant BatchTransfer1155Params_data_length_basePtr = 0xc4; uint256 constant BatchTransfer1155Params_calldata_baseSize = 0xc4; uint256 constant BatchTransfer1155Params_ids_length_ptr = 0xc4; uint256 constant BatchTransfer1155Params_ids_length_offset = 0xa0; uint256 constant BatchTransfer1155Params_amounts_length_baseOffset = 0xc0; uint256 constant BatchTransfer1155Params_data_length_baseOffset = 0xe0; uint256 constant ConduitBatch1155Transfer_usable_head_size = 0x80; uint256 constant ConduitBatch1155Transfer_from_offset = 0x20; uint256 constant ConduitBatch1155Transfer_ids_head_offset = 0x60; uint256 constant ConduitBatch1155Transfer_amounts_head_offset = 0x80; uint256 constant ConduitBatch1155Transfer_ids_length_offset = 0xa0; uint256 constant ConduitBatch1155Transfer_amounts_length_baseOffset = 0xc0; uint256 constant ConduitBatch1155Transfer_calldata_baseSize = 0xc0; // Note: abbreviated version of above constant to adhere to line length limit. uint256 constant ConduitBatchTransfer_amounts_head_offset = 0x80; uint256 constant Invalid1155BatchTransferEncoding_ptr = 0x00; uint256 constant Invalid1155BatchTransferEncoding_length = 0x04; uint256 constant Invalid1155BatchTransferEncoding_selector = ( 0xeba2084c00000000000000000000000000000000000000000000000000000000 ); uint256 constant ERC1155BatchTransferGenericFailure_error_signature = ( 0xafc445e200000000000000000000000000000000000000000000000000000000 ); uint256 constant ERC1155BatchTransferGenericFailure_token_ptr = 0x04; uint256 constant ERC1155BatchTransferGenericFailure_ids_offset = 0xc0; // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; /** * @title TokenTransferrerErrors */ interface TokenTransferrerErrors { /** * @dev Revert with an error when an ERC721 transfer with amount other than * one is attempted. */ error InvalidERC721TransferAmount(); /** * @dev Revert with an error when attempting to fulfill an order where an * item has an amount of zero. */ error MissingItemAmount(); /** * @dev Revert with an error when attempting to fulfill an order where an * item has unused parameters. This includes both the token and the * identifier parameters for native transfers as well as the identifier * parameter for ERC20 transfers. Note that the conduit does not * perform this check, leaving it up to the calling channel to enforce * when desired. */ error UnusedItemParameters(); /** * @dev Revert with an error when an ERC20, ERC721, or ERC1155 token * transfer reverts. * * @param token The token for which the transfer was attempted. * @param from The source of the attempted transfer. * @param to The recipient of the attempted transfer. * @param identifier The identifier for the attempted transfer. * @param amount The amount for the attempted transfer. */ error TokenTransferGenericFailure( address token, address from, address to, uint256 identifier, uint256 amount ); /** * @dev Revert with an error when a batch ERC1155 token transfer reverts. * * @param token The token for which the transfer was attempted. * @param from The source of the attempted transfer. * @param to The recipient of the attempted transfer. * @param identifiers The identifiers for the attempted transfer. * @param amounts The amounts for the attempted transfer. */ error ERC1155BatchTransferGenericFailure( address token, address from, address to, uint256[] identifiers, uint256[] amounts ); /** * @dev Revert with an error when an ERC20 token transfer returns a falsey * value. * * @param token The token for which the ERC20 transfer was attempted. * @param from The source of the attempted ERC20 transfer. * @param to The recipient of the attempted ERC20 transfer. * @param amount The amount for the attempted ERC20 transfer. */ error BadReturnValueFromERC20OnTransfer( address token, address from, address to, uint256 amount ); /** * @dev Revert with an error when an account being called as an assumed * contract does not have code and returns no data. * * @param account The account that should contain code. */ error NoContract(address account); /** * @dev Revert with an error when attempting to execute an 1155 batch * transfer using calldata not produced by default ABI encoding or with * different lengths for ids and amounts arrays. */ error Invalid1155BatchTransferEncoding(); }
File 2 of 3: RaribleUserToken
pragma solidity ^0.5.0; pragma experimental ABIEncoderV2; /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ library SafeMath { /** * @dev Multiplies two numbers, throws on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { // Gas optimization: this is cheaper than asserting 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } c = a * b; assert(c / a == b); return c; } /** * @dev Integer division of two numbers, truncating the quotient. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // assert(b > 0); // Solidity automatically throws when dividing by 0 // uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return a / b; } /** * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } /** * @dev Adds two numbers, throws on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256 c) { c = a + b; assert(c >= a); return c; } } /** Note: Simple contract to use as base for const vals */ contract CommonConstants { bytes4 constant internal ERC1155_ACCEPTED = 0xf23a6e61; // bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) bytes4 constant internal ERC1155_BATCH_ACCEPTED = 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")) } /** Note: The ERC-165 identifier for this interface is 0x4e2312e0. */ interface ERC1155TokenReceiver { /** @notice Handle the receipt of a single ERC1155 token type. @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeTransferFrom` after the balance has been updated. This function MUST return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` (i.e. 0xf23a6e61) if it accepts the transfer. This function MUST revert if it rejects the transfer. Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller. @param _operator The address which initiated the transfer (i.e. msg.sender) @param _from The address which previously owned the token @param _id The ID of the token being transferred @param _value 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 An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated. This function MUST return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` (i.e. 0xbc197c81) if it accepts the transfer(s). This function MUST revert if it rejects the transfer(s). Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller. @param _operator The address which initiated the batch transfer (i.e. msg.sender) @param _from The address which previously owned the token @param _ids An array containing ids of each token being transferred (order and length must match _values array) @param _values An array containing amounts of each token being transferred (order and length must match _ids array) @param _data Additional data with no specified format @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` */ function onERC1155BatchReceived(address _operator, address _from, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external returns(bytes4); } /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); } /** @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. */ contract IERC1155 is IERC165 { /** @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). The `_operator` argument MUST be msg.sender. The `_from` argument MUST be the address of the holder whose balance is decreased. The `_to` argument MUST be the address of the recipient whose balance is increased. The `_id` argument MUST be the token type being transferred. The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by. When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). */ 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 (see "Safe Transfer Rules" section of the standard). The `_operator` argument MUST be msg.sender. The `_from` argument MUST be the address of the holder whose balance is decreased. The `_to` argument MUST be the address of the recipient whose balance is increased. The `_ids` argument MUST be the list of tokens being transferred. The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by. When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). */ event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values); /** @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absense of an event assumes disabled). */ 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 URI 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 (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; /** @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; /** @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 (i.e. balance for each (owner, id) pair) */ 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); } /** * @dev Implementation of the {IERC165} interface. * * Contracts may inherit from this and call {_registerInterface} to declare * their support of an interface. */ contract ERC165 is IERC165 { /* * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7 */ bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7; /** * @dev Mapping of interface ids to whether or not it's supported. */ mapping(bytes4 => bool) private _supportedInterfaces; constructor () internal { // Derived contracts need only register support for their own interfaces, // we register support for ERC165 itself here _registerInterface(_INTERFACE_ID_ERC165); } /** * @dev See {IERC165-supportsInterface}. * * Time complexity O(1), guaranteed to always use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool) { return _supportedInterfaces[interfaceId]; } /** * @dev Registers the contract as an implementer of the interface defined by * `interfaceId`. Support of the actual ERC165 interface is automatic and * registering its interface id is not required. * * See {IERC165-supportsInterface}. * * Requirements: * * - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`). */ function _registerInterface(bytes4 interfaceId) internal { require(interfaceId != 0xffffffff, "ERC165: invalid interface id"); _supportedInterfaces[interfaceId] = true; } } /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * ==== */ function isContract(address account) internal view returns (bool) { // According to EIP-1052, 0x0 is the value returned for not-yet created accounts // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned // for accounts without code, i.e. `keccak256('')` bytes32 codehash; bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; // solhint-disable-next-line no-inline-assembly assembly { codehash := extcodehash(account) } return (codehash != accountHash && codehash != 0x0); } /** * @dev Converts an `address` into `address payable`. Note that this is * simply a type cast: the actual underlying value is not changed. * * _Available since v2.4.0._ */ function toPayable(address account) internal pure returns (address payable) { return address(uint160(account)); } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. * * _Available since v2.4.0._ */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); // solhint-disable-next-line avoid-call-value (bool success, ) = recipient.call.value(amount)(""); require(success, "Address: unable to send value, recipient may have reverted"); } } // A sample implementation of core ERC1155 function. contract ERC1155 is IERC1155, ERC165, CommonConstants { using SafeMath for uint256; 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("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; /////////////////////////////////////////// CONSTRUCTOR ////////////////////////////////////////// constructor() public { _registerInterface(INTERFACE_SIGNATURE_ERC1155); } /////////////////////////////////////////// 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."); // SafeMath will throw with insuficient funds _from // or if _id is not valid (balance will be 0) balances[_id][_from] = balances[_id][_from].sub(_value); balances[_id][_to] = _value.add(balances[_id][_to]); // 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 lenght 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]; // SafeMath will throw with insuficient funds _from // or if _id is not valid (balance will be 0) balances[id][_from] = balances[id][_from].sub(value); balances[id][_to] = value.add(balances[id][_to]); } // 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 @return The _owner's balance of the Token type requested */ 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 @return The _owner's balance of the Token types requested (i.e. balance for each (owner, id) pair) */ 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) external 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"); } } library UintLibrary { function toString(uint256 _i) internal pure returns (string memory) { if (_i == 0) { return "0"; } uint j = _i; uint len; while (j != 0) { len++; j /= 10; } bytes memory bstr = new bytes(len); uint k = len - 1; while (_i != 0) { bstr[k--] = byte(uint8(48 + _i % 10)); _i /= 10; } return string(bstr); } } library StringLibrary { using UintLibrary for uint256; function append(string memory _a, string memory _b) internal pure returns (string memory) { bytes memory _ba = bytes(_a); bytes memory _bb = bytes(_b); bytes memory bab = new bytes(_ba.length + _bb.length); uint k = 0; for (uint i = 0; i < _ba.length; i++) bab[k++] = _ba[i]; for (uint i = 0; i < _bb.length; i++) bab[k++] = _bb[i]; return string(bab); } function append(string memory _a, string memory _b, string memory _c) internal pure returns (string memory) { bytes memory _ba = bytes(_a); bytes memory _bb = bytes(_b); bytes memory _bc = bytes(_c); bytes memory bbb = new bytes(_ba.length + _bb.length + _bc.length); uint k = 0; for (uint i = 0; i < _ba.length; i++) bbb[k++] = _ba[i]; for (uint i = 0; i < _bb.length; i++) bbb[k++] = _bb[i]; for (uint i = 0; i < _bc.length; i++) bbb[k++] = _bc[i]; return string(bbb); } function recover(string memory message, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { bytes memory msgBytes = bytes(message); bytes memory fullMessage = concat( bytes("\x19Ethereum Signed Message:\n"), bytes(msgBytes.length.toString()), msgBytes, new bytes(0), new bytes(0), new bytes(0), new bytes(0) ); return ecrecover(keccak256(fullMessage), v, r, s); } function concat(bytes memory _ba, bytes memory _bb, bytes memory _bc, bytes memory _bd, bytes memory _be, bytes memory _bf, bytes memory _bg) internal pure returns (bytes memory) { bytes memory resultBytes = new bytes(_ba.length + _bb.length + _bc.length + _bd.length + _be.length + _bf.length + _bg.length); uint k = 0; for (uint i = 0; i < _ba.length; i++) resultBytes[k++] = _ba[i]; for (uint i = 0; i < _bb.length; i++) resultBytes[k++] = _bb[i]; for (uint i = 0; i < _bc.length; i++) resultBytes[k++] = _bc[i]; for (uint i = 0; i < _bd.length; i++) resultBytes[k++] = _bd[i]; for (uint i = 0; i < _be.length; i++) resultBytes[k++] = _be[i]; for (uint i = 0; i < _bf.length; i++) resultBytes[k++] = _bf[i]; for (uint i = 0; i < _bg.length; i++) resultBytes[k++] = _bg[i]; return resultBytes; } } contract HasContractURI is ERC165 { string public contractURI; /* * bytes4(keccak256('contractURI()')) == 0xe8a3d485 */ bytes4 private constant _INTERFACE_ID_CONTRACT_URI = 0xe8a3d485; constructor(string memory _contractURI) public { contractURI = _contractURI; _registerInterface(_INTERFACE_ID_CONTRACT_URI); } /** * @dev Internal function to set the contract URI * @param _contractURI string URI prefix to assign */ function _setContractURI(string memory _contractURI) internal { contractURI = _contractURI; } } contract HasTokenURI { using StringLibrary for string; //Token URI prefix string public tokenURIPrefix; // Optional mapping for token URIs mapping(uint256 => string) private _tokenURIs; constructor(string memory _tokenURIPrefix) public { tokenURIPrefix = _tokenURIPrefix; } /** * @dev Returns an URI for a given token ID. * Throws if the token ID does not exist. May return an empty string. * @param tokenId uint256 ID of the token to query */ function _tokenURI(uint256 tokenId) internal view returns (string memory) { return tokenURIPrefix.append(_tokenURIs[tokenId]); } /** * @dev Internal function to set the token URI for a given token. * Reverts if the token ID does not exist. * @param tokenId uint256 ID of the token to set its URI * @param uri string URI to assign */ function _setTokenURI(uint256 tokenId, string memory uri) internal { _tokenURIs[tokenId] = uri; } /** * @dev Internal function to set the token URI prefix. * @param _tokenURIPrefix string URI prefix to assign */ function _setTokenURIPrefix(string memory _tokenURIPrefix) internal { tokenURIPrefix = _tokenURIPrefix; } function _clearTokenURI(uint256 tokenId) internal { if (bytes(_tokenURIs[tokenId]).length != 0) { delete _tokenURIs[tokenId]; } } } /* * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with GSN meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ contract Context { // Empty internal constructor, to prevent people from mistakenly deploying // an instance of this contract, which should be used via inheritance. constructor () internal { } // solhint-disable-previous-line no-empty-blocks function _msgSender() internal view returns (address payable) { return msg.sender; } function _msgData() internal view returns (bytes memory) { this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 return msg.data; } } /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor () internal { address msgSender = _msgSender(); _owner = msgSender; emit OwnershipTransferred(address(0), msgSender); } /** * @dev Returns the address of the current owner. */ function owner() public view returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(isOwner(), "Ownable: caller is not the owner"); _; } /** * @dev Returns true if the caller is the current owner. */ function isOwner() public view returns (bool) { return _msgSender() == _owner; } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions anymore. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public onlyOwner { emit OwnershipTransferred(_owner, address(0)); _owner = address(0); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public onlyOwner { _transferOwnership(newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). */ function _transferOwnership(address newOwner) internal { require(newOwner != address(0), "Ownable: new owner is the zero address"); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; } } /** Note: The ERC-165 identifier for this interface is 0x0e89341c. */ interface IERC1155Metadata_URI { /** @notice A distinct Uniform Resource Identifier (URI) for a given token. @dev URIs are defined in RFC 3986. The URI may point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema". @return URI string */ function uri(uint256 _id) external view returns (string memory); } /** Note: The ERC-165 identifier for this interface is 0x0e89341c. */ contract ERC1155Metadata_URI is IERC1155Metadata_URI, HasTokenURI { constructor(string memory _tokenURIPrefix) HasTokenURI(_tokenURIPrefix) public { } function uri(uint256 _id) external view returns (string memory) { return _tokenURI(_id); } } contract HasSecondarySaleFees is ERC165 { event SecondarySaleFees(uint256 tokenId, address[] recipients, uint[] bps); /* * bytes4(keccak256('getFeeBps(uint256)')) == 0x0ebd4c7f * bytes4(keccak256('getFeeRecipients(uint256)')) == 0xb9c4d9fb * * => 0x0ebd4c7f ^ 0xb9c4d9fb == 0xb7799584 */ bytes4 private constant _INTERFACE_ID_FEES = 0xb7799584; constructor() public { _registerInterface(_INTERFACE_ID_FEES); } function getFeeRecipients(uint256 id) public view returns (address payable[] memory); function getFeeBps(uint256 id) public view returns (uint[] memory); } contract ERC1155Base is HasSecondarySaleFees, Ownable, ERC1155Metadata_URI, HasContractURI, ERC1155 { struct Fee { address payable recipient; uint256 value; } // id => creator mapping (uint256 => address) public creators; // id => fees mapping (uint256 => Fee[]) public fees; constructor(string memory contractURI, string memory tokenURIPrefix) HasContractURI(contractURI) ERC1155Metadata_URI(tokenURIPrefix) public { } function getFeeRecipients(uint256 id) public view returns (address payable[] memory) { Fee[] memory _fees = fees[id]; address payable[] memory result = new address payable[](_fees.length); for (uint i = 0; i < _fees.length; i++) { result[i] = _fees[i].recipient; } return result; } function getFeeBps(uint256 id) public view returns (uint[] memory) { Fee[] memory _fees = fees[id]; uint[] memory result = new uint[](_fees.length); for (uint i = 0; i < _fees.length; i++) { result[i] = _fees[i].value; } return result; } // Creates a new token type and assings _initialSupply to minter function _mint(uint256 _id, Fee[] memory _fees, uint256 _supply, string memory _uri) internal { require(creators[_id] == address(0x0), "Token is already minted"); require(_supply != 0, "Supply should be positive"); require(bytes(_uri).length > 0, "uri should be set"); creators[_id] = msg.sender; address[] memory recipients = new address[](_fees.length); uint[] memory bps = new uint[](_fees.length); for (uint i = 0; i < _fees.length; i++) { require(_fees[i].recipient != address(0x0), "Recipient should be present"); require(_fees[i].value != 0, "Fee value should be positive"); fees[_id].push(_fees[i]); recipients[i] = _fees[i].recipient; bps[i] = _fees[i].value; } if (_fees.length > 0) { emit SecondarySaleFees(_id, recipients, bps); } balances[_id][msg.sender] = _supply; _setTokenURI(_id, _uri); // Transfer event with mint semantic emit TransferSingle(msg.sender, address(0x0), msg.sender, _id, _supply); emit URI(_uri, _id); } function burn(address _owner, uint256 _id, uint256 _value) external { require(_owner == msg.sender || operatorApproval[_owner][msg.sender] == true, "Need operator approval for 3rd party burns."); // SafeMath will throw with insuficient funds _owner // or if _id is not valid (balance will be 0) balances[_id][_owner] = balances[_id][_owner].sub(_value); // MUST emit event emit TransferSingle(msg.sender, _owner, address(0x0), _id, _value); } /** * @dev Internal function to set the token URI for a given token. * Reverts if the token ID does not exist. * @param tokenId uint256 ID of the token to set its URI * @param uri string URI to assign */ function _setTokenURI(uint256 tokenId, string memory uri) internal { require(creators[tokenId] != address(0x0), "_setTokenURI: Token should exist"); super._setTokenURI(tokenId, uri); } function setTokenURIPrefix(string memory tokenURIPrefix) public onlyOwner { _setTokenURIPrefix(tokenURIPrefix); } function setContractURI(string memory contractURI) public onlyOwner { _setContractURI(contractURI); } } /** * @title Roles * @dev Library for managing addresses assigned to a Role. */ library Roles { struct Role { mapping (address => bool) bearer; } /** * @dev Give an account access to this role. */ function add(Role storage role, address account) internal { require(!has(role, account), "Roles: account already has role"); role.bearer[account] = true; } /** * @dev Remove an account's access to this role. */ function remove(Role storage role, address account) internal { require(has(role, account), "Roles: account does not have role"); role.bearer[account] = false; } /** * @dev Check if an account has this role. * @return bool */ function has(Role storage role, address account) internal view returns (bool) { require(account != address(0), "Roles: account is the zero address"); return role.bearer[account]; } } contract SignerRole is Context { using Roles for Roles.Role; event SignerAdded(address indexed account); event SignerRemoved(address indexed account); Roles.Role private _signers; constructor () internal { _addSigner(_msgSender()); } modifier onlySigner() { require(isSigner(_msgSender()), "SignerRole: caller does not have the Signer role"); _; } function isSigner(address account) public view returns (bool) { return _signers.has(account); } function addSigner(address account) public onlySigner { _addSigner(account); } function renounceSigner() public { _removeSigner(_msgSender()); } function _addSigner(address account) internal { _signers.add(account); emit SignerAdded(account); } function _removeSigner(address account) internal { _signers.remove(account); emit SignerRemoved(account); } } contract RaribleToken is Ownable, SignerRole, ERC1155Base { string public name; string public symbol; constructor(string memory _name, string memory _symbol, address signer, string memory contractURI, string memory tokenURIPrefix) ERC1155Base(contractURI, tokenURIPrefix) public { name = _name; symbol = _symbol; _addSigner(signer); _registerInterface(bytes4(keccak256('MINT_WITH_ADDRESS'))); } function addSigner(address account) public onlyOwner { _addSigner(account); } function removeSigner(address account) public onlyOwner { _removeSigner(account); } function mint(uint256 id, uint8 v, bytes32 r, bytes32 s, Fee[] memory fees, uint256 supply, string memory uri) public { require(isSigner(ecrecover(keccak256(abi.encodePacked(this, id)), v, r, s)), "signer should sign tokenId"); _mint(id, fees, supply, uri); } } contract RaribleUserToken is RaribleToken { event CreateERC1155_v1(address indexed creator, string name, string symbol); constructor(string memory name, string memory symbol, string memory contractURI, string memory tokenURIPrefix, address signer) RaribleToken(name, symbol, signer, contractURI, tokenURIPrefix) public { emit CreateERC1155_v1(msg.sender, name, symbol); } function mint(uint256 id, uint8 v, bytes32 r, bytes32 s, Fee[] memory fees, uint256 supply, string memory uri) onlyOwner public { super.mint(id, v, r, s, fees, supply, uri); } }
File 3 of 3: Conduit
// SPDX-License-Identifier: MIT pragma solidity >=0.8.7; import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; import { ConduitItemType } from "./lib/ConduitEnums.sol"; import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; // prettier-ignore import { ConduitTransfer, ConduitBatch1155Transfer } from "./lib/ConduitStructs.sol"; import "./lib/ConduitConstants.sol"; /** * @title Conduit * @author 0age * @notice This contract serves as an originator for "proxied" transfers. Each * conduit is deployed and controlled by a "conduit controller" that can * add and remove "channels" or contracts that can instruct the conduit * to transfer approved ERC20/721/1155 tokens. *IMPORTANT NOTE: each * conduit has an owner that can arbitrarily add or remove channels, and * a malicious or negligent owner can add a channel that allows for any * approved ERC20/721/1155 tokens to be taken immediately — be extremely * cautious with what conduits you give token approvals to!* */ contract Conduit is ConduitInterface, TokenTransferrer { // Set deployer as an immutable controller that can update channel statuses. address private immutable _controller; // Track the status of each channel. mapping(address => bool) private _channels; /** * @notice Ensure that the caller is currently registered as an open channel * on the conduit. */ modifier onlyOpenChannel() { // Utilize assembly to access channel storage mapping directly. assembly { // Write the caller to scratch space. mstore(ChannelKey_channel_ptr, caller()) // Write the storage slot for _channels to scratch space. mstore(ChannelKey_slot_ptr, _channels.slot) // Derive the position in storage of _channels[msg.sender] // and check if the stored value is zero. if iszero( sload(keccak256(ChannelKey_channel_ptr, ChannelKey_length)) ) { // The caller is not an open channel; revert with // ChannelClosed(caller). First, set error signature in memory. mstore(ChannelClosed_error_ptr, ChannelClosed_error_signature) // Next, set the caller as the argument. mstore(ChannelClosed_channel_ptr, caller()) // Finally, revert, returning full custom error with argument. revert(ChannelClosed_error_ptr, ChannelClosed_error_length) } } // Continue with function execution. _; } /** * @notice In the constructor, set the deployer as the controller. */ constructor() { // Set the deployer as the controller. _controller = msg.sender; } /** * @notice Execute a sequence of ERC20/721/1155 transfers. Only a caller * with an open channel can call this function. Note that channels * are expected to implement reentrancy protection if desired, and * that cross-channel reentrancy may be possible if the conduit has * multiple open channels at once. Also note that channels are * expected to implement checks against transferring any zero-amount * items if that constraint is desired. * * @param transfers The ERC20/721/1155 transfers to perform. * * @return magicValue A magic value indicating that the transfers were * performed successfully. */ function execute(ConduitTransfer[] calldata transfers) external override onlyOpenChannel returns (bytes4 magicValue) { // Retrieve the total number of transfers and place on the stack. uint256 totalStandardTransfers = transfers.length; // Iterate over each transfer. for (uint256 i = 0; i < totalStandardTransfers; ) { // Retrieve the transfer in question and perform the transfer. _transfer(transfers[i]); // Skip overflow check as for loop is indexed starting at zero. unchecked { ++i; } } // Return a magic value indicating that the transfers were performed. magicValue = this.execute.selector; } /** * @notice Execute a sequence of batch 1155 item transfers. Only a caller * with an open channel can call this function. Note that channels * are expected to implement reentrancy protection if desired, and * that cross-channel reentrancy may be possible if the conduit has * multiple open channels at once. Also note that channels are * expected to implement checks against transferring any zero-amount * items if that constraint is desired. * * @param batchTransfers The 1155 batch item transfers to perform. * * @return magicValue A magic value indicating that the item transfers were * performed successfully. */ function executeBatch1155( ConduitBatch1155Transfer[] calldata batchTransfers ) external override onlyOpenChannel returns (bytes4 magicValue) { // Perform 1155 batch transfers. Note that memory should be considered // entirely corrupted from this point forward. _performERC1155BatchTransfers(batchTransfers); // Return a magic value indicating that the transfers were performed. magicValue = this.executeBatch1155.selector; } /** * @notice Execute a sequence of transfers, both single ERC20/721/1155 item * transfers as well as batch 1155 item transfers. Only a caller * with an open channel can call this function. Note that channels * are expected to implement reentrancy protection if desired, and * that cross-channel reentrancy may be possible if the conduit has * multiple open channels at once. Also note that channels are * expected to implement checks against transferring any zero-amount * items if that constraint is desired. * * @param standardTransfers The ERC20/721/1155 item transfers to perform. * @param batchTransfers The 1155 batch item transfers to perform. * * @return magicValue A magic value indicating that the item transfers were * performed successfully. */ function executeWithBatch1155( ConduitTransfer[] calldata standardTransfers, ConduitBatch1155Transfer[] calldata batchTransfers ) external override onlyOpenChannel returns (bytes4 magicValue) { // Retrieve the total number of transfers and place on the stack. uint256 totalStandardTransfers = standardTransfers.length; // Iterate over each standard transfer. for (uint256 i = 0; i < totalStandardTransfers; ) { // Retrieve the transfer in question and perform the transfer. _transfer(standardTransfers[i]); // Skip overflow check as for loop is indexed starting at zero. unchecked { ++i; } } // Perform 1155 batch transfers. Note that memory should be considered // entirely corrupted from this point forward aside from the free memory // pointer having the default value. _performERC1155BatchTransfers(batchTransfers); // Return a magic value indicating that the transfers were performed. magicValue = this.executeWithBatch1155.selector; } /** * @notice Open or close a given channel. Only callable by the controller. * * @param channel The channel to open or close. * @param isOpen The status of the channel (either open or closed). */ function updateChannel(address channel, bool isOpen) external override { // Ensure that the caller is the controller of this contract. if (msg.sender != _controller) { revert InvalidController(); } // Ensure that the channel does not already have the indicated status. if (_channels[channel] == isOpen) { revert ChannelStatusAlreadySet(channel, isOpen); } // Update the status of the channel. _channels[channel] = isOpen; // Emit a corresponding event. emit ChannelUpdated(channel, isOpen); } /** * @dev Internal function to transfer a given ERC20/721/1155 item. Note that * channels are expected to implement checks against transferring any * zero-amount items if that constraint is desired. * * @param item The ERC20/721/1155 item to transfer. */ function _transfer(ConduitTransfer calldata item) internal { // Determine the transfer method based on the respective item type. if (item.itemType == ConduitItemType.ERC20) { // Transfer ERC20 token. Note that item.identifier is ignored and // therefore ERC20 transfer items are potentially malleable — this // check should be performed by the calling channel if a constraint // on item malleability is desired. _performERC20Transfer(item.token, item.from, item.to, item.amount); } else if (item.itemType == ConduitItemType.ERC721) { // Ensure that exactly one 721 item is being transferred. if (item.amount != 1) { revert InvalidERC721TransferAmount(); } // Transfer ERC721 token. _performERC721Transfer( item.token, item.from, item.to, item.identifier ); } else if (item.itemType == ConduitItemType.ERC1155) { // Transfer ERC1155 token. _performERC1155Transfer( item.token, item.from, item.to, item.identifier, item.amount ); } else { // Throw with an error. revert InvalidItemType(); } } } // SPDX-License-Identifier: MIT pragma solidity >=0.8.7; // prettier-ignore import { ConduitTransfer, ConduitBatch1155Transfer } from "../conduit/lib/ConduitStructs.sol"; /** * @title ConduitInterface * @author 0age * @notice ConduitInterface contains all external function interfaces, events, * and errors for conduit contracts. */ interface ConduitInterface { /** * @dev Revert with an error when attempting to execute transfers using a * caller that does not have an open channel. */ error ChannelClosed(address channel); /** * @dev Revert with an error when attempting to update a channel to the * current status of that channel. */ error ChannelStatusAlreadySet(address channel, bool isOpen); /** * @dev Revert with an error when attempting to execute a transfer for an * item that does not have an ERC20/721/1155 item type. */ error InvalidItemType(); /** * @dev Revert with an error when attempting to update the status of a * channel from a caller that is not the conduit controller. */ error InvalidController(); /** * @dev Emit an event whenever a channel is opened or closed. * * @param channel The channel that has been updated. * @param open A boolean indicating whether the conduit is open or not. */ event ChannelUpdated(address indexed channel, bool open); /** * @notice Execute a sequence of ERC20/721/1155 transfers. Only a caller * with an open channel can call this function. * * @param transfers The ERC20/721/1155 transfers to perform. * * @return magicValue A magic value indicating that the transfers were * performed successfully. */ function execute(ConduitTransfer[] calldata transfers) external returns (bytes4 magicValue); /** * @notice Execute a sequence of batch 1155 transfers. Only a caller with an * open channel can call this function. * * @param batch1155Transfers The 1155 batch transfers to perform. * * @return magicValue A magic value indicating that the transfers were * performed successfully. */ function executeBatch1155( ConduitBatch1155Transfer[] calldata batch1155Transfers ) external returns (bytes4 magicValue); /** * @notice Execute a sequence of transfers, both single and batch 1155. Only * a caller with an open channel can call this function. * * @param standardTransfers The ERC20/721/1155 transfers to perform. * @param batch1155Transfers The 1155 batch transfers to perform. * * @return magicValue A magic value indicating that the transfers were * performed successfully. */ function executeWithBatch1155( ConduitTransfer[] calldata standardTransfers, ConduitBatch1155Transfer[] calldata batch1155Transfers ) external returns (bytes4 magicValue); /** * @notice Open or close a given channel. Only callable by the controller. * * @param channel The channel to open or close. * @param isOpen The status of the channel (either open or closed). */ function updateChannel(address channel, bool isOpen) external; } // SPDX-License-Identifier: MIT pragma solidity >=0.8.7; enum ConduitItemType { NATIVE, // unused ERC20, ERC721, ERC1155 } // SPDX-License-Identifier: MIT pragma solidity >=0.8.7; import "./TokenTransferrerConstants.sol"; // prettier-ignore import { TokenTransferrerErrors } from "../interfaces/TokenTransferrerErrors.sol"; import { ConduitBatch1155Transfer } from "../conduit/lib/ConduitStructs.sol"; /** * @title TokenTransferrer * @author 0age * @custom:coauthor d1ll0n * @custom:coauthor transmissions11 * @notice TokenTransferrer is a library for performing optimized ERC20, ERC721, * ERC1155, and batch ERC1155 transfers, used by both Seaport as well as * by conduits deployed by the ConduitController. Use great caution when * considering these functions for use in other codebases, as there are * significant side effects and edge cases that need to be thoroughly * understood and carefully addressed. */ contract TokenTransferrer is TokenTransferrerErrors { /** * @dev Internal function to transfer ERC20 tokens from a given originator * to a given recipient. Sufficient approvals must be set on the * contract performing the transfer. * * @param token The ERC20 token to transfer. * @param from The originator of the transfer. * @param to The recipient of the transfer. * @param amount The amount to transfer. */ function _performERC20Transfer( address token, address from, address to, uint256 amount ) internal { // Utilize assembly to perform an optimized ERC20 token transfer. assembly { // The free memory pointer memory slot will be used when populating // call data for the transfer; read the value and restore it later. let memPointer := mload(FreeMemoryPointerSlot) // Write call data into memory, starting with function selector. mstore(ERC20_transferFrom_sig_ptr, ERC20_transferFrom_signature) mstore(ERC20_transferFrom_from_ptr, from) mstore(ERC20_transferFrom_to_ptr, to) mstore(ERC20_transferFrom_amount_ptr, amount) // Make call & copy up to 32 bytes of return data to scratch space. // Scratch space does not need to be cleared ahead of time, as the // subsequent check will ensure that either at least a full word of // return data is received (in which case it will be overwritten) or // that no data is received (in which case scratch space will be // ignored) on a successful call to the given token. let callStatus := call( gas(), token, 0, ERC20_transferFrom_sig_ptr, ERC20_transferFrom_length, 0, OneWord ) // Determine whether transfer was successful using status & result. let success := and( // Set success to whether the call reverted, if not check it // either returned exactly 1 (can't just be non-zero data), or // had no return data. or( and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize()) ), callStatus ) // Handle cases where either the transfer failed or no data was // returned. Group these, as most transfers will succeed with data. // Equivalent to `or(iszero(success), iszero(returndatasize()))` // but after it's inverted for JUMPI this expression is cheaper. if iszero(and(success, iszero(iszero(returndatasize())))) { // If the token has no code or the transfer failed: Equivalent // to `or(iszero(success), iszero(extcodesize(token)))` but // after it's inverted for JUMPI this expression is cheaper. if iszero(and(iszero(iszero(extcodesize(token))), success)) { // If the transfer failed: if iszero(success) { // If it was due to a revert: if iszero(callStatus) { // If it returned a message, bubble it up as long as // sufficient gas remains to do so: if returndatasize() { // Ensure that sufficient gas is available to // copy returndata while expanding memory where // necessary. Start by computing the word size // of returndata and allocated memory. Round up // to the nearest full word. let returnDataWords := div( add(returndatasize(), AlmostOneWord), OneWord ) // Note: use the free memory pointer in place of // msize() to work around a Yul warning that // prevents accessing msize directly when the IR // pipeline is activated. let msizeWords := div(memPointer, OneWord) // Next, compute the cost of the returndatacopy. let cost := mul(CostPerWord, returnDataWords) // Then, compute cost of new memory allocation. if gt(returnDataWords, msizeWords) { cost := add( cost, add( mul( sub( returnDataWords, msizeWords ), CostPerWord ), div( sub( mul( returnDataWords, returnDataWords ), mul(msizeWords, msizeWords) ), MemoryExpansionCoefficient ) ) ) } // Finally, add a small constant and compare to // gas remaining; bubble up the revert data if // enough gas is still available. if lt(add(cost, ExtraGasBuffer), gas()) { // Copy returndata to memory; overwrite // existing memory. returndatacopy(0, 0, returndatasize()) // Revert, specifying memory region with // copied returndata. revert(0, returndatasize()) } } // Otherwise revert with a generic error message. mstore( TokenTransferGenericFailure_error_sig_ptr, TokenTransferGenericFailure_error_signature ) mstore( TokenTransferGenericFailure_error_token_ptr, token ) mstore( TokenTransferGenericFailure_error_from_ptr, from ) mstore(TokenTransferGenericFailure_error_to_ptr, to) mstore(TokenTransferGenericFailure_error_id_ptr, 0) mstore( TokenTransferGenericFailure_error_amount_ptr, amount ) revert( TokenTransferGenericFailure_error_sig_ptr, TokenTransferGenericFailure_error_length ) } // Otherwise revert with a message about the token // returning false or non-compliant return values. mstore( BadReturnValueFromERC20OnTransfer_error_sig_ptr, BadReturnValueFromERC20OnTransfer_error_signature ) mstore( BadReturnValueFromERC20OnTransfer_error_token_ptr, token ) mstore( BadReturnValueFromERC20OnTransfer_error_from_ptr, from ) mstore( BadReturnValueFromERC20OnTransfer_error_to_ptr, to ) mstore( BadReturnValueFromERC20OnTransfer_error_amount_ptr, amount ) revert( BadReturnValueFromERC20OnTransfer_error_sig_ptr, BadReturnValueFromERC20OnTransfer_error_length ) } // Otherwise, revert with error about token not having code: mstore(NoContract_error_sig_ptr, NoContract_error_signature) mstore(NoContract_error_token_ptr, token) revert(NoContract_error_sig_ptr, NoContract_error_length) } // Otherwise, the token just returned no data despite the call // having succeeded; no need to optimize for this as it's not // technically ERC20 compliant. } // Restore the original free memory pointer. mstore(FreeMemoryPointerSlot, memPointer) // Restore the zero slot to zero. mstore(ZeroSlot, 0) } } /** * @dev Internal function to transfer an ERC721 token from a given * originator to a given recipient. Sufficient approvals must be set on * the contract performing the transfer. Note that this function does * not check whether the receiver can accept the ERC721 token (i.e. it * does not use `safeTransferFrom`). * * @param token The ERC721 token to transfer. * @param from The originator of the transfer. * @param to The recipient of the transfer. * @param identifier The tokenId to transfer. */ function _performERC721Transfer( address token, address from, address to, uint256 identifier ) internal { // Utilize assembly to perform an optimized ERC721 token transfer. assembly { // If the token has no code, revert. if iszero(extcodesize(token)) { mstore(NoContract_error_sig_ptr, NoContract_error_signature) mstore(NoContract_error_token_ptr, token) revert(NoContract_error_sig_ptr, NoContract_error_length) } // The free memory pointer memory slot will be used when populating // call data for the transfer; read the value and restore it later. let memPointer := mload(FreeMemoryPointerSlot) // Write call data to memory starting with function selector. mstore(ERC721_transferFrom_sig_ptr, ERC721_transferFrom_signature) mstore(ERC721_transferFrom_from_ptr, from) mstore(ERC721_transferFrom_to_ptr, to) mstore(ERC721_transferFrom_id_ptr, identifier) // Perform the call, ignoring return data. let success := call( gas(), token, 0, ERC721_transferFrom_sig_ptr, ERC721_transferFrom_length, 0, 0 ) // If the transfer reverted: if iszero(success) { // If it returned a message, bubble it up as long as sufficient // gas remains to do so: if returndatasize() { // Ensure that sufficient gas is available to copy // returndata while expanding memory where necessary. Start // by computing word size of returndata & allocated memory. // Round up to the nearest full word. let returnDataWords := div( add(returndatasize(), AlmostOneWord), OneWord ) // Note: use the free memory pointer in place of msize() to // work around a Yul warning that prevents accessing msize // directly when the IR pipeline is activated. let msizeWords := div(memPointer, OneWord) // Next, compute the cost of the returndatacopy. let cost := mul(CostPerWord, returnDataWords) // Then, compute cost of new memory allocation. if gt(returnDataWords, msizeWords) { cost := add( cost, add( mul( sub(returnDataWords, msizeWords), CostPerWord ), div( sub( mul(returnDataWords, returnDataWords), mul(msizeWords, msizeWords) ), MemoryExpansionCoefficient ) ) ) } // Finally, add a small constant and compare to gas // remaining; bubble up the revert data if enough gas is // still available. if lt(add(cost, ExtraGasBuffer), gas()) { // Copy returndata to memory; overwrite existing memory. returndatacopy(0, 0, returndatasize()) // Revert, giving memory region with copied returndata. revert(0, returndatasize()) } } // Otherwise revert with a generic error message. mstore( TokenTransferGenericFailure_error_sig_ptr, TokenTransferGenericFailure_error_signature ) mstore(TokenTransferGenericFailure_error_token_ptr, token) mstore(TokenTransferGenericFailure_error_from_ptr, from) mstore(TokenTransferGenericFailure_error_to_ptr, to) mstore(TokenTransferGenericFailure_error_id_ptr, identifier) mstore(TokenTransferGenericFailure_error_amount_ptr, 1) revert( TokenTransferGenericFailure_error_sig_ptr, TokenTransferGenericFailure_error_length ) } // Restore the original free memory pointer. mstore(FreeMemoryPointerSlot, memPointer) // Restore the zero slot to zero. mstore(ZeroSlot, 0) } } /** * @dev Internal function to transfer ERC1155 tokens from a given * originator to a given recipient. Sufficient approvals must be set on * the contract performing the transfer and contract recipients must * implement the ERC1155TokenReceiver interface to indicate that they * are willing to accept the transfer. * * @param token The ERC1155 token to transfer. * @param from The originator of the transfer. * @param to The recipient of the transfer. * @param identifier The id to transfer. * @param amount The amount to transfer. */ function _performERC1155Transfer( address token, address from, address to, uint256 identifier, uint256 amount ) internal { // Utilize assembly to perform an optimized ERC1155 token transfer. assembly { // If the token has no code, revert. if iszero(extcodesize(token)) { mstore(NoContract_error_sig_ptr, NoContract_error_signature) mstore(NoContract_error_token_ptr, token) revert(NoContract_error_sig_ptr, NoContract_error_length) } // The following memory slots will be used when populating call data // for the transfer; read the values and restore them later. let memPointer := mload(FreeMemoryPointerSlot) let slot0x80 := mload(Slot0x80) let slot0xA0 := mload(Slot0xA0) let slot0xC0 := mload(Slot0xC0) // Write call data into memory, beginning with function selector. mstore( ERC1155_safeTransferFrom_sig_ptr, ERC1155_safeTransferFrom_signature ) mstore(ERC1155_safeTransferFrom_from_ptr, from) mstore(ERC1155_safeTransferFrom_to_ptr, to) mstore(ERC1155_safeTransferFrom_id_ptr, identifier) mstore(ERC1155_safeTransferFrom_amount_ptr, amount) mstore( ERC1155_safeTransferFrom_data_offset_ptr, ERC1155_safeTransferFrom_data_length_offset ) mstore(ERC1155_safeTransferFrom_data_length_ptr, 0) // Perform the call, ignoring return data. let success := call( gas(), token, 0, ERC1155_safeTransferFrom_sig_ptr, ERC1155_safeTransferFrom_length, 0, 0 ) // If the transfer reverted: if iszero(success) { // If it returned a message, bubble it up as long as sufficient // gas remains to do so: if returndatasize() { // Ensure that sufficient gas is available to copy // returndata while expanding memory where necessary. Start // by computing word size of returndata & allocated memory. // Round up to the nearest full word. let returnDataWords := div( add(returndatasize(), AlmostOneWord), OneWord ) // Note: use the free memory pointer in place of msize() to // work around a Yul warning that prevents accessing msize // directly when the IR pipeline is activated. let msizeWords := div(memPointer, OneWord) // Next, compute the cost of the returndatacopy. let cost := mul(CostPerWord, returnDataWords) // Then, compute cost of new memory allocation. if gt(returnDataWords, msizeWords) { cost := add( cost, add( mul( sub(returnDataWords, msizeWords), CostPerWord ), div( sub( mul(returnDataWords, returnDataWords), mul(msizeWords, msizeWords) ), MemoryExpansionCoefficient ) ) ) } // Finally, add a small constant and compare to gas // remaining; bubble up the revert data if enough gas is // still available. if lt(add(cost, ExtraGasBuffer), gas()) { // Copy returndata to memory; overwrite existing memory. returndatacopy(0, 0, returndatasize()) // Revert, giving memory region with copied returndata. revert(0, returndatasize()) } } // Otherwise revert with a generic error message. mstore( TokenTransferGenericFailure_error_sig_ptr, TokenTransferGenericFailure_error_signature ) mstore(TokenTransferGenericFailure_error_token_ptr, token) mstore(TokenTransferGenericFailure_error_from_ptr, from) mstore(TokenTransferGenericFailure_error_to_ptr, to) mstore(TokenTransferGenericFailure_error_id_ptr, identifier) mstore(TokenTransferGenericFailure_error_amount_ptr, amount) revert( TokenTransferGenericFailure_error_sig_ptr, TokenTransferGenericFailure_error_length ) } mstore(Slot0x80, slot0x80) // Restore slot 0x80. mstore(Slot0xA0, slot0xA0) // Restore slot 0xA0. mstore(Slot0xC0, slot0xC0) // Restore slot 0xC0. // Restore the original free memory pointer. mstore(FreeMemoryPointerSlot, memPointer) // Restore the zero slot to zero. mstore(ZeroSlot, 0) } } /** * @dev Internal function to transfer ERC1155 tokens from a given * originator to a given recipient. Sufficient approvals must be set on * the contract performing the transfer and contract recipients must * implement the ERC1155TokenReceiver interface to indicate that they * are willing to accept the transfer. NOTE: this function is not * memory-safe; it will overwrite existing memory, restore the free * memory pointer to the default value, and overwrite the zero slot. * This function should only be called once memory is no longer * required and when uninitialized arrays are not utilized, and memory * should be considered fully corrupted (aside from the existence of a * default-value free memory pointer) after calling this function. * * @param batchTransfers The group of 1155 batch transfers to perform. */ function _performERC1155BatchTransfers( ConduitBatch1155Transfer[] calldata batchTransfers ) internal { // Utilize assembly to perform optimized batch 1155 transfers. assembly { let len := batchTransfers.length // Pointer to first head in the array, which is offset to the struct // at each index. This gets incremented after each loop to avoid // multiplying by 32 to get the offset for each element. let nextElementHeadPtr := batchTransfers.offset // Pointer to beginning of the head of the array. This is the // reference position each offset references. It's held static to // let each loop calculate the data position for an element. let arrayHeadPtr := nextElementHeadPtr // Write the function selector, which will be reused for each call: // safeBatchTransferFrom(address,address,uint256[],uint256[],bytes) mstore( ConduitBatch1155Transfer_from_offset, ERC1155_safeBatchTransferFrom_signature ) // Iterate over each batch transfer. for { let i := 0 } lt(i, len) { i := add(i, 1) } { // Read the offset to the beginning of the element and add // it to pointer to the beginning of the array head to get // the absolute position of the element in calldata. let elementPtr := add( arrayHeadPtr, calldataload(nextElementHeadPtr) ) // Retrieve the token from calldata. let token := calldataload(elementPtr) // If the token has no code, revert. if iszero(extcodesize(token)) { mstore(NoContract_error_sig_ptr, NoContract_error_signature) mstore(NoContract_error_token_ptr, token) revert(NoContract_error_sig_ptr, NoContract_error_length) } // Get the total number of supplied ids. let idsLength := calldataload( add(elementPtr, ConduitBatch1155Transfer_ids_length_offset) ) // Determine the expected offset for the amounts array. let expectedAmountsOffset := add( ConduitBatch1155Transfer_amounts_length_baseOffset, mul(idsLength, OneWord) ) // Validate struct encoding. let invalidEncoding := iszero( and( // ids.length == amounts.length eq( idsLength, calldataload(add(elementPtr, expectedAmountsOffset)) ), and( // ids_offset == 0xa0 eq( calldataload( add( elementPtr, ConduitBatch1155Transfer_ids_head_offset ) ), ConduitBatch1155Transfer_ids_length_offset ), // amounts_offset == 0xc0 + ids.length*32 eq( calldataload( add( elementPtr, ConduitBatchTransfer_amounts_head_offset ) ), expectedAmountsOffset ) ) ) ) // Revert with an error if the encoding is not valid. if invalidEncoding { mstore( Invalid1155BatchTransferEncoding_ptr, Invalid1155BatchTransferEncoding_selector ) revert( Invalid1155BatchTransferEncoding_ptr, Invalid1155BatchTransferEncoding_length ) } // Update the offset position for the next loop nextElementHeadPtr := add(nextElementHeadPtr, OneWord) // Copy the first section of calldata (before dynamic values). calldatacopy( BatchTransfer1155Params_ptr, add(elementPtr, ConduitBatch1155Transfer_from_offset), ConduitBatch1155Transfer_usable_head_size ) // Determine size of calldata required for ids and amounts. Note // that the size includes both lengths as well as the data. let idsAndAmountsSize := add(TwoWords, mul(idsLength, TwoWords)) // Update the offset for the data array in memory. mstore( BatchTransfer1155Params_data_head_ptr, add( BatchTransfer1155Params_ids_length_offset, idsAndAmountsSize ) ) // Set the length of the data array in memory to zero. mstore( add( BatchTransfer1155Params_data_length_basePtr, idsAndAmountsSize ), 0 ) // Determine the total calldata size for the call to transfer. let transferDataSize := add( BatchTransfer1155Params_calldata_baseSize, idsAndAmountsSize ) // Copy second section of calldata (including dynamic values). calldatacopy( BatchTransfer1155Params_ids_length_ptr, add(elementPtr, ConduitBatch1155Transfer_ids_length_offset), idsAndAmountsSize ) // Perform the call to transfer 1155 tokens. let success := call( gas(), token, 0, ConduitBatch1155Transfer_from_offset, // Data portion start. transferDataSize, // Location of the length of callData. 0, 0 ) // If the transfer reverted: if iszero(success) { // If it returned a message, bubble it up as long as // sufficient gas remains to do so: if returndatasize() { // Ensure that sufficient gas is available to copy // returndata while expanding memory where necessary. // Start by computing word size of returndata and // allocated memory. Round up to the nearest full word. let returnDataWords := div( add(returndatasize(), AlmostOneWord), OneWord ) // Note: use transferDataSize in place of msize() to // work around a Yul warning that prevents accessing // msize directly when the IR pipeline is activated. // The free memory pointer is not used here because // this function does almost all memory management // manually and does not update it, and transferDataSize // should be the largest memory value used (unless a // previous batch was larger). let msizeWords := div(transferDataSize, OneWord) // Next, compute the cost of the returndatacopy. let cost := mul(CostPerWord, returnDataWords) // Then, compute cost of new memory allocation. if gt(returnDataWords, msizeWords) { cost := add( cost, add( mul( sub(returnDataWords, msizeWords), CostPerWord ), div( sub( mul( returnDataWords, returnDataWords ), mul(msizeWords, msizeWords) ), MemoryExpansionCoefficient ) ) ) } // Finally, add a small constant and compare to gas // remaining; bubble up the revert data if enough gas is // still available. if lt(add(cost, ExtraGasBuffer), gas()) { // Copy returndata to memory; overwrite existing. returndatacopy(0, 0, returndatasize()) // Revert with memory region containing returndata. revert(0, returndatasize()) } } // Set the error signature. mstore( 0, ERC1155BatchTransferGenericFailure_error_signature ) // Write the token. mstore(ERC1155BatchTransferGenericFailure_token_ptr, token) // Increase the offset to ids by 32. mstore( BatchTransfer1155Params_ids_head_ptr, ERC1155BatchTransferGenericFailure_ids_offset ) // Increase the offset to amounts by 32. mstore( BatchTransfer1155Params_amounts_head_ptr, add( OneWord, mload(BatchTransfer1155Params_amounts_head_ptr) ) ) // Return modified region. The total size stays the same as // `token` uses the same number of bytes as `data.length`. revert(0, transferDataSize) } } // Reset the free memory pointer to the default value; memory must // be assumed to be dirtied and not reused from this point forward. // Also note that the zero slot is not reset to zero, meaning empty // arrays cannot be safely created or utilized until it is restored. mstore(FreeMemoryPointerSlot, DefaultFreeMemoryPointer) } } } // SPDX-License-Identifier: MIT pragma solidity >=0.8.7; import { ConduitItemType } from "./ConduitEnums.sol"; struct ConduitTransfer { ConduitItemType itemType; address token; address from; address to; uint256 identifier; uint256 amount; } struct ConduitBatch1155Transfer { address token; address from; address to; uint256[] ids; uint256[] amounts; } // SPDX-License-Identifier: MIT pragma solidity >=0.8.7; // error ChannelClosed(address channel) uint256 constant ChannelClosed_error_signature = ( 0x93daadf200000000000000000000000000000000000000000000000000000000 ); uint256 constant ChannelClosed_error_ptr = 0x00; uint256 constant ChannelClosed_channel_ptr = 0x4; uint256 constant ChannelClosed_error_length = 0x24; // For the mapping: // mapping(address => bool) channels // The position in storage for a particular account is: // keccak256(abi.encode(account, channels.slot)) uint256 constant ChannelKey_channel_ptr = 0x00; uint256 constant ChannelKey_slot_ptr = 0x20; uint256 constant ChannelKey_length = 0x40; // SPDX-License-Identifier: MIT pragma solidity >=0.8.7; /* * -------------------------- Disambiguation & Other Notes --------------------- * - The term "head" is used as it is in the documentation for ABI encoding, * but only in reference to dynamic types, i.e. it always refers to the * offset or pointer to the body of a dynamic type. In calldata, the head * is always an offset (relative to the parent object), while in memory, * the head is always the pointer to the body. More information found here: * https://docs.soliditylang.org/en/v0.8.14/abi-spec.html#argument-encoding * - Note that the length of an array is separate from and precedes the * head of the array. * * - The term "body" is used in place of the term "head" used in the ABI * documentation. It refers to the start of the data for a dynamic type, * e.g. the first word of a struct or the first word of the first element * in an array. * * - The term "pointer" is used to describe the absolute position of a value * and never an offset relative to another value. * - The suffix "_ptr" refers to a memory pointer. * - The suffix "_cdPtr" refers to a calldata pointer. * * - The term "offset" is used to describe the position of a value relative * to some parent value. For example, OrderParameters_conduit_offset is the * offset to the "conduit" value in the OrderParameters struct relative to * the start of the body. * - Note: Offsets are used to derive pointers. * * - Some structs have pointers defined for all of their fields in this file. * Lines which are commented out are fields that are not used in the * codebase but have been left in for readability. */ uint256 constant AlmostOneWord = 0x1f; uint256 constant OneWord = 0x20; uint256 constant TwoWords = 0x40; uint256 constant ThreeWords = 0x60; uint256 constant FreeMemoryPointerSlot = 0x40; uint256 constant ZeroSlot = 0x60; uint256 constant DefaultFreeMemoryPointer = 0x80; uint256 constant Slot0x80 = 0x80; uint256 constant Slot0xA0 = 0xa0; uint256 constant Slot0xC0 = 0xc0; // abi.encodeWithSignature("transferFrom(address,address,uint256)") uint256 constant ERC20_transferFrom_signature = ( 0x23b872dd00000000000000000000000000000000000000000000000000000000 ); uint256 constant ERC20_transferFrom_sig_ptr = 0x0; uint256 constant ERC20_transferFrom_from_ptr = 0x04; uint256 constant ERC20_transferFrom_to_ptr = 0x24; uint256 constant ERC20_transferFrom_amount_ptr = 0x44; uint256 constant ERC20_transferFrom_length = 0x64; // 4 + 32 * 3 == 100 // abi.encodeWithSignature( // "safeTransferFrom(address,address,uint256,uint256,bytes)" // ) uint256 constant ERC1155_safeTransferFrom_signature = ( 0xf242432a00000000000000000000000000000000000000000000000000000000 ); uint256 constant ERC1155_safeTransferFrom_sig_ptr = 0x0; uint256 constant ERC1155_safeTransferFrom_from_ptr = 0x04; uint256 constant ERC1155_safeTransferFrom_to_ptr = 0x24; uint256 constant ERC1155_safeTransferFrom_id_ptr = 0x44; uint256 constant ERC1155_safeTransferFrom_amount_ptr = 0x64; uint256 constant ERC1155_safeTransferFrom_data_offset_ptr = 0x84; uint256 constant ERC1155_safeTransferFrom_data_length_ptr = 0xa4; uint256 constant ERC1155_safeTransferFrom_length = 0xc4; // 4 + 32 * 6 == 196 uint256 constant ERC1155_safeTransferFrom_data_length_offset = 0xa0; // abi.encodeWithSignature( // "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)" // ) uint256 constant ERC1155_safeBatchTransferFrom_signature = ( 0x2eb2c2d600000000000000000000000000000000000000000000000000000000 ); bytes4 constant ERC1155_safeBatchTransferFrom_selector = bytes4( bytes32(ERC1155_safeBatchTransferFrom_signature) ); uint256 constant ERC721_transferFrom_signature = ERC20_transferFrom_signature; uint256 constant ERC721_transferFrom_sig_ptr = 0x0; uint256 constant ERC721_transferFrom_from_ptr = 0x04; uint256 constant ERC721_transferFrom_to_ptr = 0x24; uint256 constant ERC721_transferFrom_id_ptr = 0x44; uint256 constant ERC721_transferFrom_length = 0x64; // 4 + 32 * 3 == 100 // abi.encodeWithSignature("NoContract(address)") uint256 constant NoContract_error_signature = ( 0x5f15d67200000000000000000000000000000000000000000000000000000000 ); uint256 constant NoContract_error_sig_ptr = 0x0; uint256 constant NoContract_error_token_ptr = 0x4; uint256 constant NoContract_error_length = 0x24; // 4 + 32 == 36 // abi.encodeWithSignature( // "TokenTransferGenericFailure(address,address,address,uint256,uint256)" // ) uint256 constant TokenTransferGenericFailure_error_signature = ( 0xf486bc8700000000000000000000000000000000000000000000000000000000 ); uint256 constant TokenTransferGenericFailure_error_sig_ptr = 0x0; uint256 constant TokenTransferGenericFailure_error_token_ptr = 0x4; uint256 constant TokenTransferGenericFailure_error_from_ptr = 0x24; uint256 constant TokenTransferGenericFailure_error_to_ptr = 0x44; uint256 constant TokenTransferGenericFailure_error_id_ptr = 0x64; uint256 constant TokenTransferGenericFailure_error_amount_ptr = 0x84; // 4 + 32 * 5 == 164 uint256 constant TokenTransferGenericFailure_error_length = 0xa4; // abi.encodeWithSignature( // "BadReturnValueFromERC20OnTransfer(address,address,address,uint256)" // ) uint256 constant BadReturnValueFromERC20OnTransfer_error_signature = ( 0x9889192300000000000000000000000000000000000000000000000000000000 ); uint256 constant BadReturnValueFromERC20OnTransfer_error_sig_ptr = 0x0; uint256 constant BadReturnValueFromERC20OnTransfer_error_token_ptr = 0x4; uint256 constant BadReturnValueFromERC20OnTransfer_error_from_ptr = 0x24; uint256 constant BadReturnValueFromERC20OnTransfer_error_to_ptr = 0x44; uint256 constant BadReturnValueFromERC20OnTransfer_error_amount_ptr = 0x64; // 4 + 32 * 4 == 132 uint256 constant BadReturnValueFromERC20OnTransfer_error_length = 0x84; uint256 constant ExtraGasBuffer = 0x20; uint256 constant CostPerWord = 3; uint256 constant MemoryExpansionCoefficient = 0x200; // Values are offset by 32 bytes in order to write the token to the beginning // in the event of a revert uint256 constant BatchTransfer1155Params_ptr = 0x24; uint256 constant BatchTransfer1155Params_ids_head_ptr = 0x64; uint256 constant BatchTransfer1155Params_amounts_head_ptr = 0x84; uint256 constant BatchTransfer1155Params_data_head_ptr = 0xa4; uint256 constant BatchTransfer1155Params_data_length_basePtr = 0xc4; uint256 constant BatchTransfer1155Params_calldata_baseSize = 0xc4; uint256 constant BatchTransfer1155Params_ids_length_ptr = 0xc4; uint256 constant BatchTransfer1155Params_ids_length_offset = 0xa0; uint256 constant BatchTransfer1155Params_amounts_length_baseOffset = 0xc0; uint256 constant BatchTransfer1155Params_data_length_baseOffset = 0xe0; uint256 constant ConduitBatch1155Transfer_usable_head_size = 0x80; uint256 constant ConduitBatch1155Transfer_from_offset = 0x20; uint256 constant ConduitBatch1155Transfer_ids_head_offset = 0x60; uint256 constant ConduitBatch1155Transfer_amounts_head_offset = 0x80; uint256 constant ConduitBatch1155Transfer_ids_length_offset = 0xa0; uint256 constant ConduitBatch1155Transfer_amounts_length_baseOffset = 0xc0; uint256 constant ConduitBatch1155Transfer_calldata_baseSize = 0xc0; // Note: abbreviated version of above constant to adhere to line length limit. uint256 constant ConduitBatchTransfer_amounts_head_offset = 0x80; uint256 constant Invalid1155BatchTransferEncoding_ptr = 0x00; uint256 constant Invalid1155BatchTransferEncoding_length = 0x04; uint256 constant Invalid1155BatchTransferEncoding_selector = ( 0xeba2084c00000000000000000000000000000000000000000000000000000000 ); uint256 constant ERC1155BatchTransferGenericFailure_error_signature = ( 0xafc445e200000000000000000000000000000000000000000000000000000000 ); uint256 constant ERC1155BatchTransferGenericFailure_token_ptr = 0x04; uint256 constant ERC1155BatchTransferGenericFailure_ids_offset = 0xc0; // SPDX-License-Identifier: MIT pragma solidity >=0.8.7; /** * @title TokenTransferrerErrors */ interface TokenTransferrerErrors { /** * @dev Revert with an error when an ERC721 transfer with amount other than * one is attempted. */ error InvalidERC721TransferAmount(); /** * @dev Revert with an error when attempting to fulfill an order where an * item has an amount of zero. */ error MissingItemAmount(); /** * @dev Revert with an error when attempting to fulfill an order where an * item has unused parameters. This includes both the token and the * identifier parameters for native transfers as well as the identifier * parameter for ERC20 transfers. Note that the conduit does not * perform this check, leaving it up to the calling channel to enforce * when desired. */ error UnusedItemParameters(); /** * @dev Revert with an error when an ERC20, ERC721, or ERC1155 token * transfer reverts. * * @param token The token for which the transfer was attempted. * @param from The source of the attempted transfer. * @param to The recipient of the attempted transfer. * @param identifier The identifier for the attempted transfer. * @param amount The amount for the attempted transfer. */ error TokenTransferGenericFailure( address token, address from, address to, uint256 identifier, uint256 amount ); /** * @dev Revert with an error when a batch ERC1155 token transfer reverts. * * @param token The token for which the transfer was attempted. * @param from The source of the attempted transfer. * @param to The recipient of the attempted transfer. * @param identifiers The identifiers for the attempted transfer. * @param amounts The amounts for the attempted transfer. */ error ERC1155BatchTransferGenericFailure( address token, address from, address to, uint256[] identifiers, uint256[] amounts ); /** * @dev Revert with an error when an ERC20 token transfer returns a falsey * value. * * @param token The token for which the ERC20 transfer was attempted. * @param from The source of the attempted ERC20 transfer. * @param to The recipient of the attempted ERC20 transfer. * @param amount The amount for the attempted ERC20 transfer. */ error BadReturnValueFromERC20OnTransfer( address token, address from, address to, uint256 amount ); /** * @dev Revert with an error when an account being called as an assumed * contract does not have code and returns no data. * * @param account The account that should contain code. */ error NoContract(address account); /** * @dev Revert with an error when attempting to execute an 1155 batch * transfer using calldata not produced by default ABI encoding or with * different lengths for ids and amounts arrays. */ error Invalid1155BatchTransferEncoding(); }