Transaction Hash:
Block:
21486576 at Dec-26-2024 12:15:47 PM +UTC
Transaction Fee:
0.000885156340924584 ETH
$1.87
Gas Used:
118,674 Gas / 7.458721716 Gwei
Emitted Events:
| 497 |
DefimonsApartments.Transfer( from=[Sender] 0x3d91925c094028e6c20ea8c37a156382ea02e16e, to=0x214F85Fc...CF851cBa9, tokenId=1354 )
|
| 498 |
DefimonsApartments.Transfer( from=[Sender] 0x3d91925c094028e6c20ea8c37a156382ea02e16e, to=0x214F85Fc...CF851cBa9, tokenId=1467 )
|
| 499 |
DefimonsApartments.Transfer( from=[Sender] 0x3d91925c094028e6c20ea8c37a156382ea02e16e, to=0x214F85Fc...CF851cBa9, tokenId=2148 )
|
| 500 |
DefimonsApartments.Transfer( from=[Sender] 0x3d91925c094028e6c20ea8c37a156382ea02e16e, to=0x214F85Fc...CF851cBa9, tokenId=2592 )
|
| 501 |
DefimonsApartments.Transfer( from=[Sender] 0x3d91925c094028e6c20ea8c37a156382ea02e16e, to=0x214F85Fc...CF851cBa9, tokenId=2661 )
|
Execution Trace
TransferHelper.bulkTransfer( items=, conduitKey=0000007B02230091A7ED01230072F7006A004D60A8D4E71D599B8104250F0000 ) => ( items=, conduitKey= )
OpenSea: Conduit.4ce34aa2( )-
DefimonsApartments.transferFrom( from=0x3D91925C094028e6c20eA8c37A156382eA02e16E, to=0x214F85FcAeea5930e090571FE1E7873CF851cBa9, tokenId=1354 )
-
DefimonsApartments.transferFrom( from=0x3D91925C094028e6c20eA8c37A156382eA02e16E, to=0x214F85FcAeea5930e090571FE1E7873CF851cBa9, tokenId=1467 )
-
DefimonsApartments.transferFrom( from=0x3D91925C094028e6c20eA8c37A156382eA02e16E, to=0x214F85FcAeea5930e090571FE1E7873CF851cBa9, tokenId=2148 )
-
DefimonsApartments.transferFrom( from=0x3D91925C094028e6c20eA8c37A156382eA02e16E, to=0x214F85FcAeea5930e090571FE1E7873CF851cBa9, tokenId=2592 )
-
DefimonsApartments.transferFrom( from=0x3D91925C094028e6c20eA8c37A156382eA02e16E, to=0x214F85FcAeea5930e090571FE1E7873CF851cBa9, tokenId=2661 )
-
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 2: TransferHelper
File 2 of 2: DefimonsApartments
// 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 2: DefimonsApartments
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {ONFT721} from "../Omnichain/token/onft/ONFT721.sol";
/**
.....::::::::.... .::^~~~~!!!!!!!!!~:.
.:~!77?????JJ???JJJJJJJ???7!~^:. :~!???????777777777??JYJ7^
:7??7!~~~^^^^^^^^^^^^~~~~!!77?JJJJ?!:. :7J?!^::........::::::^~7JYY7:::::..
~J?~:...................:::::^^^~!7?YYJ7^. :JJ~:...................:^7JY5YYYYYYJ7^. ... .^~~~~^. ..:::::..
^Y?^...........................:::^^^~7?YY?^. .:^~!77??7777!~^:. .?J!.....................:^7JJ7!~~!?Y55YJ^ :!7??JJ?7~..~???7??JYY?^ ...:... .:^~~^:. .:^~~~~:. .^!7????JJJJJ??!^.
7Y?:...............................::^^~!JYY?: :~7??77!!~~!!!77?JJJJ?7Y?^.....................^7J7:.....:~JYYYY~7J7~^^~!?YYJJJ!:..:^~7JYY7. .^!7????JJJJ??!~: :!?????JYJ?~. .~??????JYJ77??!~^::::^^~!7?YYJ~
!YJ!:.................................:^^~?Y5J7J?~::..........::^~!7JYY?:........:^~~~~!!!!!!7JYY^.......:7YYYYYJ!.....:^!JYY7......^^7JY5! .~7??!~~^^^^^~!!?JYYJ!!J?^:.::^!?Y5J!. :?J!:..:^~7JY?^............:^7JY5!
.?5Y?!~^^^^~~..........:~~^^:..........:^^~?YYJ~...........::.....:^~7J7:.......^^7JYYYYYYYYYYYYY?:.....:!YYYYYY7:......^^7J?:......:^~?YYJ^!J?!:..........::^^~7JYY?^......:^!?JYY!?Y7......:^?!:..............:!JYYJ:
.!J5YYYYYYJ?........:^~?YYJJ7!^........:^^!JJ~.........:~7J?!.....:^~?7:.......^^~!!!!!!!!?JYYYYYJ!~~~!JYYYYYYJ~.......:^~?^........^^7JYYY?~................::^~7J?^........:^~7JYYY7......:^7:.......::::...:!?YY5?.
.^7?JYYYY?........:^^7YYYYYY?^.......:^^!?7:........:^7JYY?:.....^^77:................::^~7YY?7~~^^~7?JYYYYY7:........:^^.........:^!?YY?^.........:::.......:^^7?^..........:^~7JY7......:^7:......:~7?????JYYYYJ:
..:?Y?........:^^7YYYYJ?~........:^^!?~.........:^7YYJ!.....:^~??:...................:!??!:.....:~7?YYYJ~......................^^7J?^........^7?JJ?!:.....:^^7^............::~77:.....:^7!........:^~7?JYYYY!.
7Y?:.......:^^7JJ?7~:.........^^~?J~.........:^~!~^.....:^~?Y?:...................:!??^.......:!?YYY7:......................:^!?!.......:!JYY5YYY?:.....^^!^...............:^......:^7?!:.........::^!?JYJ!:
.^~!7JY?:.......:^^~~::...........:^~7JJ~..................:^~7JYY?^..................^!JJ?^.......^!?YYJ~........................^^?!.......^?YYY7:!YJ^.....:^~^......:^:..............:^7JY?~:..........:^~?Y5J~
:7??7!!~~~..........................^~?YYY!..............::~!7?JYYYYJ~.........^!!!777??JYYJ?^.......^7?YY7.......::................:^!!........!JY57~?J!:.....^^7^......^!?!^............:^7JYYYJ?!^:........:^7JYY~
!J?^:..............................:~7JYYYY?:..........^7?JJ7!~^^^!7J?~.........~JYYY5555YYYJ?^.......^7?Y?^.....:^~7:........^~.....:^^7^........^!7?7!^......:^~?~......^!?YJ7^..........:^7JYJ???J??7~:......:~?YYJ:
^Y?:..............................:~7JYY5YJYJ!...........!JJ!:.....:~?J~.........^?YYY7!~~~^YJ?^.......^7?J7......^^7?!.......:7?:.....:^!?^...................:^~??~......^!?YYYJ7^........:^7?!^::::^^^^:......:~?YYY^
?Y7:..........................:^~7?YY55Y7^.?5J!:..........::.......^!JJ!.........^?YYY^ :YJ?:.......^7??^.....:^~?YJ!:...:^7JJ~.....:^^??!:................:~7JYJ~......^!JYYYYYJ7^......:^7!.................:7JYYY:
!5J!:...................::^~!7?JYY55Y?!: .?5Y?!^...............:~7JYY?^.......:!JYYY^ :YJ?^.......~???^.....^^7JYYYJ??7?JYYY7:.....:^!JY?!^:...........^~7JYYYJ~......^!JYYY~7Y5J?~:..:~7J7:...............^7JYY57.
.?5Y?!~^^:^^^^^^^~~!!77?JJYYY55YYJ7~: .!Y5YJ?!~~^^^:^^^~~!7JJYYY5YJ7~^^^^!?YYY57. .JYJ?~:::::~7JYY?~^::^~7JYYYJY55555YYYJ~.....^~7JYYYYJ7!~~^^~~!7?JYY55YYYJ!^:::~7JYY5J. :7Y5YJ??JJYYY?!^^:::::::::^!?JYYYY7.
.!J55YYYYYYYYYYYYYY55555YYJJ7!^:. .~?YY5YYYYYYYYYYY5555YJ7~?Y5YYYYYY555J~. :?5YJJ????JYYY55YJJJJYYY5Y7.:^~~~^::J5J7!~~!7?YYYY?JY55YYYYYYY555YY?!:^J5YYJJJJYY5Y?: :!JYYYYYYJJ5YYYJJJJJJJJJYY55YY7^
.^!?JJJJJJJJJJJ??77!~~::. .^!7??JJJJJJJ??7!~:. :!?JJJJJ?!^. .~?YY555555YJ!~?JYYYYY?!: :7Y55YYYY55YJ~ .:~!7??JJ???7~^: .~?JYYYYYJ7^. .:::::..~7?JYYYY5YYYYYJ?7^.
........... ........ ..... :^~!!!!^:. .::::. :~7?JJ??!^. .. ..::::. ..:::^^^^::.
*/
contract DefimonsApartments is ONFT721 {
//
// Using Statements
//
using MerkleProof for bytes32[];
using SafeERC20 for IERC20;
//
// Constants
//
uint16 public constant MAX_MINT = 9500;
//
// Errors
//
error InvalidSaleInterval(uint256 start, uint256 finish);
error InvalidWhitelistRoot();
error SaleNotFound(uint256 saleId);
error ZeroMintQuantity();
error NotInSalePhase(
uint256 saleId,
uint256 start,
uint256 finish,
uint256 current
);
error UserNotWhitelistedOrWrongProof(
uint256 saleId,
address user,
bytes32[] proof
);
error WrongValueSentForMint(
uint256 saleId,
uint256 value,
uint256 price,
uint8 quantity
);
error MaximumSaleLimitReached(uint256 saleId, address user, uint8 limit);
error MaximumSupplyReached();
//
// Events
//
event LogSetURI(string newURI);
event LogSaleCreated(
uint256 indexed saleId,
uint64 start,
uint64 finish,
uint8 limit,
uint64 price,
bool whitelist,
bytes32 root
);
event LogSaleEdited(
uint256 indexed saleId,
uint64 start,
uint64 finish,
uint8 limit,
uint64 price,
bool whitelist,
bytes32 root
);
event LogSale(uint256 indexed saleId, address indexed to, uint256 quantity);
//
// Structs
//
/// Defines the parameters for a sale period.
/// This is created by the contract's Sale Admin.
/// A sale period has a start time, finish time, mint price, mint limit and a merkle root.
/// The root parameter indicates the current sale's merkle root (0 for public sales).
/// The whitelist boolean is 'true' for all whitelist mint periods and 'false' for public mints.
/// A sale is active when block.timestamp in interval [start, finish].
/// The price indicates how much a user has to pay for one NFT in this period.
/// The limit specifies how many NFTs a user can mint during this period.
struct Sale {
bytes32 root;
bool whitelist;
uint64 start;
uint64 finish;
uint64 price;
uint8 limit;
}
//
// State
//
/// Base NFT metadata URI.
string private _URI;
/// Id of the next NFT id to mint (sequential id).
uint256 public nextToMint = 1;
/// List of sale phases.
Sale[] private _sales;
/// Mapping of the quantity of NFTs minted to each address.
/// Used to track and cap how many tokens an address is allowed to mint per round.
/// current sale id => user address => number of NFTs minted
mapping(uint256 => mapping(address => uint256)) private _minted;
//
// ERC721
//
/// @dev See {IERC721Metadata-tokenURI}.
function _baseURI() internal view override returns (string memory) {
return _URI;
}
//
// Constructor
//
constructor(
string memory _name,
string memory _symbol,
address _lzEndpoint,
string memory _initialURI
) ONFT721(_name, _symbol, _lzEndpoint) {
setURI(_initialURI);
}
//
// Owner API
//
function setURI(string memory _newURI) public onlyOwner {
_URI = _newURI;
emit LogSetURI(_newURI);
}
/// @notice Adds a new sale period.
/// @dev Can only be called by the contract owner.
/// @param _start The start of the sale.
/// @param _finish The end of the sale.
/// @param _limit The maximum number of NFTs an account can mint during this period.
/// @param _price The price of each NFT during this period.
/// @param _whitelist Whether the sale is a whitelist sale
/// @param _root When adding a whitelist sale, this parameter defines the merkle root to be used for verification.
function addSale(
uint64 _start,
uint64 _finish,
uint8 _limit,
uint64 _price,
bool _whitelist,
bytes32 _root
) external onlyOwner {
_validateSaleParams(_start, _finish, _whitelist, _root);
Sale memory sale = Sale({
start: _start,
finish: _finish,
limit: _limit,
price: _price,
whitelist: _whitelist,
root: _root
});
_sales.push(sale);
emit LogSaleCreated(
_sales.length - 1,
_start,
_finish,
_limit,
_price,
_whitelist,
_root
);
}
/// @notice Edits a Sale Phase
/// @dev Can only be called by the contract owner.
/// @param _saleId The unique ID of the sale to be edited
/// @param _start The new start time we want the sale to have
/// @param _finish The new end time we want the sale to have
/// @param _limit The new limit of NFTs we want the sale to have
/// @param _price The new price we want the NFTs to have
/// @param _whitelist Whether it is a whitelist sale.
/// @param _root Defines the root to be used for whitelist verification.
/// If we want any Sale parameter to stay unchanged, send the same value as a parameter to the function
function editSale(
uint256 _saleId,
uint64 _start,
uint64 _finish,
uint8 _limit,
uint64 _price,
bool _whitelist,
bytes32 _root
) external onlyOwner {
_validateSaleParams(_start, _finish, _whitelist, _root);
if (_saleId >= _sales.length) revert SaleNotFound(_saleId);
Sale storage sale = _sales[_saleId];
sale.start = _start;
sale.finish = _finish;
sale.limit = _limit;
sale.price = _price;
sale.whitelist = _whitelist;
sale.root = _root;
emit LogSaleEdited(
_saleId,
_start,
_finish,
_limit,
_price,
_whitelist,
_root
);
}
/// @notice Withdraws any ETH sent to this contract.
/// @dev Only callable by this contract's owner.
/// @param _to The address to withdraw to.
/// @param _amount The amount of ETH (in Wei) to withdraw.
function withdrawEther(address _to, uint256 _amount) external onlyOwner {
payable(_to).transfer(_amount);
}
/// Withdraws any ERC20 tokens sent to the contract.
/// @dev only callable by the owner.
/// @param _token The ERC20 token to withdraw
/// @param _to The address to withdraw to.
/// @param _amount The amount to withdraw
function withdrawERC20(
address _token,
address _to,
uint256 _amount
) external onlyOwner {
IERC20(_token).safeTransfer(_to, _amount);
}
//
// Public Read API
//
function getSale(uint256 _saleId) external view returns (Sale memory) {
return _sales[_saleId];
}
function getSalesCount() external view returns (uint256) {
return _sales.length;
}
function isSaleActive(uint256 _saleId) external view returns (bool) {
Sale memory sale = _sales[_saleId];
return block.timestamp >= sale.start && block.timestamp <= sale.finish;
}
function getMintedAmount(uint256 _saleId, address _user)
external
view
returns (uint256)
{
return _minted[_saleId][_user];
}
//
// Public Write API
//
function saleMint(
uint256 _saleId,
address _user,
uint8 _quantity,
bytes32[] calldata _proof
) external payable {
// check if sale is registered and quantity is grater than zero
if (_saleId >= _sales.length) revert SaleNotFound(_saleId);
if (_quantity == 0) revert ZeroMintQuantity();
Sale memory sale = _sales[_saleId];
if (block.timestamp < sale.start || block.timestamp > sale.finish)
revert NotInSalePhase(
_saleId,
sale.start,
sale.finish,
block.timestamp
);
// validate whitelist
if (sale.whitelist) {
bytes32 leaf = keccak256(abi.encodePacked(_user));
if (!_verify(sale.root, _proof, leaf))
revert UserNotWhitelistedOrWrongProof(_saleId, _user, _proof);
}
// validate ETH amount send to contract
if (msg.value != _quantity * sale.price) {
revert WrongValueSentForMint(
_saleId,
msg.value,
sale.price,
_quantity
);
}
// validate individual mint limit
uint256 availableUser = sale.limit - _minted[_saleId][_user];
if (availableUser < _quantity) {
revert MaximumSaleLimitReached(_saleId, _user, sale.limit);
}
// validate total mint limit
uint256 mintedBefore = nextToMint;
uint256 availableTotal = 1 + MAX_MINT - mintedBefore;
if (availableTotal == 0) {
revert MaximumSupplyReached();
}
// bound the quantity to mint and increase mint count
uint256 quantityToMint = Math.min(availableTotal, _quantity);
_minted[_saleId][_user] += quantityToMint;
nextToMint += quantityToMint;
// mint NFTs
for (uint256 i = 0; i < quantityToMint; ) {
_mint(_user, mintedBefore + i);
unchecked {
++i;
}
}
// emit sale event
emit LogSale(_saleId, _user, quantityToMint);
// refund leftover eth to buyer
if (quantityToMint < _quantity) {
// can fail when minting through a contract
payable(msg.sender).transfer(
sale.price * (_quantity - quantityToMint)
);
}
}
//
// Internal
//
function _validateSaleParams(
uint64 _start,
uint64 _finish,
bool _whitelist,
bytes32 _root
) internal pure {
if (_start > _finish) revert InvalidSaleInterval(_start, _finish);
if (_whitelist && _root == bytes32(0)) revert InvalidWhitelistRoot();
}
/// @notice Internal merkle proof verification.
/// @dev Verify that `proof` is valid and `leaf` occurs in the merkle tree with root hash `merkleRoot`.
/// @param root The Merkle Tree Root to be used for verification
/// @param proof The merkle proof.
/// @param leaf The leaf node to find.
function _verify(
bytes32 root,
bytes32[] calldata proof,
bytes32 leaf
) internal pure returns (bool verified) {
verified = proof.verify(root, leaf);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.0;
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The proofs can be generated using the JavaScript library
* https://github.com/miguelmota/merkletreejs[merkletreejs].
* Note: the hashing algorithm should be keccak256 and pair sorting should be enabled.
*
* See `test/utils/cryptography/MerkleProof.test.js` for some examples.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the merkle tree could be reinterpreted as a leaf value.
*/
library MerkleProof {
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Calldata version of {verify}
*
* _Available since v4.7._
*/
function verifyCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*
* _Available since v4.4._
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Calldata version of {processProof}
*
* _Available since v4.7._
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be proved to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* _Available since v4.7._
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Calldata version of {multiProofVerify}
*
* _Available since v4.7._
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and the sibling nodes in `proof`,
* consuming from one or the other at each step according to the instructions given by
* `proofFlags`.
*
* _Available since v4.7._
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leavesLen = leaves.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
return hashes[totalHashes - 1];
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Calldata version of {processMultiProof}
*
* _Available since v4.7._
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leavesLen = leaves.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
return hashes[totalHashes - 1];
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding rounding
) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`.
// We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`.
// This gives `2**k < a <= 2**(k+1)` → `2**(k/2) <= sqrt(a) < 2 ** (k/2+1)`.
// Using an algorithm similar to the msb computation, we are able to compute `result = 2**(k/2)` which is a
// good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1;
uint256 x = a;
if (x >> 128 > 0) {
x >>= 128;
result <<= 64;
}
if (x >> 64 > 0) {
x >>= 64;
result <<= 32;
}
if (x >> 32 > 0) {
x >>= 32;
result <<= 16;
}
if (x >> 16 > 0) {
x >>= 16;
result <<= 8;
}
if (x >> 8 > 0) {
x >>= 8;
result <<= 4;
}
if (x >> 4 > 0) {
x >>= 4;
result <<= 2;
}
if (x >> 2 > 0) {
result <<= 1;
}
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
uint256 result = sqrt(a);
if (rounding == Rounding.Up && result * result < a) {
result += 1;
}
return result;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IONFT721.sol";
import "./ONFT721Core.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
// NOTE: this ONFT contract has no public minting logic.
// must implement your own minting logic in child classes
contract ONFT721 is ONFT721Core, ERC721, IERC721Receiver, IONFT721 {
constructor(string memory _name, string memory _symbol, address _lzEndpoint) ERC721(_name, _symbol) ONFT721Core(_lzEndpoint) {}
function supportsInterface(bytes4 interfaceId) public view virtual override(ONFT721Core, ERC721, IERC165) returns (bool) {
return interfaceId == type(IONFT721).interfaceId || super.supportsInterface(interfaceId);
}
function _debitFrom(address _from, uint16, bytes memory, uint _tokenId) internal virtual override {
require(_isApprovedOrOwner(_msgSender(), _tokenId), "ONFT721: not owner nor approved");
require(ERC721.ownerOf(_tokenId) == _from, "ONFT721: incorrect owner");
_burn(_tokenId);
}
function _creditTo(uint16, address _toAddress, uint _tokenId) internal virtual override {
_safeMint(_toAddress, _tokenId);
}
function onERC721Received(address _operator, address, uint, bytes memory) public virtual override returns (bytes4) {
// only allow `this` to tranfser token from others
if (_operator != address(this)) return bytes4(0);
return IERC721Receiver.onERC721Received.selector;
}
/// @notice Checks if there is a payload waiting to be delivered.
function hasStoredPayload(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (bool) {
return lzEndpoint.hasStoredPayload(_srcChainId, _srcAddress);
}
/// @notice Retries to send a payload in case an error occurs on receiving chain.
/// @dev In case an error occurs on receiving chain, the message is stuck in the pipeline until this function is called.
/// Reason for message failing once contracts are stet up correctly is usually running out of gas.
/// retry this message with a higher amount of gas.
/// Info on retriving stored payload: https://layerzero.gitbook.io/docs/guides/error-messages/storedpayload-detection
/// @param _srcChainId The source chain ID
/// @param _srcAddress The source address
/// @param _payload Message payload. THis can be retrieved from etherscan:
function retryPayload(
uint16 _srcChainId,
bytes calldata _srcAddress,
bytes calldata _payload) external payable {
lzEndpoint.retryPayload(_srcChainId, _srcAddress, _payload);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @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
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @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].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IONFT721Core.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
/**
* @dev Interface of the ONFT standard
*/
interface IONFT721 is IONFT721Core, IERC721 {
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IONFT721Core.sol";
import "../../lzApp/NonblockingLzApp.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
abstract contract ONFT721Core is NonblockingLzApp, ERC165, IONFT721Core {
constructor(address _lzEndpoint) NonblockingLzApp(_lzEndpoint) {}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return interfaceId == type(IONFT721Core).interfaceId || super.supportsInterface(interfaceId);
}
function estimateSendFee(uint16 _dstChainId, bytes memory _toAddress, uint _tokenId, bool _useZro, bytes memory _adapterParams) public view virtual override returns (uint nativeFee, uint zroFee) {
// mock the payload for send()
bytes memory payload = abi.encode(_toAddress, _tokenId);
return lzEndpoint.estimateFees(_dstChainId, address(this), payload, _useZro, _adapterParams);
}
function send(uint16 _dstChainId, bytes memory _toAddress, uint _tokenId, address payable _refundAddress, address _zroPaymentAddress, bytes memory _adapterParams) public payable virtual override {
_send(msg.sender, _dstChainId, _toAddress, _tokenId, _refundAddress, _zroPaymentAddress, _adapterParams);
}
function _send(address _from, uint16 _dstChainId, bytes memory _toAddress, uint _tokenId, address payable _refundAddress, address _zroPaymentAddress, bytes memory _adapterParams) internal virtual {
_debitFrom(_from, _dstChainId, _toAddress, _tokenId);
bytes memory payload = abi.encode(_toAddress, _tokenId);
_lzSend(_dstChainId, payload, _refundAddress, _zroPaymentAddress, _adapterParams);
uint64 nonce = lzEndpoint.getOutboundNonce(_dstChainId, address(this));
emit SendToChain(_from, _dstChainId, _toAddress, _tokenId, nonce);
}
function _nonblockingLzReceive(uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes memory _payload) internal virtual override {
// decode and load the toAddress
(bytes memory toAddressBytes, uint tokenId) = abi.decode(_payload, (bytes, uint));
address toAddress;
assembly {
toAddress := mload(add(toAddressBytes, 20))
}
_creditTo(_srcChainId, toAddress, tokenId);
emit ReceiveFromChain(_srcChainId, _srcAddress, toAddress, tokenId, _nonce);
}
function _debitFrom(address _from, uint16 _dstChainId, bytes memory _toAddress, uint _tokenId) internal virtual;
function _creditTo(uint16 _srcChainId, address _toAddress, uint _tokenId) internal virtual;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.0;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/ERC721.sol)
pragma solidity ^0.8.0;
import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./extensions/IERC721Metadata.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/Strings.sol";
import "../../utils/introspection/ERC165.sol";
/**
* @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
* the Metadata extension, but not including the Enumerable extension, which is available separately as
* {ERC721Enumerable}.
*/
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
using Address for address;
using Strings for uint256;
// Token name
string private _name;
// Token symbol
string private _symbol;
// Mapping from token ID to owner address
mapping(uint256 => address) private _owners;
// Mapping owner address to token count
mapping(address => uint256) private _balances;
// Mapping from token ID to approved address
mapping(uint256 => address) private _tokenApprovals;
// Mapping from owner to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
/**
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC721-balanceOf}.
*/
function balanceOf(address owner) public view virtual override returns (uint256) {
require(owner != address(0), "ERC721: address zero is not a valid owner");
return _balances[owner];
}
/**
* @dev See {IERC721-ownerOf}.
*/
function ownerOf(uint256 tokenId) public view virtual override returns (address) {
address owner = _owners[tokenId];
require(owner != address(0), "ERC721: invalid token ID");
return owner;
}
/**
* @dev See {IERC721Metadata-name}.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev See {IERC721Metadata-symbol}.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev See {IERC721Metadata-tokenURI}.
*/
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
_requireMinted(tokenId);
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}
/**
* @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
* token will be the concatenation of the `baseURI` and the `tokenId`. Empty
* by default, can be overridden in child contracts.
*/
function _baseURI() internal view virtual returns (string memory) {
return "";
}
/**
* @dev See {IERC721-approve}.
*/
function approve(address to, uint256 tokenId) public virtual override {
address owner = ERC721.ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner");
require(
_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
"ERC721: approve caller is not token owner or approved for all"
);
_approve(to, tokenId);
}
/**
* @dev See {IERC721-getApproved}.
*/
function getApproved(uint256 tokenId) public view virtual override returns (address) {
_requireMinted(tokenId);
return _tokenApprovals[tokenId];
}
/**
* @dev See {IERC721-setApprovalForAll}.
*/
function setApprovalForAll(address operator, bool approved) public virtual override {
_setApprovalForAll(_msgSender(), operator, approved);
}
/**
* @dev See {IERC721-isApprovedForAll}.
*/
function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator];
}
/**
* @dev See {IERC721-transferFrom}.
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) public virtual override {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_transfer(from, to, tokenId);
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) public virtual override {
safeTransferFrom(from, to, tokenId, "");
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory data
) public virtual override {
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_safeTransfer(from, to, tokenId, data);
}
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* `data` is additional data, it has no specified format and it is sent in call to `to`.
*
* This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
* implement alternative mechanisms to perform token transfer, such as signature-based.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeTransfer(
address from,
address to,
uint256 tokenId,
bytes memory data
) internal virtual {
_transfer(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
}
/**
* @dev Returns whether `tokenId` exists.
*
* Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
*
* Tokens start existing when they are minted (`_mint`),
* and stop existing when they are burned (`_burn`).
*/
function _exists(uint256 tokenId) internal view virtual returns (bool) {
return _owners[tokenId] != address(0);
}
/**
* @dev Returns whether `spender` is allowed to manage `tokenId`.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
address owner = ERC721.ownerOf(tokenId);
return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
}
/**
* @dev Safely mints `tokenId` and transfers it to `to`.
*
* Requirements:
*
* - `tokenId` must not exist.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeMint(address to, uint256 tokenId) internal virtual {
_safeMint(to, tokenId, "");
}
/**
* @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
* forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
*/
function _safeMint(
address to,
uint256 tokenId,
bytes memory data
) internal virtual {
_mint(to, tokenId);
require(
_checkOnERC721Received(address(0), to, tokenId, data),
"ERC721: transfer to non ERC721Receiver implementer"
);
}
/**
* @dev Mints `tokenId` and transfers it to `to`.
*
* WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
*
* Requirements:
*
* - `tokenId` must not exist.
* - `to` cannot be the zero address.
*
* Emits a {Transfer} event.
*/
function _mint(address to, uint256 tokenId) internal virtual {
require(to != address(0), "ERC721: mint to the zero address");
require(!_exists(tokenId), "ERC721: token already minted");
_beforeTokenTransfer(address(0), to, tokenId);
_balances[to] += 1;
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
_afterTokenTransfer(address(0), to, tokenId);
}
/**
* @dev Destroys `tokenId`.
* The approval is cleared when the token is burned.
* This is an internal function that does not check if the sender is authorized to operate on the token.
*
* Requirements:
*
* - `tokenId` must exist.
*
* Emits a {Transfer} event.
*/
function _burn(uint256 tokenId) internal virtual {
address owner = ERC721.ownerOf(tokenId);
_beforeTokenTransfer(owner, address(0), tokenId);
// Clear approvals
delete _tokenApprovals[tokenId];
_balances[owner] -= 1;
delete _owners[tokenId];
emit Transfer(owner, address(0), tokenId);
_afterTokenTransfer(owner, address(0), tokenId);
}
/**
* @dev Transfers `tokenId` from `from` to `to`.
* As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
*
* Emits a {Transfer} event.
*/
function _transfer(
address from,
address to,
uint256 tokenId
) internal virtual {
require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
require(to != address(0), "ERC721: transfer to the zero address");
_beforeTokenTransfer(from, to, tokenId);
// Clear approvals from the previous owner
delete _tokenApprovals[tokenId];
_balances[from] -= 1;
_balances[to] += 1;
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
_afterTokenTransfer(from, to, tokenId);
}
/**
* @dev Approve `to` to operate on `tokenId`
*
* Emits an {Approval} event.
*/
function _approve(address to, uint256 tokenId) internal virtual {
_tokenApprovals[tokenId] = to;
emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
}
/**
* @dev Approve `operator` to operate on all of `owner` tokens
*
* Emits an {ApprovalForAll} event.
*/
function _setApprovalForAll(
address owner,
address operator,
bool approved
) internal virtual {
require(owner != operator, "ERC721: approve to caller");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/**
* @dev Reverts if the `tokenId` has not been minted yet.
*/
function _requireMinted(uint256 tokenId) internal view virtual {
require(_exists(tokenId), "ERC721: invalid token ID");
}
/**
* @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
* The call is not executed if the target address is not a contract.
*
* @param from address representing the previous owner of the given token ID
* @param to target address that will receive the tokens
* @param tokenId uint256 ID of the token to be transferred
* @param data bytes optional data to send along with the call
* @return bool whether the call correctly returned the expected magic value
*/
function _checkOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory data
) private returns (bool) {
if (to.isContract()) {
try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
return retval == IERC721Receiver.onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert("ERC721: transfer to non ERC721Receiver implementer");
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
} else {
return true;
}
}
/**
* @dev Hook that is called before any token transfer. This includes minting
* and burning.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
* transferred to `to`.
* - When `from` is zero, `tokenId` will be minted for `to`.
* - When `to` is zero, ``from``'s `tokenId` will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual {}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
/**
* @dev Interface of the ONFT Core standard
*/
interface IONFT721Core is IERC165 {
/**
* @dev estimate send token `_tokenId` to (`_dstChainId`, `_toAddress`)
* _dstChainId - L0 defined chain id to send tokens too
* _toAddress - dynamic bytes array which contains the address to whom you are sending tokens to on the dstChain
* _tokenId - token Id to transfer
* _useZro - indicates to use zro to pay L0 fees
* _adapterParams - flexible bytes array to indicate messaging adapter services in L0
*/
function estimateSendFee(uint16 _dstChainId, bytes calldata _toAddress, uint _tokenId, bool _useZro, bytes calldata _adapterParams) external view returns (uint nativeFee, uint zroFee);
/**
* @dev send token `_tokenId` to (`_dstChainId`, `_toAddress`) from `_from`
* `_toAddress` can be any size depending on the `dstChainId`.
* `_zroPaymentAddress` set to address(0x0) if not paying in ZRO (LayerZero Token)
* `_adapterParams` is a flexible bytes array to indicate messaging adapter services
*/
function send(uint16 _dstChainId, bytes calldata _toAddress, uint _tokenId, address payable _refundAddress, address _zroPaymentAddress, bytes calldata _adapterParams) external payable;
/**
* @dev Emitted when `_tokenId` are moved from the `_sender` to (`_dstChainId`, `_toAddress`)
* `_nonce` is the outbound nonce from
*/
event SendToChain(address indexed _sender, uint16 indexed _dstChainId, bytes indexed _toAddress, uint _tokenId, uint64 _nonce);
/**
* @dev Emitted when `_tokenId` are sent from `_srcChainId` to the `_toAddress` at this chain. `_nonce` is the inbound nonce.
*/
event ReceiveFromChain(uint16 indexed _srcChainId, bytes indexed _srcAddress, address indexed _toAddress, uint _tokenId, uint64 _nonce);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool _approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./LzApp.sol";
/*
* the default LayerZero messaging behaviour is blocking, i.e. any failed message will block the channel
* this abstract class try-catch all fail messages and store locally for future retry. hence, non-blocking
* NOTE: if the srcAddress is not configured properly, it will still block the message pathway from (srcChainId, srcAddress)
*/
abstract contract NonblockingLzApp is LzApp {
constructor(address _endpoint) LzApp(_endpoint) {}
mapping(uint16 => mapping(bytes => mapping(uint64 => bytes32))) public failedMessages;
event MessageFailed(uint16 _srcChainId, bytes _srcAddress, uint64 _nonce, bytes _payload);
// overriding the virtual function in LzReceiver
function _blockingLzReceive(uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes memory _payload) internal virtual override {
// try-catch all errors/exceptions
try this.nonblockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload) {
// do nothing
} catch {
// error / exception
failedMessages[_srcChainId][_srcAddress][_nonce] = keccak256(_payload);
emit MessageFailed(_srcChainId, _srcAddress, _nonce, _payload);
}
}
function nonblockingLzReceive(uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes memory _payload) public virtual {
// only internal transaction
require(_msgSender() == address(this), "LzReceiver: caller must be LzApp");
_nonblockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload);
}
//@notice override this function
function _nonblockingLzReceive(uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes memory _payload) internal virtual;
function retryMessage(uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes calldata _payload) public payable virtual {
// assert there is message to retry
bytes32 payloadHash = failedMessages[_srcChainId][_srcAddress][_nonce];
require(payloadHash != bytes32(0), "LzReceiver: no stored message");
require(keccak256(_payload) == payloadHash, "LzReceiver: invalid payload");
// clear the stored message
failedMessages[_srcChainId][_srcAddress][_nonce] = bytes32(0);
// execute the message. revert if it fails again
_nonblockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC721.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Metadata is IERC721 {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @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 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.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _HEX_SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @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);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "../interfaces/ILayerZeroReceiver.sol";
import "../interfaces/ILayerZeroUserApplicationConfig.sol";
import "../interfaces/ILayerZeroEndpoint.sol";
/*
* a generic LzReceiver implementation
*/
abstract contract LzApp is Ownable, ILayerZeroReceiver, ILayerZeroUserApplicationConfig {
ILayerZeroEndpoint public immutable lzEndpoint;
mapping(uint16 => bytes) public trustedRemoteLookup;
event SetTrustedRemote(uint16 _srcChainId, bytes _srcAddress);
constructor(address _endpoint) {
lzEndpoint = ILayerZeroEndpoint(_endpoint);
}
function lzReceive(uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes memory _payload) public virtual override {
// lzReceive must be called by the endpoint for security
require(_msgSender() == address(lzEndpoint), "LzReceiver: !endpoint");
bytes memory trustedRemote = trustedRemoteLookup[_srcChainId];
// if will still block the message pathway from (srcChainId, srcAddress). should not receive message from untrusted remote.
require(_srcAddress.length == trustedRemote.length && keccak256(_srcAddress) == keccak256(trustedRemote), "LzReceiver: invalid source");
_blockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload);
}
// abstract function - the default behaviour of LayerZero is blocking. See: NonblockingLzApp if you dont need to enforce ordered messaging
function _blockingLzReceive(uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes memory _payload) internal virtual;
function _lzSend(uint16 _dstChainId, bytes memory _payload, address payable _refundAddress, address _zroPaymentAddress, bytes memory _adapterParams) internal virtual {
bytes memory trustedRemote = trustedRemoteLookup[_dstChainId];
require(trustedRemote.length != 0, "LzSend: !trusted source.");
lzEndpoint.send{value: msg.value}(_dstChainId, trustedRemote, _payload, _refundAddress, _zroPaymentAddress, _adapterParams);
}
//---------------------------UserApplication config----------------------------------------
function getConfig(uint16 _version, uint16 _chainId, address, uint _configType) external view returns (bytes memory) {
return lzEndpoint.getConfig(_version, _chainId, address(this), _configType);
}
// generic config for LayerZero user Application
function setConfig(uint16 _version, uint16 _chainId, uint _configType, bytes calldata _config) external override onlyOwner {
lzEndpoint.setConfig(_version, _chainId, _configType, _config);
}
function setSendVersion(uint16 _version) external override onlyOwner {
lzEndpoint.setSendVersion(_version);
}
function setReceiveVersion(uint16 _version) external override onlyOwner {
lzEndpoint.setReceiveVersion(_version);
}
function forceResumeReceive(uint16 _srcChainId, bytes calldata _srcAddress) external override onlyOwner {
lzEndpoint.forceResumeReceive(_srcChainId, _srcAddress);
}
// allow owner to set it multiple times.
function setTrustedRemote(uint16 _srcChainId, bytes calldata _srcAddress) external onlyOwner {
trustedRemoteLookup[_srcChainId] = _srcAddress;
emit SetTrustedRemote(_srcChainId, _srcAddress);
}
//--------------------------- VIEW FUNCTION ----------------------------------------
function isTrustedRemote(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (bool) {
bytes memory trustedSource = trustedRemoteLookup[_srcChainId];
return keccak256(trustedSource) == keccak256(_srcAddress);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @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.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* 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.
*/
abstract 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() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the 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 virtual onlyOwner {
_transferOwnership(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 virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface ILayerZeroReceiver {
// @notice LayerZero endpoint will invoke this function to deliver the message on the destination
// @param _srcChainId - the source endpoint identifier
// @param _srcAddress - the source sending contract address from the source chain
// @param _nonce - the ordered message nonce
// @param _payload - the signed payload is the UA bytes has encoded to be sent
function lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64 _nonce, bytes calldata _payload) external;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface ILayerZeroUserApplicationConfig {
// @notice set the configuration of the LayerZero messaging library of the specified version
// @param _version - messaging library version
// @param _chainId - the chainId for the pending config change
// @param _configType - type of configuration. every messaging library has its own convention.
// @param _config - configuration in the bytes. can encode arbitrary content.
function setConfig(uint16 _version, uint16 _chainId, uint _configType, bytes calldata _config) external;
// @notice set the send() LayerZero messaging library version to _version
// @param _version - new messaging library version
function setSendVersion(uint16 _version) external;
// @notice set the lzReceive() LayerZero messaging library version to _version
// @param _version - new messaging library version
function setReceiveVersion(uint16 _version) external;
// @notice Only when the UA needs to resume the message flow in blocking mode and clear the stored payload
// @param _srcChainId - the chainId of the source chain
// @param _srcAddress - the contract address of the source contract at the source chain
function forceResumeReceive(uint16 _srcChainId, bytes calldata _srcAddress) external;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "./ILayerZeroUserApplicationConfig.sol";
interface ILayerZeroEndpoint is ILayerZeroUserApplicationConfig {
// @notice send a LayerZero message to the specified address at a LayerZero endpoint.
// @param _dstChainId - the destination chain identifier
// @param _destination - the address on destination chain (in bytes). address length/format may vary by chains
// @param _payload - a custom bytes payload to send to the destination contract
// @param _refundAddress - if the source transaction is cheaper than the amount of value passed, refund the additional amount to this address
// @param _zroPaymentAddress - the address of the ZRO token holder who would pay for the transaction
// @param _adapterParams - parameters for custom functionality. e.g. receive airdropped native gas from the relayer on destination
function send(uint16 _dstChainId, bytes calldata _destination, bytes calldata _payload, address payable _refundAddress, address _zroPaymentAddress, bytes calldata _adapterParams) external payable;
// @notice used by the messaging library to publish verified payload
// @param _srcChainId - the source chain identifier
// @param _srcAddress - the source contract (as bytes) at the source chain
// @param _dstAddress - the address on destination chain
// @param _nonce - the unbound message ordering nonce
// @param _gasLimit - the gas limit for external contract execution
// @param _payload - verified payload to send to the destination contract
function receivePayload(uint16 _srcChainId, bytes calldata _srcAddress, address _dstAddress, uint64 _nonce, uint _gasLimit, bytes calldata _payload) external;
// @notice get the inboundNonce of a lzApp from a source chain which could be EVM or non-EVM chain
// @param _srcChainId - the source chain identifier
// @param _srcAddress - the source chain contract address
function getInboundNonce(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (uint64);
// @notice get the outboundNonce from this source chain which, consequently, is always an EVM
// @param _srcAddress - the source chain contract address
function getOutboundNonce(uint16 _dstChainId, address _srcAddress) external view returns (uint64);
// @notice gets a quote in source native gas, for the amount that send() requires to pay for message delivery
// @param _dstChainId - the destination chain identifier
// @param _userApplication - the user app address on this EVM chain
// @param _payload - the custom message to send over LayerZero
// @param _payInZRO - if false, user app pays the protocol fee in native token
// @param _adapterParam - parameters for the adapter service, e.g. send some dust native token to dstChain
function estimateFees(uint16 _dstChainId, address _userApplication, bytes calldata _payload, bool _payInZRO, bytes calldata _adapterParam) external view returns (uint nativeFee, uint zroFee);
// @notice get this Endpoint's immutable source identifier
function getChainId() external view returns (uint16);
// @notice the interface to retry failed message on this Endpoint destination
// @param _srcChainId - the source chain identifier
// @param _srcAddress - the source chain contract address
// @param _payload - the payload to be retried
function retryPayload(uint16 _srcChainId, bytes calldata _srcAddress, bytes calldata _payload) external;
// @notice query if any STORED payload (message blocking) at the endpoint.
// @param _srcChainId - the source chain identifier
// @param _srcAddress - the source chain contract address
function hasStoredPayload(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (bool);
// @notice query if the _libraryAddress is valid for sending msgs.
// @param _userApplication - the user app address on this EVM chain
function getSendLibraryAddress(address _userApplication) external view returns (address);
// @notice query if the _libraryAddress is valid for receiving msgs.
// @param _userApplication - the user app address on this EVM chain
function getReceiveLibraryAddress(address _userApplication) external view returns (address);
// @notice query if the non-reentrancy guard for send() is on
// @return true if the guard is on. false otherwise
function isSendingPayload() external view returns (bool);
// @notice query if the non-reentrancy guard for receive() is on
// @return true if the guard is on. false otherwise
function isReceivingPayload() external view returns (bool);
// @notice get the configuration of the LayerZero messaging library of the specified version
// @param _version - messaging library version
// @param _chainId - the chainId for the pending config change
// @param _userApplication - the contract address of the user application
// @param _configType - type of configuration. every messaging library has its own convention.
function getConfig(uint16 _version, uint16 _chainId, address _userApplication, uint _configType) external view returns (bytes memory);
// @notice get the send() LayerZero messaging library version
// @param _userApplication - the contract address of the user application
function getSendVersion(address _userApplication) external view returns (uint16);
// @notice get the lzReceive() LayerZero messaging library version
// @param _userApplication - the contract address of the user application
function getReceiveVersion(address _userApplication) external view returns (uint16);
}