Wormhole.sol 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. // contracts/Wormhole.sol
  2. // SPDX-License-Identifier: Apache 2
  3. pragma solidity ^0.6.0;
  4. pragma experimental ABIEncoderV2;
  5. import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
  6. import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
  7. import "@openzeppelin/contracts/math/SafeMath.sol";
  8. import "./BytesLib.sol";
  9. import "./WrappedAsset.sol";
  10. contract Wormhole {
  11. using SafeERC20 for IERC20;
  12. using BytesLib for bytes;
  13. using SafeMath for uint256;
  14. // Address of the Wrapped asset template
  15. address public wrappedAssetMaster;
  16. // Chain ID of Ethereum
  17. uint8 CHAIN_ID = 2;
  18. // Address of the official WETH contract
  19. address constant WETHAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
  20. struct GuardianSet {
  21. address[] keys;
  22. uint32 expiration_time;
  23. }
  24. event LogGuardianSetChanged(
  25. GuardianSet indexed oldGuardian,
  26. GuardianSet indexed newGuardian
  27. );
  28. event LogTokensLocked(
  29. uint8 target_chain,
  30. uint8 token_chain,
  31. bytes32 indexed token,
  32. bytes32 indexed sender,
  33. bytes32 recipient,
  34. uint256 amount
  35. );
  36. // Mapping of guardian_set_index => guardian set
  37. mapping(uint32 => GuardianSet) public guardian_sets;
  38. // Current active guardian set
  39. uint32 public guardian_set_index;
  40. // Period for which an vaa is valid in seconds
  41. uint32 public vaa_expiry;
  42. // Mapping of already consumedVAAs
  43. mapping(bytes32 => bool) consumedVAAs;
  44. // Mapping of wrapped asset ERC20 contracts
  45. mapping(bytes32 => address) public wrappedAssets;
  46. mapping(address => bool) public isWrappedAsset;
  47. constructor(GuardianSet memory initial_guardian_set, address wrapped_asset_master, uint32 _vaa_expiry) public {
  48. guardian_sets[0] = initial_guardian_set;
  49. // Explicitly set for doc purposes
  50. guardian_set_index = 0;
  51. vaa_expiry = _vaa_expiry;
  52. wrappedAssetMaster = wrapped_asset_master;
  53. }
  54. function submitVAA(
  55. bytes calldata vaa
  56. ) public {
  57. uint8 version = vaa.toUint8(0);
  58. require(version == 1, "VAA version incompatible");
  59. // Load 4 bytes starting from index 1
  60. uint32 vaa_guardian_set_index = vaa.toUint32(1);
  61. uint256 len_signers = vaa.toUint8(5);
  62. uint offset = 6 + 66 * len_signers;
  63. // Load 4 bytes timestamp
  64. uint32 timestamp = vaa.toUint32(offset);
  65. // Verify that the VAA is still valid
  66. require(timestamp + vaa_expiry > block.timestamp, "VAA has expired");
  67. // Hash the body
  68. bytes32 hash = keccak256(vaa.slice(offset, vaa.length - offset));
  69. require(!consumedVAAs[hash], "VAA was already executed");
  70. GuardianSet memory guardian_set = guardian_sets[vaa_guardian_set_index];
  71. require(guardian_set.expiration_time == 0 || guardian_set.expiration_time > block.timestamp, "guardian set has expired");
  72. require(guardian_set.keys.length * 3 / 4 + 1 <= len_signers, "no quorum");
  73. for (uint i = 0; i < len_signers; i++) {
  74. uint8 index = vaa.toUint8(6 + i * 66);
  75. bytes32 r = vaa.toBytes32(7 + i * 66);
  76. bytes32 s = vaa.toBytes32(39 + i * 66);
  77. uint8 v = vaa.toUint8(71 + i * 66);
  78. v += 27;
  79. require(ecrecover(hash, v, r, s) == guardian_set.keys[index], "VAA signature invalid");
  80. }
  81. uint8 action = vaa.toUint8(offset + 4);
  82. uint8 payload_len = vaa.toUint8(offset + 5);
  83. bytes memory payload = vaa.slice(offset + 6, payload_len);
  84. // Process VAA
  85. if (action == 0x01) {
  86. require(vaa_guardian_set_index == guardian_set_index, "only the current guardian set can change the guardian set");
  87. vaaUpdateGuardianSet(payload);
  88. } else if (action == 0x10) {
  89. vaaTransfer(payload);
  90. } else {
  91. revert("invalid VAA action");
  92. }
  93. // Set the VAA as consumed
  94. consumedVAAs[hash] = true;
  95. }
  96. function vaaUpdateGuardianSet(bytes memory data) private {
  97. uint32 new_guardian_set_index = data.toUint32(0);
  98. uint8 len = data.toUint8(4);
  99. address[] memory new_guardians = new address[](len);
  100. for (uint i = 0; i < len; i++) {
  101. address addr = data.toAddress(5 + i * 20);
  102. new_guardians[i] = addr;
  103. }
  104. uint32 old_guardian_set_index = guardian_set_index;
  105. guardian_set_index = new_guardian_set_index;
  106. GuardianSet memory new_guardian_set = GuardianSet(new_guardians, 0);
  107. guardian_sets[guardian_set_index] = new_guardian_set;
  108. guardian_sets[old_guardian_set_index].expiration_time = uint32(block.timestamp) + vaa_expiry;
  109. emit LogGuardianSetChanged(guardian_sets[old_guardian_set_index], new_guardian_set);
  110. }
  111. function vaaTransfer(bytes memory data) private {
  112. //uint32 nonce = data.toUint64(0);
  113. uint8 source_chain = data.toUint8(4);
  114. uint8 target_chain = data.toUint8(5);
  115. //bytes32 source_address = data.toBytes32(6);
  116. //bytes32 target_address = data.toBytes32(38);
  117. address target_address = data.toAddress(38 + 12);
  118. uint8 token_chain = data.toUint8(70);
  119. //bytes32 token_address = data.toBytes32(71);
  120. uint256 amount = data.toUint256(103);
  121. require(source_chain != target_chain, "same chain transfers are not supported");
  122. require(target_chain == CHAIN_ID, "transfer must be incoming");
  123. if (token_chain != CHAIN_ID) {
  124. bytes32 token_address = data.toBytes32(71);
  125. bytes32 asset_id = keccak256(abi.encodePacked(token_chain, token_address));
  126. // if yes: mint to address
  127. // if no: create and mint
  128. address wrapped_asset = wrappedAssets[asset_id];
  129. if (wrapped_asset == address(0)) {
  130. wrapped_asset = deployWrappedAsset(asset_id, token_chain, token_address);
  131. }
  132. WrappedAsset(wrapped_asset).mint(target_address, amount);
  133. } else {
  134. address token_address = data.toAddress(71 + 12);
  135. IERC20(token_address).safeTransfer(target_address, amount);
  136. }
  137. }
  138. function deployWrappedAsset(bytes32 seed, uint8 token_chain, bytes32 token_address) private returns (address asset){
  139. // Taken from https://github.com/OpenZeppelin/openzeppelin-sdk/blob/master/packages/lib/contracts/upgradeability/ProxyFactory.sol
  140. // Licensed under MIT
  141. bytes20 targetBytes = bytes20(wrappedAssetMaster);
  142. assembly {
  143. let clone := mload(0x40)
  144. mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
  145. mstore(add(clone, 0x14), targetBytes)
  146. mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
  147. asset := create(0, clone, 0x37)
  148. }
  149. // Call initializer
  150. WrappedAsset(asset).initialize(token_chain, token_address);
  151. // Store address
  152. wrappedAssets[seed] = asset;
  153. isWrappedAsset[asset] = true;
  154. }
  155. function lockAssets(
  156. address asset,
  157. uint256 amount,
  158. bytes32 recipient,
  159. uint8 target_chain
  160. ) public {
  161. require(amount != 0, "amount must not be 0");
  162. uint8 asset_chain = CHAIN_ID;
  163. bytes32 asset_address;
  164. if (isWrappedAsset[asset]) {
  165. WrappedAsset(asset).burn(msg.sender, amount);
  166. asset_chain = WrappedAsset(asset).assetChain();
  167. asset_address = WrappedAsset(asset).assetAddress();
  168. } else {
  169. uint256 balanceBefore = IERC20(asset).balanceOf(address(this));
  170. IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
  171. uint256 balanceAfter = IERC20(asset).balanceOf(address(this));
  172. // The amount that was transferred in is the delta between balance before and after the transfer.
  173. // This is to properly handle tokens that charge a fee on transfer.
  174. amount = balanceAfter.sub(balanceBefore);
  175. asset_address = bytes32(uint256(asset));
  176. }
  177. emit LogTokensLocked(target_chain, asset_chain, asset_address, bytes32(uint256(msg.sender)), recipient, amount);
  178. }
  179. function lockETH(
  180. bytes32 recipient,
  181. uint8 target_chain
  182. ) public payable {
  183. require(msg.value != 0, "amount must not be 0");
  184. // Wrap tx value in WETH
  185. WETH(WETHAddress).deposit{value : msg.value}();
  186. // Log deposit of WETH
  187. emit LogTokensLocked(target_chain, CHAIN_ID, bytes32(uint256(WETHAddress)), bytes32(uint256(msg.sender)), recipient, msg.value);
  188. }
  189. fallback() external payable {revert("please use lockETH to transfer ETH to Solana");}
  190. receive() external payable {revert("please use lockETH to transfer ETH to Solana");}
  191. }
  192. interface WETH is IERC20 {
  193. function deposit() external payable;
  194. function withdraw(uint256 amount) external;
  195. }