NFTBridge.sol 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. // contracts/Bridge.sol
  2. // SPDX-License-Identifier: Apache 2
  3. pragma solidity ^0.8.0;
  4. import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
  5. import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
  6. import "../libraries/external/BytesLib.sol";
  7. import "./NFTBridgeGetters.sol";
  8. import "./NFTBridgeSetters.sol";
  9. import "./NFTBridgeStructs.sol";
  10. import "./NFTBridgeGovernance.sol";
  11. import "./token/NFT.sol";
  12. import "./token/NFTImplementation.sol";
  13. contract NFTBridge is NFTBridgeGovernance {
  14. using BytesLib for bytes;
  15. // Initiate a Transfer
  16. function transferNFT(address token, uint256 tokenID, uint16 recipientChain, bytes32 recipient, uint32 nonce) public payable returns (uint64 sequence) {
  17. // determine token parameters
  18. uint16 tokenChain;
  19. bytes32 tokenAddress;
  20. if (isWrappedAsset(token)) {
  21. tokenChain = NFTImplementation(token).chainId();
  22. tokenAddress = NFTImplementation(token).nativeContract();
  23. } else {
  24. tokenChain = chainId();
  25. tokenAddress = bytes32(uint256(uint160(token)));
  26. // Verify that the correct interfaces are implemented
  27. require(ERC165(token).supportsInterface(type(IERC721).interfaceId), "must support the ERC721 interface");
  28. require(ERC165(token).supportsInterface(type(IERC721Metadata).interfaceId), "must support the ERC721-Metadata extension");
  29. }
  30. string memory symbolString;
  31. string memory nameString;
  32. string memory uriString;
  33. {
  34. if (tokenChain != 1) { // SPL tokens use cache
  35. (,bytes memory queriedSymbol) = token.staticcall(abi.encodeWithSignature("symbol()"));
  36. (,bytes memory queriedName) = token.staticcall(abi.encodeWithSignature("name()"));
  37. symbolString = abi.decode(queriedSymbol, (string));
  38. nameString = abi.decode(queriedName, (string));
  39. }
  40. (,bytes memory queriedURI) = token.staticcall(abi.encodeWithSignature("tokenURI(uint256)", tokenID));
  41. uriString = abi.decode(queriedURI, (string));
  42. }
  43. bytes32 symbol;
  44. bytes32 name;
  45. if (tokenChain == 1) {
  46. // use cached SPL token info, as the contracts uses unified values
  47. NFTBridgeStorage.SPLCache memory cache = splCache(tokenID);
  48. symbol = cache.symbol;
  49. name = cache.name;
  50. clearSplCache(tokenID);
  51. } else {
  52. assembly {
  53. // first 32 bytes hold string length
  54. // mload then loads the next word, i.e. the first 32 bytes of the strings
  55. // NOTE: this means that we might end up with an
  56. // invalid utf8 string (e.g. if we slice an emoji in half). The VAA
  57. // payload specification doesn't require that these are valid utf8
  58. // strings, and it's cheaper to do any validation off-chain for
  59. // presentation purposes
  60. symbol := mload(add(symbolString, 32))
  61. name := mload(add(nameString, 32))
  62. }
  63. }
  64. if (tokenChain == chainId()) {
  65. IERC721(token).safeTransferFrom(msg.sender, address(this), tokenID);
  66. } else {
  67. NFTImplementation(token).burn(tokenID);
  68. }
  69. sequence = logTransfer(NFTBridgeStructs.Transfer({
  70. tokenAddress : tokenAddress,
  71. tokenChain : tokenChain,
  72. name : name,
  73. symbol : symbol,
  74. tokenID : tokenID,
  75. uri : uriString,
  76. to : recipient,
  77. toChain : recipientChain
  78. }), msg.value, nonce);
  79. }
  80. function logTransfer(NFTBridgeStructs.Transfer memory transfer, uint256 callValue, uint32 nonce) internal returns (uint64 sequence) {
  81. bytes memory encoded = encodeTransfer(transfer);
  82. sequence = wormhole().publishMessage{
  83. value : callValue
  84. }(nonce, encoded, 15);
  85. }
  86. function completeTransfer(bytes memory encodedVm) public {
  87. _completeTransfer(encodedVm);
  88. }
  89. // Execute a Transfer message
  90. function _completeTransfer(bytes memory encodedVm) internal {
  91. (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
  92. require(valid, reason);
  93. require(verifyBridgeVM(vm), "invalid emitter");
  94. NFTBridgeStructs.Transfer memory transfer = parseTransfer(vm.payload);
  95. require(!isTransferCompleted(vm.hash), "transfer already completed");
  96. setTransferCompleted(vm.hash);
  97. require(transfer.toChain == chainId(), "invalid target chain");
  98. IERC721 transferToken;
  99. if (transfer.tokenChain == chainId()) {
  100. transferToken = IERC721(address(uint160(uint256(transfer.tokenAddress))));
  101. } else {
  102. address wrapped = wrappedAsset(transfer.tokenChain, transfer.tokenAddress);
  103. // If the wrapped asset does not exist yet, create it
  104. if (wrapped == address(0)) {
  105. wrapped = _createWrapped(transfer.tokenChain, transfer.tokenAddress, transfer.name, transfer.symbol);
  106. }
  107. transferToken = IERC721(wrapped);
  108. }
  109. // transfer bridged NFT to recipient
  110. address transferRecipient = address(uint160(uint256(transfer.to)));
  111. if (transfer.tokenChain != chainId()) {
  112. if (transfer.tokenChain == 1) {
  113. // Cache SPL token info which otherwise would get lost
  114. setSplCache(transfer.tokenID, NFTBridgeStorage.SPLCache({
  115. name : transfer.name,
  116. symbol : transfer.symbol
  117. }));
  118. }
  119. // mint wrapped asset
  120. NFTImplementation(address(transferToken)).mint(transferRecipient, transfer.tokenID, transfer.uri);
  121. } else {
  122. transferToken.safeTransferFrom(address(this), transferRecipient, transfer.tokenID);
  123. }
  124. }
  125. // Creates a wrapped asset using AssetMeta
  126. function _createWrapped(uint16 tokenChain, bytes32 tokenAddress, bytes32 name, bytes32 symbol) internal returns (address token) {
  127. require(tokenChain != chainId(), "can only wrap tokens from foreign chains");
  128. require(wrappedAsset(tokenChain, tokenAddress) == address(0), "wrapped asset already exists");
  129. // SPL NFTs all use the same NFT contract, so unify the name
  130. if (tokenChain == 1) {
  131. // "Wormhole Bridged Solana-NFT" - right-padded
  132. name = 0x576f726d686f6c65204272696467656420536f6c616e612d4e46540000000000;
  133. // "WORMSPLNFT" - right-padded
  134. symbol = 0x574f524d53504c4e465400000000000000000000000000000000000000000000;
  135. }
  136. // initialize the NFTImplementation
  137. bytes memory initialisationArgs = abi.encodeWithSelector(
  138. NFTImplementation.initialize.selector,
  139. bytes32ToString(name),
  140. bytes32ToString(symbol),
  141. address(this),
  142. tokenChain,
  143. tokenAddress
  144. );
  145. // initialize the BeaconProxy
  146. bytes memory constructorArgs = abi.encode(address(this), initialisationArgs);
  147. // deployment code
  148. bytes memory bytecode = abi.encodePacked(type(BridgeNFT).creationCode, constructorArgs);
  149. bytes32 salt = keccak256(abi.encodePacked(tokenChain, tokenAddress));
  150. assembly {
  151. token := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
  152. if iszero(extcodesize(token)) {
  153. revert(0, 0)
  154. }
  155. }
  156. setWrappedAsset(tokenChain, tokenAddress, token);
  157. }
  158. function verifyBridgeVM(IWormhole.VM memory vm) internal view returns (bool){
  159. if (bridgeContracts(vm.emitterChainId) == vm.emitterAddress) {
  160. return true;
  161. }
  162. return false;
  163. }
  164. function encodeTransfer(NFTBridgeStructs.Transfer memory transfer) public pure returns (bytes memory encoded) {
  165. // There is a global limit on 200 bytes of tokenURI in Wormhole due to Solana
  166. require(bytes(transfer.uri).length <= 200, "tokenURI must not exceed 200 bytes");
  167. encoded = abi.encodePacked(
  168. uint8(1),
  169. transfer.tokenAddress,
  170. transfer.tokenChain,
  171. transfer.symbol,
  172. transfer.name,
  173. transfer.tokenID,
  174. uint8(bytes(transfer.uri).length),
  175. transfer.uri,
  176. transfer.to,
  177. transfer.toChain
  178. );
  179. }
  180. function parseTransfer(bytes memory encoded) public pure returns (NFTBridgeStructs.Transfer memory transfer) {
  181. uint index = 0;
  182. uint8 payloadID = encoded.toUint8(index);
  183. index += 1;
  184. require(payloadID == 1, "invalid Transfer");
  185. transfer.tokenAddress = encoded.toBytes32(index);
  186. index += 32;
  187. transfer.tokenChain = encoded.toUint16(index);
  188. index += 2;
  189. transfer.symbol = encoded.toBytes32(index);
  190. index += 32;
  191. transfer.name = encoded.toBytes32(index);
  192. index += 32;
  193. transfer.tokenID = encoded.toUint256(index);
  194. index += 32;
  195. // Ignore length due to malformatted payload
  196. index += 1;
  197. transfer.uri = string(encoded.slice(index, encoded.length - index - 34));
  198. // From here we read backwards due malformatted package
  199. index = encoded.length;
  200. index -= 2;
  201. transfer.toChain = encoded.toUint16(index);
  202. index -= 32;
  203. transfer.to = encoded.toBytes32(index);
  204. //require(encoded.length == index, "invalid Transfer");
  205. }
  206. function onERC721Received(
  207. address operator,
  208. address,
  209. uint256,
  210. bytes calldata
  211. ) external view returns (bytes4){
  212. require(operator == address(this), "can only bridge tokens via transferNFT method");
  213. return type(IERC721Receiver).interfaceId;
  214. }
  215. function bytes32ToString(bytes32 input) internal pure returns (string memory) {
  216. uint256 i;
  217. while (i < 32 && input[i] != 0) {
  218. i++;
  219. }
  220. bytes memory array = new bytes(i);
  221. for (uint c = 0; c < i; c++) {
  222. array[c] = input[c];
  223. }
  224. return string(array);
  225. }
  226. }