wormhole/ethereum/contracts/Wormhole.sol

249 lines
8.6 KiB
Solidity
Raw Normal View History

2020-07-26 09:04:45 -07:00
// contracts/Wormhole.sol
2020-08-03 06:09:40 -07:00
// SPDX-License-Identifier: Apache 2
2020-07-26 09:04:45 -07:00
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
2020-08-06 07:26:25 -07:00
import "@openzeppelin/contracts/math/SafeMath.sol";
2020-08-03 06:09:40 -07:00
import "./BytesLib.sol";
import "./SchnorrSECP256K1.sol";
import "./WrappedAsset.sol";
2020-07-26 09:04:45 -07:00
contract Wormhole {
using SafeERC20 for IERC20;
2020-08-03 06:09:40 -07:00
using BytesLib for bytes;
2020-08-06 07:26:25 -07:00
using SafeMath for uint256;
2020-07-26 09:04:45 -07:00
2020-08-06 04:38:25 -07:00
// Address of the Wrapped asset template
address public wrappedAssetMaster;
2020-07-26 09:04:45 -07:00
2020-08-03 06:09:40 -07:00
// Chain ID of Ethereum
2020-08-06 06:43:09 -07:00
uint8 CHAIN_ID = 2;
2020-07-26 09:04:45 -07:00
2020-08-03 06:09:40 -07:00
// Address of the official WETH contract
address constant WETHAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
struct GuardianSet {
uint256 x;
uint8 parity;
uint32 expiration_time;
2020-07-26 09:04:45 -07:00
}
2020-08-03 06:09:40 -07:00
event LogGuardianSetChanged(
GuardianSet indexed oldGuardian,
GuardianSet indexed newGuardian
2020-07-26 09:04:45 -07:00
);
event LogTokensLocked(
2020-08-06 06:43:09 -07:00
uint8 target_chain,
uint8 token_chain,
bytes32 indexed token,
bytes32 indexed sender,
bytes32 recipient,
2020-07-26 09:04:45 -07:00
uint256 amount
);
2020-08-03 06:09:40 -07:00
// Mapping of guardian_set_index => guardian set
2020-08-06 14:32:31 -07:00
mapping(uint32 => GuardianSet) public guardian_sets;
2020-08-03 06:09:40 -07:00
// Current active guardian set
uint32 public guardian_set_index;
// Period for which an vaa is valid in seconds
2020-08-04 07:59:38 -07:00
uint32 public vaa_expiry;
2020-08-03 06:09:40 -07:00
// Mapping of already consumedVAAs
mapping(bytes32 => bool) consumedVAAs;
// Mapping of wrapped asset ERC20 contracts
2020-08-09 12:16:18 -07:00
mapping(bytes32 => address) public wrappedAssets;
mapping(address => bool) public isWrappedAsset;
2020-08-03 06:09:40 -07:00
2020-08-06 14:32:31 -07:00
constructor(GuardianSet memory initial_guardian_set, address wrapped_asset_master, uint32 _vaa_expiry) public {
2020-08-03 06:09:40 -07:00
guardian_sets[0] = initial_guardian_set;
// Explicitly set for doc purposes
guardian_set_index = 0;
2020-08-06 14:32:31 -07:00
vaa_expiry = _vaa_expiry;
2020-08-06 04:38:25 -07:00
wrappedAssetMaster = wrapped_asset_master;
2020-08-03 06:09:40 -07:00
}
function submitVAA(
bytes calldata vaa
) public {
uint8 version = vaa.toUint8(0);
require(version == 1, "VAA version incompatible");
// Load 4 bytes starting from index 1
uint32 vaa_guardian_set_index = vaa.toUint32(1);
2020-08-05 13:44:28 -07:00
uint256 signature = vaa.toUint256(5);
address sig_address = vaa.toAddress(37);
2020-07-26 09:04:45 -07:00
2020-08-03 06:09:40 -07:00
// Load 4 bytes starting from index 77
2020-08-05 13:44:28 -07:00
uint32 timestamp = vaa.toUint32(57);
2020-07-26 09:04:45 -07:00
2020-08-03 06:09:40 -07:00
// Verify that the VAA is still valid
2020-08-06 14:32:31 -07:00
require(timestamp + vaa_expiry > block.timestamp, "VAA has expired");
2020-07-26 09:04:45 -07:00
2020-08-03 06:09:40 -07:00
// Hash the body
2020-08-05 13:44:28 -07:00
bytes32 hash = keccak256(vaa.slice(57, vaa.length - 57));
2020-08-03 06:09:40 -07:00
require(!consumedVAAs[hash], "VAA was already executed");
2020-07-26 09:04:45 -07:00
2020-08-03 06:09:40 -07:00
GuardianSet memory guardian_set = guardian_sets[vaa_guardian_set_index];
require(guardian_set.expiration_time == 0 || guardian_set.expiration_time > block.timestamp, "guardian set has expired");
require(
Schnorr.verifySignature(
guardian_set.x,
guardian_set.parity,
signature,
uint256(hash),
sig_address
),
2020-08-05 13:44:28 -07:00
"VAA signature invalid");
2020-08-03 06:09:40 -07:00
2020-08-05 13:44:28 -07:00
uint8 action = vaa.toUint8(61);
uint8 payload_len = vaa.toUint8(62);
bytes memory payload = vaa.slice(63, payload_len);
2020-08-03 06:09:40 -07:00
// Process VAA
if (action == 0x01) {
require(vaa_guardian_set_index == guardian_set_index, "only the current guardian set can change the guardian set");
2020-08-03 06:09:40 -07:00
vaaUpdateGuardianSet(payload);
} else if (action == 0x10) {
vaaTransfer(payload);
} else {
revert("invalid VAA action");
2020-07-26 09:04:45 -07:00
}
2020-08-03 06:09:40 -07:00
// Set the VAA as consumed
consumedVAAs[hash] = true;
2020-07-26 09:04:45 -07:00
}
2020-08-03 06:09:40 -07:00
function vaaUpdateGuardianSet(bytes memory data) private {
uint256 new_key_x = data.toUint256(0);
2020-08-06 14:32:31 -07:00
uint256 y_parity = data.toUint8(32);
uint32 new_guardian_set_index = data.toUint32(33);
2020-08-03 06:09:40 -07:00
require(new_guardian_set_index > guardian_set_index, "index of new guardian set must be > current");
2020-08-06 14:32:31 -07:00
require(y_parity <= 1, "invalid y parity");
2020-08-03 06:09:40 -07:00
uint32 old_guardian_set_index = guardian_set_index;
guardian_set_index = new_guardian_set_index;
2020-07-26 09:04:45 -07:00
2020-08-06 14:32:31 -07:00
GuardianSet memory new_guardian_set = GuardianSet(new_key_x, uint8(y_parity), 0);
2020-08-03 06:09:40 -07:00
guardian_sets[guardian_set_index] = new_guardian_set;
2020-08-04 07:59:38 -07:00
guardian_sets[old_guardian_set_index].expiration_time = uint32(block.timestamp) + vaa_expiry;
2020-07-26 09:04:45 -07:00
2020-08-03 06:09:40 -07:00
emit LogGuardianSetChanged(guardian_sets[old_guardian_set_index], new_guardian_set);
2020-07-26 09:04:45 -07:00
}
2020-08-03 06:09:40 -07:00
function vaaTransfer(bytes memory data) private {
2020-08-05 13:44:28 -07:00
//uint32 nonce = data.toUint64(0);
uint8 source_chain = data.toUint8(4);
2020-08-03 06:09:40 -07:00
2020-08-05 13:44:28 -07:00
uint8 target_chain = data.toUint8(5);
//bytes32 source_address = data.toBytes32(6);
//bytes32 target_address = data.toBytes32(38);
address target_address = data.toAddress(38 + 12);
2020-08-03 06:09:40 -07:00
2020-08-05 13:44:28 -07:00
uint8 token_chain = data.toUint8(70);
//bytes32 token_address = data.toBytes32(71);
uint256 amount = data.toUint256(103);
2020-08-03 06:09:40 -07:00
require(source_chain != target_chain, "same chain transfers are not supported");
require(target_chain == CHAIN_ID, "transfer must be incoming");
if (token_chain != CHAIN_ID) {
2020-08-05 13:44:28 -07:00
bytes32 token_address = data.toBytes32(71);
2020-08-03 06:09:40 -07:00
bytes32 asset_id = keccak256(abi.encodePacked(token_chain, token_address));
// if yes: mint to address
// if no: create and mint
address wrapped_asset = wrappedAssets[asset_id];
if (wrapped_asset == address(0)) {
wrapped_asset = deployWrappedAsset(asset_id, token_chain, token_address);
2020-07-26 09:04:45 -07:00
}
2020-08-03 06:09:40 -07:00
WrappedAsset(wrapped_asset).mint(target_address, amount);
} else {
2020-08-05 13:44:28 -07:00
address token_address = data.toAddress(71 + 12);
2020-08-03 06:09:40 -07:00
IERC20(token_address).safeTransfer(target_address, amount);
2020-07-26 09:04:45 -07:00
}
2020-08-03 06:09:40 -07:00
}
2020-07-26 09:04:45 -07:00
2020-08-06 04:38:25 -07:00
function deployWrappedAsset(bytes32 seed, uint8 token_chain, bytes32 token_address) private returns (address asset){
// Taken from https://github.com/OpenZeppelin/openzeppelin-sdk/blob/master/packages/lib/contracts/upgradeability/ProxyFactory.sol
// Licensed under MIT
bytes20 targetBytes = bytes20(wrappedAssetMaster);
2020-08-03 06:09:40 -07:00
assembly {
2020-08-06 04:38:25 -07:00
let clone := mload(0x40)
mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(clone, 0x14), targetBytes)
mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
asset := create(0, clone, 0x37)
2020-08-03 06:09:40 -07:00
}
2020-07-26 09:04:45 -07:00
2020-08-06 04:38:25 -07:00
// Call initializer
WrappedAsset(asset).initialize(token_chain, token_address);
2020-07-26 09:04:45 -07:00
2020-08-06 04:38:25 -07:00
// Store address
wrappedAssets[seed] = asset;
isWrappedAsset[asset] = true;
2020-07-26 09:04:45 -07:00
}
function lockAssets(
address asset,
uint256 amount,
2020-08-03 06:09:40 -07:00
bytes32 recipient,
2020-08-06 04:38:25 -07:00
uint8 target_chain
2020-07-26 09:04:45 -07:00
) public {
require(amount != 0, "amount must not be 0");
2020-08-06 06:43:09 -07:00
uint8 asset_chain = CHAIN_ID;
bytes32 asset_address;
2020-08-03 06:09:40 -07:00
if (isWrappedAsset[asset]) {
2020-08-06 04:38:25 -07:00
WrappedAsset(asset).burn(msg.sender, amount);
2020-08-06 06:43:09 -07:00
asset_chain = WrappedAsset(asset).assetChain();
asset_address = WrappedAsset(asset).assetAddress();
2020-08-03 06:09:40 -07:00
} else {
2020-08-06 07:26:25 -07:00
uint256 balanceBefore = IERC20(asset).balanceOf(address(this));
2020-08-03 06:09:40 -07:00
IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
2020-08-06 07:26:25 -07:00
uint256 balanceAfter = IERC20(asset).balanceOf(address(this));
// The amount that was transferred in is the delta between balance before and after the transfer.
// This is to properly handle tokens that charge a fee on transfer.
amount = balanceAfter.sub(balanceBefore);
2020-08-06 06:43:09 -07:00
asset_address = bytes32(uint256(asset));
2020-08-03 06:09:40 -07:00
}
2020-08-06 06:43:09 -07:00
2020-08-06 14:32:31 -07:00
emit LogTokensLocked(target_chain, asset_chain, asset_address, bytes32(uint256(msg.sender)), recipient, amount);
2020-07-26 09:04:45 -07:00
}
function lockETH(
2020-08-03 06:09:40 -07:00
bytes32 recipient,
uint8 target_chain
2020-07-26 09:04:45 -07:00
) public payable {
require(msg.value != 0, "amount must not be 0");
2020-07-28 03:39:38 -07:00
// Wrap tx value in WETH
WETH(WETHAddress).deposit{value : msg.value}();
// Log deposit of WETH
2020-08-06 14:32:31 -07:00
emit LogTokensLocked(target_chain, CHAIN_ID, bytes32(uint256(WETHAddress)), bytes32(uint256(msg.sender)), recipient, msg.value);
2020-07-26 09:04:45 -07:00
}
2020-07-28 03:39:38 -07:00
2020-08-03 06:09:40 -07:00
fallback() external payable {revert("please use lockETH to transfer ETH to Solana");}
receive() external payable {revert("please use lockETH to transfer ETH to Solana");}
2020-07-26 09:04:45 -07:00
}
2020-07-28 03:39:38 -07:00
interface WETH is IERC20 {
2020-08-03 06:09:40 -07:00
function deposit() external payable;
2020-07-26 09:04:45 -07:00
2020-08-03 06:09:40 -07:00
function withdraw(uint256 amount) external;
2020-07-26 09:04:45 -07:00
}