nft: add special case for spl naming, update tests

Change-Id: Ifbe9eeaad5d5604d52d75fdac6b018e94afa9d75
This commit is contained in:
valentin 2021-09-20 12:25:09 +02:00 committed by Hendrik Hofstadt
parent af4fdbf1a1
commit 46440b3bf9
5 changed files with 149 additions and 10 deletions

View File

@ -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,

View File

@ -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];
}
}

View File

@ -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];
}
}

View File

@ -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;
}
}

View File

@ -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();