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 nameString;
|
||||||
string memory uriString;
|
string memory uriString;
|
||||||
{
|
{
|
||||||
(,bytes memory queriedSymbol) = token.staticcall(abi.encodeWithSignature("symbol()"));
|
if (tokenChain != 1) { // SPL tokens use cache
|
||||||
(,bytes memory queriedName) = token.staticcall(abi.encodeWithSignature("name()"));
|
(,bytes memory queriedSymbol) = token.staticcall(abi.encodeWithSignature("symbol()"));
|
||||||
(,bytes memory queriedURI) = token.staticcall(abi.encodeWithSignature("tokenURI(uint256)", tokenID));
|
(,bytes memory queriedName) = token.staticcall(abi.encodeWithSignature("name()"));
|
||||||
|
symbolString = abi.decode(queriedSymbol, (string));
|
||||||
|
nameString = abi.decode(queriedName, (string));
|
||||||
|
}
|
||||||
|
|
||||||
symbolString = abi.decode(queriedSymbol, (string));
|
(,bytes memory queriedURI) = token.staticcall(abi.encodeWithSignature("tokenURI(uint256)", tokenID));
|
||||||
nameString = abi.decode(queriedName, (string));
|
|
||||||
uriString = abi.decode(queriedURI, (string));
|
uriString = abi.decode(queriedURI, (string));
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes32 symbol;
|
bytes32 symbol;
|
||||||
bytes32 name;
|
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
|
// 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()) {
|
||||||
|
@ -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,
|
||||||
|
|
|
@ -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];
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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];
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue