// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
abstract contract Migratable {
event PrepareMigration(uint256 migrationTimestamp, address source, address destination);
event ApproveMigration(uint256 migrationTimestamp, address source, address destination);
event ExecuteMigration(uint256 migrationTimestamp, address source, address destination);
address public controller;
uint256 public migrationTimestamp;
address public migrationDestination;
bool public isMigrated;
modifier _controller_() {
require(msg.sender == controller, 'Migratable._controller_: can only called by controller');
_;
}
modifier _valid_() {
require(!isMigrated, 'Migratable._valid_: cannot proceed, this contract has been migrated');
_;
}
function setController(address newController) public _controller_ _valid_ {
require(newController != address(0), 'Migratable.setController: to 0 address');
controller = newController;
}
function prepareMigration(address destination, uint256 graceDays) public _controller_ _valid_ {
require(destination != address(0), 'Migratable.prepareMigration: to 0 address');
require(graceDays >= 3 && graceDays <= 365, 'Migratable.prepareMigration: graceDays must be 3-365 days');
migrationTimestamp = block.timestamp + graceDays * 1 days;
migrationDestination = destination;
emit PrepareMigration(migrationTimestamp, address(this), migrationDestination);
}
function approveMigration() public virtual;
function executeMigration(address source) public virtual;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
import './Migratable.sol';
contract Vault is Migratable {
event Claim(address indexed account, uint256 amount, uint256 deadline, uint256 nonce);
string public constant name = 'MiningVault';
address public tokenAddress;
uint256 public chainId;
mapping (bytes32 => bool) public usedHash;
bytes32 public constant DOMAIN_TYPEHASH = keccak256('EIP712Domain(string name,uint256 chainId,address verifyingContract)');
bytes32 public constant CLAIM_TYPEHASH = keccak256('Claim(address account,uint256 amount,uint256 deadline,uint256 nonce)');
constructor (address tokenAddress_) {
controller = msg.sender;
tokenAddress = tokenAddress_;
uint256 _chainId;
assembly {
_chainId := chainid()
}
chainId = _chainId;
}
function approveMigration() public override _controller_ _valid_ {
require(migrationTimestamp != 0 && block.timestamp >= migrationTimestamp, 'Vault.approveMigration: migrationTimestamp not met yet');
IERC20(tokenAddress).approve(migrationDestination, type(uint256).max);
isMigrated = true;
emit ApproveMigration(migrationTimestamp, address(this), migrationDestination);
}
function executeMigration(address source) public override _controller_ _valid_ {
uint256 _migrationTimestamp = IVault(source).migrationTimestamp();
address _migrationDestination = IVault(source).migrationDestination();
require(_migrationTimestamp != 0 && block.timestamp >= _migrationTimestamp, 'Vault.executeMigration: migrationTimestamp not met yet');
require(_migrationDestination == address(this), 'Vault.executeMigration: not destination address');
IERC20(tokenAddress).transferFrom(source, address(this), IERC20(tokenAddress).balanceOf(source));
emit ExecuteMigration(_migrationTimestamp, source, address(this));
}
function claim(address account, uint256 amount, uint256 deadline, uint256 nonce, uint8 v, bytes32 r, bytes32 s) public _valid_ {
require(block.timestamp <= deadline, 'Vault.claim: signature expired');
bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), chainId, address(this)));
bytes32 structHash = keccak256(abi.encode(CLAIM_TYPEHASH, account, amount, deadline, nonce));
require(!usedHash[structHash], 'Vault.claim: replay');
usedHash[structHash] = true;
bytes32 digest = keccak256(abi.encodePacked('\x19\x01', domainSeparator, structHash));
address signatory = ecrecover(digest, v, r, s);
require(signatory == controller, 'Vault.claim: unauthorized');
IERC20(tokenAddress).transfer(account, amount);
emit Claim(account, amount, deadline, nonce);
}
}
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function approve(address account, uint256 amount) external returns (bool);
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
interface IVault {
function migrationTimestamp() external view returns (uint256);
function migrationDestination() external view returns (address);
}
{
"compilationTarget": {
"contracts/Vault.sol": "Vault"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"tokenAddress_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"migrationTimestamp","type":"uint256"},{"indexed":false,"internalType":"address","name":"source","type":"address"},{"indexed":false,"internalType":"address","name":"destination","type":"address"}],"name":"ApproveMigration","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"deadline","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"Claim","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"migrationTimestamp","type":"uint256"},{"indexed":false,"internalType":"address","name":"source","type":"address"},{"indexed":false,"internalType":"address","name":"destination","type":"address"}],"name":"ExecuteMigration","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"migrationTimestamp","type":"uint256"},{"indexed":false,"internalType":"address","name":"source","type":"address"},{"indexed":false,"internalType":"address","name":"destination","type":"address"}],"name":"PrepareMigration","type":"event"},{"inputs":[],"name":"CLAIM_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"approveMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"chainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"controller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"source","type":"address"}],"name":"executeMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isMigrated","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"migrationDestination","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"migrationTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"destination","type":"address"},{"internalType":"uint256","name":"graceDays","type":"uint256"}],"name":"prepareMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newController","type":"address"}],"name":"setController","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"tokenAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"usedHash","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]