// 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 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
// OpenZeppelin Contracts (last updated v4.9.0) (utils/introspection/IERC1820Registry.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the global ERC1820 Registry, as defined in the
* https://eips.ethereum.org/EIPS/eip-1820[EIP]. Accounts may register
* implementers for interfaces in this registry, as well as query support.
*
* Implementers may be shared by multiple accounts, and can also implement more
* than a single interface for each account. Contracts can implement interfaces
* for themselves, but externally-owned accounts (EOA) must delegate this to a
* contract.
*
* {IERC165} interfaces can also be queried via the registry.
*
* For an in-depth explanation and source code analysis, see the EIP text.
*/
interface IERC1820Registry {
event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer);
event ManagerChanged(address indexed account, address indexed newManager);
/**
* @dev Sets `newManager` as the manager for `account`. A manager of an
* account is able to set interface implementers for it.
*
* By default, each account is its own manager. Passing a value of `0x0` in
* `newManager` will reset the manager to this initial state.
*
* Emits a {ManagerChanged} event.
*
* Requirements:
*
* - the caller must be the current manager for `account`.
*/
function setManager(address account, address newManager) external;
/**
* @dev Returns the manager for `account`.
*
* See {setManager}.
*/
function getManager(address account) external view returns (address);
/**
* @dev Sets the `implementer` contract as ``account``'s implementer for
* `interfaceHash`.
*
* `account` being the zero address is an alias for the caller's address.
* The zero address can also be used in `implementer` to remove an old one.
*
* See {interfaceHash} to learn how these are created.
*
* Emits an {InterfaceImplementerSet} event.
*
* Requirements:
*
* - the caller must be the current manager for `account`.
* - `interfaceHash` must not be an {IERC165} interface id (i.e. it must not
* end in 28 zeroes).
* - `implementer` must implement {IERC1820Implementer} and return true when
* queried for support, unless `implementer` is the caller. See
* {IERC1820Implementer-canImplementInterfaceForAddress}.
*/
function setInterfaceImplementer(address account, bytes32 _interfaceHash, address implementer) external;
/**
* @dev Returns the implementer of `interfaceHash` for `account`. If no such
* implementer is registered, returns the zero address.
*
* If `interfaceHash` is an {IERC165} interface id (i.e. it ends with 28
* zeroes), `account` will be queried for support of it.
*
* `account` being the zero address is an alias for the caller's address.
*/
function getInterfaceImplementer(address account, bytes32 _interfaceHash) external view returns (address);
/**
* @dev Returns the interface hash for an `interfaceName`, as defined in the
* corresponding
* https://eips.ethereum.org/EIPS/eip-1820#interface-name[section of the EIP].
*/
function interfaceHash(string calldata interfaceName) external pure returns (bytes32);
/**
* @notice Updates the cache with whether the contract implements an ERC165 interface or not.
* @param account Address of the contract for which to update the cache.
* @param interfaceId ERC165 interface for which to update the cache.
*/
function updateERC165Cache(address account, bytes4 interfaceId) external;
/**
* @notice Checks whether a contract implements an ERC165 interface or not.
* If the result is not cached a direct lookup on the contract address is performed.
* If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling
* {updateERC165Cache} with the contract address.
* @param account Address of the contract to check.
* @param interfaceId ERC165 interface to check.
* @return True if `account` implements `interfaceId`, false otherwise.
*/
function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool);
/**
* @notice Checks whether a contract implements an ERC165 interface or not without using or updating the cache.
* @param account Address of the contract to check.
* @param interfaceId ERC165 interface to check.
* @return True if `account` implements `interfaceId`, false otherwise.
*/
function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC777/IERC777Recipient.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC777TokensRecipient standard as defined in the EIP.
*
* Accounts can be notified of {IERC777} tokens being sent to them by having a
* contract implement this interface (contract holders can be their own
* implementer) and registering it on the
* https://eips.ethereum.org/EIPS/eip-1820[ERC1820 global registry].
*
* See {IERC1820Registry} and {ERC1820Implementer}.
*/
interface IERC777Recipient {
/**
* @dev Called by an {IERC777} token contract whenever tokens are being
* moved or created into a registered account (`to`). The type of operation
* is conveyed by `from` being the zero address or not.
*
* This call occurs _after_ the token contract's state is updated, so
* {IERC777-balanceOf}, etc., can be used to query the post-operation state.
*
* This function may revert to prevent the operation from being executed.
*/
function tokensReceived(
address operator,
address from,
address to,
uint256 amount,
bytes calldata userData,
bytes calldata operatorData
) external;
}
// _ _ _ _ _____ ___ _ __ ____ __ __ __ _ _ _ ___ _ _
// | | | | | | |/ / __|/ _ \ | |\ \ / /\ \/ /___ | \/ (_)__ _ _ _ __ _| |_(_)___ _ _ / __|___ _ _| |_ _ _ __ _ __| |_
// | |_| |_| | ' <\__ \ (_) | | |_\ V / > </ -_) | |\/| | / _` | '_/ _` | _| / _ \ ' \ | (__/ _ \ ' \ _| '_/ _` / _| _|
// |____\___/|_|\_\___/\___/ |____|_| /_/\_\___| |_| |_|_\__, |_| \__,_|\__|_\___/_||_| \___\___/_||_\__|_| \__,_\__|\__|
// |___/
// SPDX-License-Identifier: CC0-1.0
pragma solidity 0.8.17;
// interfaces
import {
IERC1820Registry
} from "@openzeppelin/contracts/interfaces/IERC1820Registry.sol";
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
import {
IERC777Recipient
} from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol";
// modules
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
contract LUKSOMigrationDepositContract is
IERC777Recipient,
IERC165,
Ownable2Step
{
// The SenderDepositData struct stores the data of a deposit made by a sender
struct SenderDepositData {
address destinationAddress;
uint96 amount;
uint256 depositId;
}
// The DestinationDepositData struct stores the data of a deposit made to a destination
struct DestinationDepositData {
address senderAddress;
uint96 amount;
uint256 depositId;
}
// The DepositData struct stores the data of a deposit
struct DepositData {
address senderAddress;
address destinationAddress;
uint96 amount;
}
// The address of the LYXe token contract.
address public constant LYX_TOKEN_CONTRACT_ADDRESS =
0xA8b919680258d369114910511cc87595aec0be6D;
// The address of the registry contract (ERC1820 Registry).
address public constant ERC1820_REGISTRY_ADDRESS =
0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24;
// The hash of the interface of the contract that receives tokens.
bytes32 private constant TOKENS_RECIPIENT_INTERFACE_HASH =
0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b;
// The deposit id
uint256 public migrationDepositCount;
// Determines whether the contract is paused or not. Deposits are disabled and not allowed while the contract is paused.
bool public paused;
// The '_senderDeposits' mapping stores an array of depositId for each sender address
mapping(address => uint256[]) private _senderDeposits;
// The '_destinationDeposits' mapping stores an array of depositId for each destination address
mapping(address => uint256[]) private _destinationDeposits;
// The '_deposits' mapping stores the DepositData struct of a depositId
mapping(uint256 => DepositData) private _deposits;
/**
* @param sender The address of the sender migrating LYXe.
* @param destinationAddress The address on the LUKSO main network that will receive the equivalent `amount` in LYX.
* @param amount The amount of LYXe being migrated.
* @param depositId The unique identifier associated with this particular migration transaction.
* @param extraData Optional additional data related to the migration transaction.
*/
event Deposit(
address indexed sender,
address indexed destinationAddress,
uint256 amount,
uint256 indexed depositId,
bytes extraData
);
// Emits a pause status changed event.
event PauseStatusChanged(bool newPauseStatus);
constructor(address owner_) {
// Set this contract as the implementer of the tokens recipient interface in the registry contract.
IERC1820Registry(ERC1820_REGISTRY_ADDRESS).setInterfaceImplementer(
address(this),
TOKENS_RECIPIENT_INTERFACE_HASH,
address(this)
);
// Set the paused state to false
paused = false;
emit PauseStatusChanged(false);
// Set the owner
_transferOwnership(owner_);
}
/**
* @dev Whenever this contract receives LYXe tokens, it must be for the reason of
* migrating LYXe.
*/
function tokensReceived(
address /* operator */,
address sender_,
address /* to */,
uint256 amount_,
bytes calldata depositData_,
bytes calldata /* operatorData */
) external {
// Check if the caller is the LYXe token contract.
require(
msg.sender == LYX_TOKEN_CONTRACT_ADDRESS,
"LUKSOMigrationDepositContract: Only LYXe can be migrated"
);
// Check that we are migrating at least 1 LYXe
require(
amount_ >= 1 ether,
"LUKSOMigrationDepositContract: A minimum of 1 LYXe is required"
);
// Check if depositData length is superior to 20
require(
depositData_.length >= 20,
"LUKSOMigrationDepositContract: depositData length must be superior to 20"
);
// Check that the contract is not paused.
require(!paused, "LUKSOMigrationDepositContract: Contract is paused");
// Check if the deposit amount is not bigger than the maximum uint96 value.
require(
amount_ < type(uint96).max,
"LUKSOMigrationDepositContract: LYXe amount too large"
);
uint96 migrationDepositAmount = uint96(amount_);
address destinationAddress = address(bytes20(depositData_));
uint256 depositId = migrationDepositCount++;
// Append the depositId to the sender's deposits array.
_senderDeposits[sender_].push(depositId);
// Append the depositId to the destination's deposits array.
_destinationDeposits[destinationAddress].push(depositId);
// Store the deposit data
_deposits[depositId] = DepositData(
sender_,
destinationAddress,
migrationDepositAmount
);
// Emit the Deposit event with the sender, the migration address and the amount.
emit Deposit(
sender_,
destinationAddress,
amount_,
depositId,
depositData_[20:]
);
}
/**
* @dev Only the owner can pause the contract.
* @param pauseStatus The status of the pause.
*/
function setPaused(bool pauseStatus) external onlyOwner {
require(
paused != pauseStatus,
"LUKSOMigrationDepositContract: Pause status is already set to this value"
);
paused = pauseStatus;
emit PauseStatusChanged(pauseStatus);
}
/**
* @dev Query the deposits made by a given address.
*
* @param senderAddress_ The address of the sender whose deposits should be queried.
* @return An array representing each SenderDepositData made successively by the `sender_`.
*/
function getDepositsBySenderAddress(
address senderAddress_
) external view returns (SenderDepositData[] memory) {
uint256[] memory senderDepositsIds = _senderDeposits[senderAddress_];
uint256 numberOfDeposits = senderDepositsIds.length;
SenderDepositData[] memory senderDepositsList = new SenderDepositData[](
numberOfDeposits
);
for (uint256 i = 0; i < numberOfDeposits; i++) {
uint256 depositId = senderDepositsIds[i];
DepositData memory deposit = _deposits[depositId];
senderDepositsList[i] = SenderDepositData(
deposit.destinationAddress,
deposit.amount,
depositId
);
}
return senderDepositsList;
}
/**
* @dev Query the migrations made to a given address.
*
* @param destinationAddress_ The address of the destination whose migrations should be queried.
* @return An array representing each DestinationDepositData made successively to the `destinationAddress_`.
*/
function getDepositsByDestinationAddress(
address destinationAddress_
) external view returns (DestinationDepositData[] memory) {
uint256[] memory destinationDepositsIds = _destinationDeposits[
destinationAddress_
];
uint256 numberOfDeposits = destinationDepositsIds.length;
DestinationDepositData[]
memory destinationDepositsList = new DestinationDepositData[](
numberOfDeposits
);
for (uint256 i = 0; i < numberOfDeposits; i++) {
uint256 depositId = destinationDepositsIds[i];
DepositData memory deposit = _deposits[depositId];
destinationDepositsList[i] = DestinationDepositData(
deposit.senderAddress,
deposit.amount,
depositId
);
}
return destinationDepositsList;
}
/**
* @dev Query the deposit data of a given depositId.
*
* @param depositId_ The depositId of the deposit to query.
* @return The DepositData of the deposit.
*/
function getDeposit(
uint256 depositId_
) external view returns (DepositData memory) {
return _deposits[depositId_];
}
/**
* @dev Determines whether the contract supports a given interface.
*
* @param interfaceId The interface ID to check.
* @return True if the contract supports the interface, false otherwise.
*/
function supportsInterface(
bytes4 interfaceId
) external pure override returns (bool) {
return
interfaceId == type(IERC165).interfaceId ||
interfaceId == type(IERC777Recipient).interfaceId;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling 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
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.0;
import "./Ownable.sol";
/**
* @dev Contract module which provides 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} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
_transferOwnership(sender);
}
}
{
"compilationTarget": {
"contracts/LUKSOMigrationDepositContract.sol": "LUKSOMigrationDepositContract"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"owner_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"destinationAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"extraData","type":"bytes"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"newPauseStatus","type":"bool"}],"name":"PauseStatusChanged","type":"event"},{"inputs":[],"name":"ERC1820_REGISTRY_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LYX_TOKEN_CONTRACT_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"depositId_","type":"uint256"}],"name":"getDeposit","outputs":[{"components":[{"internalType":"address","name":"senderAddress","type":"address"},{"internalType":"address","name":"destinationAddress","type":"address"},{"internalType":"uint96","name":"amount","type":"uint96"}],"internalType":"struct LUKSOMigrationDepositContract.DepositData","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"destinationAddress_","type":"address"}],"name":"getDepositsByDestinationAddress","outputs":[{"components":[{"internalType":"address","name":"senderAddress","type":"address"},{"internalType":"uint96","name":"amount","type":"uint96"},{"internalType":"uint256","name":"depositId","type":"uint256"}],"internalType":"struct LUKSOMigrationDepositContract.DestinationDepositData[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"senderAddress_","type":"address"}],"name":"getDepositsBySenderAddress","outputs":[{"components":[{"internalType":"address","name":"destinationAddress","type":"address"},{"internalType":"uint96","name":"amount","type":"uint96"},{"internalType":"uint256","name":"depositId","type":"uint256"}],"internalType":"struct LUKSOMigrationDepositContract.SenderDepositData[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"migrationDepositCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"pauseStatus","type":"bool"}],"name":"setPaused","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"sender_","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"amount_","type":"uint256"},{"internalType":"bytes","name":"depositData_","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"tokensReceived","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]