Prechádzať zdrojové kódy

nft: add special case for spl naming, update tests

Change-Id: Ifbe9eeaad5d5604d52d75fdac6b018e94afa9d75
valentin 4 rokov pred
rodič
commit
46440b3bf9

+ 34 - 8
ethereum/contracts/nft/NFTBridge.sol

@@ -39,21 +39,31 @@ contract NFTBridge is NFTBridgeGovernance {
         string memory nameString;
         string memory uriString;
         {
-            (,bytes memory queriedSymbol) = token.staticcall(abi.encodeWithSignature("symbol()"));
-            (,bytes memory queriedName) = token.staticcall(abi.encodeWithSignature("name()"));
-            (,bytes memory queriedURI) = token.staticcall(abi.encodeWithSignature("tokenURI(uint256)", tokenID));
+            if (tokenChain != 1) { // SPL tokens use cache
+                (,bytes memory queriedSymbol) = token.staticcall(abi.encodeWithSignature("symbol()"));
+                (,bytes memory queriedName) = token.staticcall(abi.encodeWithSignature("name()"));
+                symbolString = abi.decode(queriedSymbol, (string));
+                nameString = abi.decode(queriedName, (string));
+            }
 
-            symbolString = abi.decode(queriedSymbol, (string));
-            nameString = abi.decode(queriedName, (string));
+            (,bytes memory queriedURI) = token.staticcall(abi.encodeWithSignature("tokenURI(uint256)", tokenID));
             uriString = abi.decode(queriedURI, (string));
         }
 
         bytes32 symbol;
         bytes32 name;
-        assembly {
+        if (tokenChain == 1) {
+            // use cached SPL token info, as the contracts uses unified values
+            NFTBridgeStorage.SPLCache memory cache = splCache(tokenID);
+            symbol = cache.symbol;
+            name = cache.name;
+            clearSplCache(tokenID);
+        } else {
+            assembly {
             // first 32 bytes hold string length
-            symbol := mload(add(symbolString, 32))
-            name := mload(add(nameString, 32))
+                symbol := mload(add(symbolString, 32))
+                name := mload(add(nameString, 32))
+            }
         }
 
         if (tokenChain == chainId()) {
@@ -118,6 +128,14 @@ contract NFTBridge is NFTBridgeGovernance {
         address transferRecipient = address(uint160(uint256(transfer.to)));
 
         if (transfer.tokenChain != chainId()) {
+            if (transfer.tokenChain == 1) {
+                // Cache SPL token info which otherwise would get lost
+                setSplCache(transfer.tokenID, NFTBridgeStorage.SPLCache({
+                    name : transfer.name,
+                    symbol : transfer.symbol
+                }));
+            }
+
             // mint wrapped asset
             NFTImplementation(address(transferToken)).mint(transferRecipient, transfer.tokenID, transfer.uri);
         } else {
@@ -130,6 +148,14 @@ contract NFTBridge is NFTBridgeGovernance {
         require(tokenChain != chainId(), "can only wrap tokens from foreign chains");
         require(wrappedAsset(tokenChain, tokenAddress) == address(0), "wrapped asset already exists");
 
+        // SPL NFTs all use the same NFT contract, so unify the name
+        if (tokenChain == 1) {
+            // "Wormhole Bridged Solana-NFT" - right-padded
+            name =   0x576f726d686f6c65204272696467656420536f6c616e612d4e46540000000000;
+            // "WORMSPLNFT" - right-padded
+            symbol = 0x574f524d53504c4e465400000000000000000000000000000000000000000000;
+        }
+
         // initialize the NFTImplementation
         bytes memory initialisationArgs = abi.encodeWithSelector(
             NFTImplementation.initialize.selector,

+ 4 - 0
ethereum/contracts/nft/NFTBridgeGetters.sol

@@ -53,4 +53,8 @@ contract NFTBridgeGetters is NFTBridgeState {
     function isWrappedAsset(address token) public view returns (bool){
         return _state.isWrappedAsset[token];
     }
+
+    function splCache(uint256 tokenId) public view returns (NFTBridgeStorage.SPLCache memory) {
+        return _state.splCache[tokenId];
+    }
 }

+ 8 - 0
ethereum/contracts/nft/NFTBridgeSetters.sol

@@ -46,4 +46,12 @@ contract NFTBridgeSetters is NFTBridgeState {
         _state.wrappedAssets[tokenChainId][tokenAddress] = wrapper;
         _state.isWrappedAsset[wrapper] = true;
     }
+
+    function setSplCache(uint256 tokenId, NFTBridgeStorage.SPLCache memory cache) internal {
+        _state.splCache[tokenId] = cache;
+    }
+
+    function clearSplCache(uint256 tokenId) internal {
+        delete _state.splCache[tokenId];
+    }
 }

+ 8 - 0
ethereum/contracts/nft/NFTBridgeState.sol

@@ -17,6 +17,11 @@ contract NFTBridgeStorage {
         bytes32 assetAddress;
     }
 
+    struct SPLCache {
+        bytes32 name;
+        bytes32 symbol;
+    }
+
     struct State {
         address payable wormhole;
         address tokenImplementation;
@@ -40,6 +45,9 @@ contract NFTBridgeStorage {
 
         // Mapping of bridge contracts on other chains
         mapping(uint16 => bytes32) bridgeImplementations;
+
+        // Mapping of spl token info caches (chainID => nativeAddress => SPLCache)
+        mapping(uint256 => SPLCache) splCache;
     }
 }
 

+ 95 - 2
ethereum/test/nft.js

@@ -24,7 +24,7 @@ contract("NFT", function () {
     let WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
     const testForeignChainId = "1";
     const testForeignBridgeContract = "0x000000000000000000000000000000000000000000000000000000000000ffff";
-    const testBridgedAssetChain = "0001";
+    const testBridgedAssetChain = "0003";
     const testBridgedAssetAddress = "000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e";
 
 
@@ -406,7 +406,7 @@ contract("NFT", function () {
         assert.equal(name, "Foreign Chain NFT");
 
         const chainId = await wrappedAsset.methods.chainId().call();
-        assert.equal(chainId, 1);
+        assert.equal(chainId, Number(testBridgedAssetChain));
 
         const nativeContract = await wrappedAsset.methods.nativeContract().call();
         assert.equal(nativeContract, "0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e");
@@ -458,6 +458,99 @@ contract("NFT", function () {
         assert.equal(ownerAfter, accounts[0]);
     })
 
+    it("should mint bridged assets from solana under unified name, caching the original", async function () {
+        const accounts = await web3.eth.getAccounts();
+        let tokenId = "1000000000000000001";
+
+        const initialized = new web3.eth.Contract(BridgeImplementationFullABI, NFTBridge.address);
+
+        // we are using the asset where we created a wrapper in the previous test
+        let data = "0x" +
+            "01" +
+            // tokenaddress
+            testBridgedAssetAddress +
+            // tokenchain
+            "0001" +
+            // symbol
+            "464f520000000000000000000000000000000000000000000000000000000000" +
+            // name
+            "466f726569676e20436861696e204e4654000000000000000000000000000000" +
+            // tokenID
+            web3.eth.abi.encodeParameter("uint256", new BigNumber(tokenId).toString()).substring(2) +
+            // url length
+            "00" +
+            // no URL
+            "" +
+            // receiver
+            web3.eth.abi.encodeParameter("address", accounts[0]).substr(2) +
+            // receiving chain
+            web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4));
+
+        let vm = await signAndEncodeVM(
+            0,
+            0,
+            testForeignChainId,
+            testForeignBridgeContract,
+            0,
+            data,
+            [
+                testSigner1PK
+            ],
+            0,
+            0
+        );
+
+        await initialized.methods.completeTransfer("0x" + vm).send({
+            value: 0,
+            from: accounts[1],
+            gasLimit: 2000000
+        });
+
+        const cache = await initialized.methods.splCache(tokenId).call()
+        assert.equal(cache.symbol, "0x464f520000000000000000000000000000000000000000000000000000000000");
+        assert.equal(cache.name, "0x466f726569676e20436861696e204e4654000000000000000000000000000000");
+
+        const wrappedAddress = await initialized.methods.wrappedAsset("0x0001", "0x" + testBridgedAssetAddress).call();
+        const wrappedAsset = new web3.eth.Contract(NFTImplementation.abi, wrappedAddress);
+
+        const symbol = await wrappedAsset.methods.symbol().call();
+        assert.equal(symbol, "WORMSPLNFT");
+
+        const name = await wrappedAsset.methods.name().call();
+        assert.equal(name, "Wormhole Bridged Solana-NFT");
+    })
+
+    it("cached SPL names are loaded when transferring out, cache is cleared", async function () {
+        const accounts = await web3.eth.getAccounts();
+        let tokenId = "1000000000000000001";
+
+        const initialized = new web3.eth.Contract(BridgeImplementationFullABI, NFTBridge.address);
+
+        const wrappedAddress = await initialized.methods.wrappedAsset("0x0001", "0x" + testBridgedAssetAddress).call();
+
+        const transfer =         await initialized.methods.transferNFT(
+            wrappedAddress,
+            tokenId,
+            "10",
+            "0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e",
+            "2345"
+        ).send({
+            value: 0,
+            from: accounts[0],
+            gasLimit: 2000000
+        });
+
+        // symbol
+        assert.ok(transfer.events[2].raw.data.includes('464f520000000000000000000000000000000000000000000000000000000000'))
+        // name
+        assert.ok(transfer.events[2].raw.data.includes('466f726569676e20436861696e204e4654000000000000000000000000000000'))
+
+        // check if cache is cleared
+        const cache = await initialized.methods.splCache(tokenId).call()
+        assert.equal(cache.symbol, "0x0000000000000000000000000000000000000000000000000000000000000000");
+        assert.equal(cache.name, "0x0000000000000000000000000000000000000000000000000000000000000000");
+    })
+
     it("should burn bridged assets wrappers on transfer to another chain", async function () {
 
         const accounts = await web3.eth.getAccounts();