nft: add special case for spl naming, update tests
Change-Id: Ifbe9eeaad5d5604d52d75fdac6b018e94afa9d75
This commit is contained in:
parent
af4fdbf1a1
commit
46440b3bf9
|
@ -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,
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue