// 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 Wormhole is Migratable {
event Freeze (
address indexed account,
uint256 amount,
uint256 fromChainId,
address fromWormhole,
uint256 toChainId,
address toWormhole,
uint256 nonce,
uint256 timestamp
);
event Claim (
address indexed account,
uint256 amount,
uint256 fromChainId,
address fromWormhole,
uint256 toChainId,
address toWormhole,
uint256 nonce,
uint256 timestamp
);
string public constant name = 'Wormhole';
address public tokenAddress;
bool public allowMintBurn;
uint256 public nonce;
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 fromChainId,address fromWormhole,uint256 toChainId,address toWormhole,uint256 nonce)'
);
constructor (address tokenAddress_, bool allowMintBurn_) {
controller = msg.sender;
tokenAddress = tokenAddress_;
allowMintBurn = allowMintBurn_;
uint256 _chainId;
assembly {
_chainId := chainid()
}
chainId = _chainId;
}
function approveMigration() public override _controller_ _valid_ {
require(migrationTimestamp != 0 && block.timestamp >= migrationTimestamp, 'Wormhole.approveMigration: migrationTimestamp not met yet');
if (allowMintBurn) {
IERC20(tokenAddress).setController(migrationDestination);
} else {
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 = IWormhole(source).migrationTimestamp();
address _migrationDestination = IWormhole(source).migrationDestination();
require(_migrationTimestamp != 0 && block.timestamp >= _migrationTimestamp, 'Wormhole.executeMigration: migrationTimestamp not met yet');
require(_migrationDestination == address(this), 'Wormhole.executeMigration: not destination address');
if (!IWormhole(source).allowMintBurn()) {
IERC20(tokenAddress).transferFrom(source, address(this), IERC20(tokenAddress).balanceOf(source));
}
emit ExecuteMigration(_migrationTimestamp, source, address(this));
}
function freeze(uint256 amount, uint256 toChainId, address toWormhole) public _valid_ {
require(amount > 0, 'Wormhole.freeze: 0 amount');
require(toChainId != chainId, 'Wormhole.freeze: to the same chain');
if (allowMintBurn) {
IERC20(tokenAddress).burn(msg.sender, amount);
} else {
IERC20(tokenAddress).transferFrom(msg.sender, address(this), amount);
}
emit Freeze(msg.sender, amount, chainId, address(this), toChainId, toWormhole, nonce++, block.timestamp);
}
function claim(uint256 amount, uint256 fromChainId, address fromWormhole, uint256 fromNonce, uint8 v, bytes32 r, bytes32 s) public _valid_ {
bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), chainId, address(this)));
bytes32 structHash = keccak256(abi.encode(CLAIM_TYPEHASH, msg.sender, amount, fromChainId, fromWormhole, chainId, address(this), fromNonce));
require(!usedHash[structHash], 'Wormhole.claim: replay');
usedHash[structHash] = true;
bytes32 digest = keccak256(abi.encodePacked('\x19\x01', domainSeparator, structHash));
address signatory = ecrecover(digest, v, r, s);
require(signatory == controller, 'Wormhole.claim: unauthorized');
if (allowMintBurn) {
IERC20(tokenAddress).mint(msg.sender, amount);
} else {
IERC20(tokenAddress).transfer(msg.sender, amount);
}
emit Claim(msg.sender, amount, fromChainId, fromWormhole, chainId, address(this), fromNonce, block.timestamp);
}
}
interface IERC20 {
function setController(address newController) external;
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);
function mint(address account, uint256 amount) external;
function burn(address account, uint256 amount) external;
}
interface IWormhole {
function migrationTimestamp() external view returns (uint256);
function migrationDestination() external view returns (address);
function allowMintBurn() external view returns (bool);
}
{
"compilationTarget": {
"contracts/Wormhole.sol": "Wormhole"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"tokenAddress_","type":"address"},{"internalType":"bool","name":"allowMintBurn_","type":"bool"}],"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":"fromChainId","type":"uint256"},{"indexed":false,"internalType":"address","name":"fromWormhole","type":"address"},{"indexed":false,"internalType":"uint256","name":"toChainId","type":"uint256"},{"indexed":false,"internalType":"address","name":"toWormhole","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","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":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fromChainId","type":"uint256"},{"indexed":false,"internalType":"address","name":"fromWormhole","type":"address"},{"indexed":false,"internalType":"uint256","name":"toChainId","type":"uint256"},{"indexed":false,"internalType":"address","name":"toWormhole","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"Freeze","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":"allowMintBurn","outputs":[{"internalType":"bool","name":"","type":"bool"}],"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":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"fromChainId","type":"uint256"},{"internalType":"address","name":"fromWormhole","type":"address"},{"internalType":"uint256","name":"fromNonce","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":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"toChainId","type":"uint256"},{"internalType":"address","name":"toWormhole","type":"address"}],"name":"freeze","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":[],"name":"nonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"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"}]