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,22 +39,32 @@ contract NFTBridge is NFTBridgeGovernance {
string memory nameString; string memory nameString;
string memory uriString; string memory uriString;
{ {
if (tokenChain != 1) { // SPL tokens use cache
(,bytes memory queriedSymbol) = token.staticcall(abi.encodeWithSignature("symbol()")); (,bytes memory queriedSymbol) = token.staticcall(abi.encodeWithSignature("symbol()"));
(,bytes memory queriedName) = token.staticcall(abi.encodeWithSignature("name()")); (,bytes memory queriedName) = token.staticcall(abi.encodeWithSignature("name()"));
(,bytes memory queriedURI) = token.staticcall(abi.encodeWithSignature("tokenURI(uint256)", tokenID));
symbolString = abi.decode(queriedSymbol, (string)); symbolString = abi.decode(queriedSymbol, (string));
nameString = abi.decode(queriedName, (string)); nameString = abi.decode(queriedName, (string));
}
(,bytes memory queriedURI) = token.staticcall(abi.encodeWithSignature("tokenURI(uint256)", tokenID));
uriString = abi.decode(queriedURI, (string)); uriString = abi.decode(queriedURI, (string));
} }
bytes32 symbol; bytes32 symbol;
bytes32 name; bytes32 name;
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 { assembly {
// first 32 bytes hold string length // first 32 bytes hold string length
symbol := mload(add(symbolString, 32)) symbol := mload(add(symbolString, 32))
name := mload(add(nameString, 32)) name := mload(add(nameString, 32))
} }
}
if (tokenChain == chainId()) { if (tokenChain == chainId()) {
IERC721(token).safeTransferFrom(msg.sender, address(this), tokenID); IERC721(token).safeTransferFrom(msg.sender, address(this), tokenID);
@ -118,6 +128,14 @@ contract NFTBridge is NFTBridgeGovernance {
address transferRecipient = address(uint160(uint256(transfer.to))); address transferRecipient = address(uint160(uint256(transfer.to)));
if (transfer.tokenChain != chainId()) { 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 // mint wrapped asset
NFTImplementation(address(transferToken)).mint(transferRecipient, transfer.tokenID, transfer.uri); NFTImplementation(address(transferToken)).mint(transferRecipient, transfer.tokenID, transfer.uri);
} else { } else {
@ -130,6 +148,14 @@ contract NFTBridge is NFTBridgeGovernance {
require(tokenChain != chainId(), "can only wrap tokens from foreign chains"); require(tokenChain != chainId(), "can only wrap tokens from foreign chains");
require(wrappedAsset(tokenChain, tokenAddress) == address(0), "wrapped asset already exists"); 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 // initialize the NFTImplementation
bytes memory initialisationArgs = abi.encodeWithSelector( bytes memory initialisationArgs = abi.encodeWithSelector(
NFTImplementation.initialize.selector, NFTImplementation.initialize.selector,

View File

@ -53,4 +53,8 @@ contract NFTBridgeGetters is NFTBridgeState {
function isWrappedAsset(address token) public view returns (bool){ function isWrappedAsset(address token) public view returns (bool){
return _state.isWrappedAsset[token]; 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.wrappedAssets[tokenChainId][tokenAddress] = wrapper;
_state.isWrappedAsset[wrapper] = true; _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; bytes32 assetAddress;
} }
struct SPLCache {
bytes32 name;
bytes32 symbol;
}
struct State { struct State {
address payable wormhole; address payable wormhole;
address tokenImplementation; address tokenImplementation;
@ -40,6 +45,9 @@ contract NFTBridgeStorage {
// Mapping of bridge contracts on other chains // Mapping of bridge contracts on other chains
mapping(uint16 => bytes32) bridgeImplementations; 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"; let WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
const testForeignChainId = "1"; const testForeignChainId = "1";
const testForeignBridgeContract = "0x000000000000000000000000000000000000000000000000000000000000ffff"; const testForeignBridgeContract = "0x000000000000000000000000000000000000000000000000000000000000ffff";
const testBridgedAssetChain = "0001"; const testBridgedAssetChain = "0003";
const testBridgedAssetAddress = "000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e"; const testBridgedAssetAddress = "000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e";
@ -406,7 +406,7 @@ contract("NFT", function () {
assert.equal(name, "Foreign Chain NFT"); assert.equal(name, "Foreign Chain NFT");
const chainId = await wrappedAsset.methods.chainId().call(); const chainId = await wrappedAsset.methods.chainId().call();
assert.equal(chainId, 1); assert.equal(chainId, Number(testBridgedAssetChain));
const nativeContract = await wrappedAsset.methods.nativeContract().call(); const nativeContract = await wrappedAsset.methods.nativeContract().call();
assert.equal(nativeContract, "0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e"); assert.equal(nativeContract, "0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e");
@ -458,6 +458,99 @@ contract("NFT", function () {
assert.equal(ownerAfter, accounts[0]); 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 () { it("should burn bridged assets wrappers on transfer to another chain", async function () {
const accounts = await web3.eth.getAccounts(); const accounts = await web3.eth.getAccounts();