// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
interface IERC20 {
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function transferFrom(address sender, address recipient, uint amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}
contract Ownable {
/// @notice The owner of the contract
address public owner;
/// @notice Event to notify when the ownership of this contract changed
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/// @notice Set the owner of this contract to its creator
constructor () {
owner = msg.sender;
emit OwnershipTransferred(address(0), msg.sender);
}
modifier onlyOwner() {
require(owner == msg.sender, "caller is not the owner");
_;
}
/**
* @notice Transfer ownership to `newOwner`
* @param newOwner The address to transfer the ownership to
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "new owner is the zero address");
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
contract TokenMigrator is Ownable {
/// @notice Token to migrate from
IERC20 public fromToken;
/// @notice Token to migrate to
IERC20 public toToken;
/// @notice The address where fromToken should be burned
address public constant BURN_ADDRESS = 0x0000000000000000000000000000000000000008;
/// @notice Notice period before migration can be closed
uint256 public constant endMigrationNoticePeriod = 2 weeks;
/// @notice Flag that indicates whether migration is possible
bool public migrationEnabled = false;
/// @notice The migration end date
uint256 public endMigrationDate = type(uint256).max;
/// @notice Event to notify when the endMigrationDate is set
event CloseMigrationNotice(uint256 epochTime);
/**
* @notice Construct a Migration contract
* @param migrateFromToken The token to migrate from
* @param migrateToToken The token to migrate into
*/
constructor(address migrateFromToken, address migrateToToken) {
fromToken = IERC20(migrateFromToken);
toToken = IERC20(migrateToToken);
}
modifier whenMigrationEnabled() {
require(migrationEnabled, "migration not enabled");
_;
}
/**
* @notice Start the migration. Can only be called if this contract has enough balance of toToken
*/
function startMigration() public {
uint256 requiredToTokenBalance = fromToken.totalSupply() - fromToken.balanceOf(BURN_ADDRESS);
require(toToken.balanceOf(address(this)) >= requiredToTokenBalance, "not enough toToken balance");
migrationEnabled = true;
}
/**
* @notice Migrate `amount` of tokens
* @param amount How many tokens to migrate
*/
function migrate(uint256 amount) public whenMigrationEnabled returns (bool success) {
require(fromToken.transferFrom(msg.sender, BURN_ADDRESS, amount), "burning fromToken failed");
require(toToken.transfer(msg.sender, amount), "sending toToken failed");
return true;
}
/**
* @notice Announce the migration can be closed after `endMigrationNoticePeriod`
*/
function announceMigrationEnd() public onlyOwner whenMigrationEnabled {
endMigrationDate = block.timestamp + endMigrationNoticePeriod;
emit CloseMigrationNotice(endMigrationDate);
}
/**
* @notice End the migration period. Can only be called after `endMigrationDate`
* This also transfers the remainingBalance of toToken back to the owner
*/
function closeMigration() public onlyOwner whenMigrationEnabled {
require(block.timestamp > endMigrationDate, "migration cannot be closed yet");
migrationEnabled = false;
uint256 remainingBalance = toToken.balanceOf(address(this));
require(toToken.transfer(owner, remainingBalance), "recovering toToken failed");
}
}
{
"compilationTarget": {
"TokenMigrator.sol": "TokenMigrator"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"migrateFromToken","type":"address"},{"internalType":"address","name":"migrateToToken","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"epochTime","type":"uint256"}],"name":"CloseMigrationNotice","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"},{"inputs":[],"name":"BURN_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"announceMigrationEnd","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"closeMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"endMigrationDate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"endMigrationNoticePeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fromToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"migrate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"migrationEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"startMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"toToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]