Bridge.sol 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. // contracts/Bridge.sol
  2. // SPDX-License-Identifier: Apache 2
  3. pragma solidity ^0.8.0;
  4. import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
  5. import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
  6. import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
  7. import "../libraries/external/BytesLib.sol";
  8. import "./BridgeGetters.sol";
  9. import "./BridgeSetters.sol";
  10. import "./BridgeStructs.sol";
  11. import "./BridgeGovernance.sol";
  12. import "./token/Token.sol";
  13. import "./token/TokenImplementation.sol";
  14. contract Bridge is BridgeGovernance, ReentrancyGuard {
  15. using BytesLib for bytes;
  16. // Produce a AssetMeta message for a given token
  17. function attestToken(address tokenAddress, uint32 nonce) public payable returns (uint64 sequence){
  18. // decimals, symbol & token are not part of the core ERC20 token standard, so we need to support contracts that dont implement them
  19. (,bytes memory queriedDecimals) = tokenAddress.staticcall(abi.encodeWithSignature("decimals()"));
  20. (,bytes memory queriedSymbol) = tokenAddress.staticcall(abi.encodeWithSignature("symbol()"));
  21. (,bytes memory queriedName) = tokenAddress.staticcall(abi.encodeWithSignature("name()"));
  22. uint8 decimals = abi.decode(queriedDecimals, (uint8));
  23. string memory symbolString = abi.decode(queriedSymbol, (string));
  24. string memory nameString = abi.decode(queriedName, (string));
  25. bytes32 symbol;
  26. bytes32 name;
  27. assembly {
  28. // first 32 bytes hold string length
  29. symbol := mload(add(symbolString, 32))
  30. name := mload(add(nameString, 32))
  31. }
  32. BridgeStructs.AssetMeta memory meta = BridgeStructs.AssetMeta({
  33. payloadID : 2,
  34. // Address of the token. Left-zero-padded if shorter than 32 bytes
  35. tokenAddress : bytes32(uint256(uint160(tokenAddress))),
  36. // Chain ID of the token
  37. tokenChain : chainId(),
  38. // Number of decimals of the token (big-endian uint8)
  39. decimals : decimals,
  40. // Symbol of the token (UTF-8)
  41. symbol : symbol,
  42. // Name of the token (UTF-8)
  43. name : name
  44. });
  45. bytes memory encoded = encodeAssetMeta(meta);
  46. sequence = wormhole().publishMessage{
  47. value : msg.value
  48. }(nonce, encoded, 15);
  49. }
  50. function wrapAndTransferETH(uint16 recipientChain, bytes32 recipient, uint256 arbiterFee, uint32 nonce) public payable returns (uint64 sequence) {
  51. uint wormholeFee = wormhole().messageFee();
  52. require(wormholeFee < msg.value, "value is smaller than wormhole fee");
  53. uint amount = msg.value - wormholeFee;
  54. require(arbiterFee <= amount, "fee is bigger than amount minus wormhole fee");
  55. uint normalizedAmount = normalizeAmount(amount, 18);
  56. uint normalizedArbiterFee = normalizeAmount(arbiterFee, 18);
  57. // refund dust
  58. uint dust = amount - deNormalizeAmount(normalizedAmount, 18);
  59. if (dust > 0) {
  60. payable(msg.sender).transfer(dust);
  61. }
  62. // deposit into WETH
  63. WETH().deposit{
  64. value : amount - dust
  65. }();
  66. // track and check outstanding token amounts
  67. bridgeOut(address(WETH()), normalizedAmount);
  68. sequence = logTransfer(chainId(), bytes32(uint256(uint160(address(WETH())))), normalizedAmount, recipientChain, recipient, normalizedArbiterFee, wormholeFee, nonce);
  69. }
  70. // Initiate a Transfer
  71. function transferTokens(address token, uint256 amount, uint16 recipientChain, bytes32 recipient, uint256 arbiterFee, uint32 nonce) public payable nonReentrant returns (uint64 sequence) {
  72. // determine token parameters
  73. uint16 tokenChain;
  74. bytes32 tokenAddress;
  75. if (isWrappedAsset(token)) {
  76. tokenChain = TokenImplementation(token).chainId();
  77. tokenAddress = TokenImplementation(token).nativeContract();
  78. } else {
  79. tokenChain = chainId();
  80. tokenAddress = bytes32(uint256(uint160(token)));
  81. }
  82. // query tokens decimals
  83. (,bytes memory queriedDecimals) = token.staticcall(abi.encodeWithSignature("decimals()"));
  84. uint8 decimals = abi.decode(queriedDecimals, (uint8));
  85. // don't deposit dust that can not be bridged due to the decimal shift
  86. amount = deNormalizeAmount(normalizeAmount(amount, decimals), decimals);
  87. if (tokenChain == chainId()) {
  88. // query own token balance before transfer
  89. (,bytes memory queriedBalanceBefore) = token.staticcall(abi.encodeWithSelector(IERC20.balanceOf.selector, address(this)));
  90. uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));
  91. // transfer tokens
  92. SafeERC20.safeTransferFrom(IERC20(token), msg.sender, address(this), amount);
  93. // query own token balance after transfer
  94. (,bytes memory queriedBalanceAfter) = token.staticcall(abi.encodeWithSelector(IERC20.balanceOf.selector, address(this)));
  95. uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256));
  96. // correct amount for potential transfer fees
  97. amount = balanceAfter - balanceBefore;
  98. } else {
  99. SafeERC20.safeTransferFrom(IERC20(token), msg.sender, address(this), amount);
  100. TokenImplementation(token).burn(address(this), amount);
  101. }
  102. // normalize amounts decimals
  103. uint256 normalizedAmount = normalizeAmount(amount, decimals);
  104. uint256 normalizedArbiterFee = normalizeAmount(arbiterFee, decimals);
  105. // track and check outstanding token amounts
  106. if (tokenChain == chainId()) {
  107. bridgeOut(token, normalizedAmount);
  108. }
  109. sequence = logTransfer(tokenChain, tokenAddress, normalizedAmount, recipientChain, recipient, normalizedArbiterFee, msg.value, nonce);
  110. }
  111. function normalizeAmount(uint256 amount, uint8 decimals) internal pure returns(uint256){
  112. if (decimals > 8) {
  113. amount /= 10 ** (decimals - 8);
  114. }
  115. return amount;
  116. }
  117. function deNormalizeAmount(uint256 amount, uint8 decimals) internal pure returns(uint256){
  118. if (decimals > 8) {
  119. amount *= 10 ** (decimals - 8);
  120. }
  121. return amount;
  122. }
  123. function logTransfer(uint16 tokenChain, bytes32 tokenAddress, uint256 amount, uint16 recipientChain, bytes32 recipient, uint256 fee, uint256 callValue, uint32 nonce) internal returns (uint64 sequence) {
  124. require(fee <= amount, "fee exceeds amount");
  125. BridgeStructs.Transfer memory transfer = BridgeStructs.Transfer({
  126. payloadID : 1,
  127. amount : amount,
  128. tokenAddress : tokenAddress,
  129. tokenChain : tokenChain,
  130. to : recipient,
  131. toChain : recipientChain,
  132. fee : fee
  133. });
  134. bytes memory encoded = encodeTransfer(transfer);
  135. sequence = wormhole().publishMessage{
  136. value : callValue
  137. }(nonce, encoded, 15);
  138. }
  139. function updateWrapped(bytes memory encodedVm) external returns (address token) {
  140. (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
  141. require(valid, reason);
  142. require(verifyBridgeVM(vm), "invalid emitter");
  143. BridgeStructs.AssetMeta memory meta = parseAssetMeta(vm.payload);
  144. return _updateWrapped(meta, vm.sequence);
  145. }
  146. function _updateWrapped(BridgeStructs.AssetMeta memory meta, uint64 sequence) internal returns (address token) {
  147. address wrapped = wrappedAsset(meta.tokenChain, meta.tokenAddress);
  148. require(wrapped != address(0), "wrapped asset does not exists");
  149. // Update metadata
  150. TokenImplementation(wrapped).updateDetails(bytes32ToString(meta.name), bytes32ToString(meta.symbol), sequence);
  151. return wrapped;
  152. }
  153. function createWrapped(bytes memory encodedVm) external returns (address token) {
  154. (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
  155. require(valid, reason);
  156. require(verifyBridgeVM(vm), "invalid emitter");
  157. BridgeStructs.AssetMeta memory meta = parseAssetMeta(vm.payload);
  158. return _createWrapped(meta, vm.sequence);
  159. }
  160. // Creates a wrapped asset using AssetMeta
  161. function _createWrapped(BridgeStructs.AssetMeta memory meta, uint64 sequence) internal returns (address token) {
  162. require(meta.tokenChain != chainId(), "can only wrap tokens from foreign chains");
  163. require(wrappedAsset(meta.tokenChain, meta.tokenAddress) == address(0), "wrapped asset already exists");
  164. // initialize the TokenImplementation
  165. bytes memory initialisationArgs = abi.encodeWithSelector(
  166. TokenImplementation.initialize.selector,
  167. bytes32ToString(meta.name),
  168. bytes32ToString(meta.symbol),
  169. meta.decimals,
  170. sequence,
  171. address(this),
  172. meta.tokenChain,
  173. meta.tokenAddress
  174. );
  175. // initialize the BeaconProxy
  176. bytes memory constructorArgs = abi.encode(address(this), initialisationArgs);
  177. // deployment code
  178. bytes memory bytecode = abi.encodePacked(type(BridgeToken).creationCode, constructorArgs);
  179. bytes32 salt = keccak256(abi.encodePacked(meta.tokenChain, meta.tokenAddress));
  180. assembly {
  181. token := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
  182. if iszero(extcodesize(token)) {
  183. revert(0, 0)
  184. }
  185. }
  186. setWrappedAsset(meta.tokenChain, meta.tokenAddress, token);
  187. }
  188. function completeTransfer(bytes memory encodedVm) public {
  189. _completeTransfer(encodedVm, false);
  190. }
  191. function completeTransferAndUnwrapETH(bytes memory encodedVm) public {
  192. _completeTransfer(encodedVm, true);
  193. }
  194. // Execute a Transfer message
  195. function _completeTransfer(bytes memory encodedVm, bool unwrapWETH) internal {
  196. (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
  197. require(valid, reason);
  198. require(verifyBridgeVM(vm), "invalid emitter");
  199. BridgeStructs.Transfer memory transfer = parseTransfer(vm.payload);
  200. require(!isTransferCompleted(vm.hash), "transfer already completed");
  201. setTransferCompleted(vm.hash);
  202. require(transfer.toChain == chainId(), "invalid target chain");
  203. IERC20 transferToken;
  204. if (transfer.tokenChain == chainId()) {
  205. transferToken = IERC20(address(uint160(uint256(transfer.tokenAddress))));
  206. // track outstanding token amounts
  207. bridgedIn(address(transferToken), transfer.amount);
  208. } else {
  209. address wrapped = wrappedAsset(transfer.tokenChain, transfer.tokenAddress);
  210. require(wrapped != address(0), "no wrapper for this token created yet");
  211. transferToken = IERC20(wrapped);
  212. }
  213. require(unwrapWETH == false || address(transferToken) == address(WETH()), "invalid token, can only unwrap WETH");
  214. // query decimals
  215. (,bytes memory queriedDecimals) = address(transferToken).staticcall(abi.encodeWithSignature("decimals()"));
  216. uint8 decimals = abi.decode(queriedDecimals, (uint8));
  217. // adjust decimals
  218. uint256 nativeAmount = deNormalizeAmount(transfer.amount, decimals);
  219. uint256 nativeFee = deNormalizeAmount(transfer.fee, decimals);
  220. // transfer fee to arbiter
  221. if (nativeFee > 0) {
  222. require(nativeFee <= nativeAmount, "fee higher than transferred amount");
  223. if (unwrapWETH) {
  224. WETH().withdraw(nativeFee);
  225. payable(msg.sender).transfer(nativeFee);
  226. } else {
  227. if (transfer.tokenChain != chainId()) {
  228. // mint wrapped asset
  229. TokenImplementation(address(transferToken)).mint(msg.sender, nativeFee);
  230. } else {
  231. SafeERC20.safeTransfer(transferToken, msg.sender, nativeFee);
  232. }
  233. }
  234. }
  235. // transfer bridged amount to recipient
  236. uint transferAmount = nativeAmount - nativeFee;
  237. address transferRecipient = address(uint160(uint256(transfer.to)));
  238. if (unwrapWETH) {
  239. WETH().withdraw(transferAmount);
  240. payable(transferRecipient).transfer(transferAmount);
  241. } else {
  242. if (transfer.tokenChain != chainId()) {
  243. // mint wrapped asset
  244. TokenImplementation(address(transferToken)).mint(transferRecipient, transferAmount);
  245. } else {
  246. SafeERC20.safeTransfer(transferToken, transferRecipient, transferAmount);
  247. }
  248. }
  249. }
  250. function bridgeOut(address token, uint normalizedAmount) internal {
  251. uint outstanding = outstandingBridged(token);
  252. require(outstanding + normalizedAmount <= type(uint64).max, "transfer exceeds max outstanding bridged token amount");
  253. setOutstandingBridged(token, outstanding + normalizedAmount);
  254. }
  255. function bridgedIn(address token, uint normalizedAmount) internal {
  256. setOutstandingBridged(token, outstandingBridged(token) - normalizedAmount);
  257. }
  258. function verifyBridgeVM(IWormhole.VM memory vm) internal view returns (bool){
  259. if (bridgeContracts(vm.emitterChainId) == vm.emitterAddress) {
  260. return true;
  261. }
  262. return false;
  263. }
  264. function encodeAssetMeta(BridgeStructs.AssetMeta memory meta) public pure returns (bytes memory encoded) {
  265. encoded = abi.encodePacked(
  266. meta.payloadID,
  267. meta.tokenAddress,
  268. meta.tokenChain,
  269. meta.decimals,
  270. meta.symbol,
  271. meta.name
  272. );
  273. }
  274. function encodeTransfer(BridgeStructs.Transfer memory transfer) public pure returns (bytes memory encoded) {
  275. encoded = abi.encodePacked(
  276. transfer.payloadID,
  277. transfer.amount,
  278. transfer.tokenAddress,
  279. transfer.tokenChain,
  280. transfer.to,
  281. transfer.toChain,
  282. transfer.fee
  283. );
  284. }
  285. function parseAssetMeta(bytes memory encoded) public pure returns (BridgeStructs.AssetMeta memory meta) {
  286. uint index = 0;
  287. meta.payloadID = encoded.toUint8(index);
  288. index += 1;
  289. require(meta.payloadID == 2, "invalid AssetMeta");
  290. meta.tokenAddress = encoded.toBytes32(index);
  291. index += 32;
  292. meta.tokenChain = encoded.toUint16(index);
  293. index += 2;
  294. meta.decimals = encoded.toUint8(index);
  295. index += 1;
  296. meta.symbol = encoded.toBytes32(index);
  297. index += 32;
  298. meta.name = encoded.toBytes32(index);
  299. index += 32;
  300. require(encoded.length == index, "invalid AssetMeta");
  301. }
  302. function parseTransfer(bytes memory encoded) public pure returns (BridgeStructs.Transfer memory transfer) {
  303. uint index = 0;
  304. transfer.payloadID = encoded.toUint8(index);
  305. index += 1;
  306. require(transfer.payloadID == 1, "invalid Transfer");
  307. transfer.amount = encoded.toUint256(index);
  308. index += 32;
  309. transfer.tokenAddress = encoded.toBytes32(index);
  310. index += 32;
  311. transfer.tokenChain = encoded.toUint16(index);
  312. index += 2;
  313. transfer.to = encoded.toBytes32(index);
  314. index += 32;
  315. transfer.toChain = encoded.toUint16(index);
  316. index += 2;
  317. transfer.fee = encoded.toUint256(index);
  318. index += 32;
  319. require(encoded.length == index, "invalid Transfer");
  320. }
  321. function bytes32ToString(bytes32 input) internal pure returns (string memory) {
  322. uint256 i;
  323. while (i < 32 && input[i] != 0) {
  324. i++;
  325. }
  326. bytes memory array = new bytes(i);
  327. for (uint c = 0; c < i; c++) {
  328. array[c] = input[c];
  329. }
  330. return string(array);
  331. }
  332. // we need to accept ETH sends to unwrap WETH
  333. receive() external payable {}
  334. }