diff --git a/contracts/nft/TributeERC721.sol b/contracts/nft/TributeERC721.sol index de450197c..777b87ce2 100644 --- a/contracts/nft/TributeERC721.sol +++ b/contracts/nft/TributeERC721.sol @@ -26,6 +26,10 @@ contract TributeERC721 is DaoRegistry public daoRegistry; + bytes32 constant TokenName = keccak256("dao-collection.TokenName"); + bytes32 constant TokenSymbol = keccak256("dao-collection.TokenSymbol"); + bytes32 constant TokenMediaPt1 = keccak256("dao-collection.TokenMediaPt1"); + bytes32 constant TokenMediaPt2 = keccak256("dao-collection.TokenMediaPt2"); bytes32 constant Transferable = keccak256("dao-collection.Transferable"); bytes32 constant CollectionSize = keccak256("dao-collection.CollectionSize"); @@ -48,17 +52,12 @@ contract TributeERC721 is } // https://docs.openzeppelin.com/contracts/4.x/api/proxy#Initializable - function initialize( - string memory name, - string memory symbol, - address daoAddress, - string memory newBaseURI - ) external initializer { - __ERC721_init(name, symbol); + function initialize(address daoAddress) external initializer { + __ERC721_init("", ""); __Ownable_init(); __UUPSUpgradeable_init(); daoRegistry = DaoRegistry(daoAddress); - setBaseURI(newBaseURI); + setBaseURI("ipfs://"); } function _authorizeUpgrade(address newImplementation) @@ -104,6 +103,16 @@ contract TributeERC721 is _createNewAmountCheckpoint(to); } + function name() public view virtual override returns (string memory) { + return + bytes32ToString(bytes32(daoRegistry.getConfiguration(TokenName))); + } + + function symbol() public view virtual override returns (string memory) { + return + bytes32ToString(bytes32(daoRegistry.getConfiguration(TokenSymbol))); + } + function _baseURI() internal view override returns (string memory) { return baseURI; } @@ -112,15 +121,25 @@ contract TributeERC721 is baseURI = newBaseURI; } - function tokenURI(uint256 tokenId) + function tokenURI(uint256) public view virtual override returns (string memory) { - _requireMinted(tokenId); - return _baseURI(); + return + string( + abi.encodePacked( + _baseURI(), + bytes32ToString( + bytes32(daoRegistry.getConfiguration(TokenMediaPt1)) + ), + bytes32ToString( + bytes32(daoRegistry.getConfiguration(TokenMediaPt2)) + ) + ) + ); } /** @@ -203,4 +222,20 @@ contract TributeERC721 is numCheckpoints[member] = nCheckpoints + 1; } } + + function bytes32ToString(bytes32 _bytes32) + public + pure + returns (string memory) + { + uint8 i = 0; + while (i < 32 && _bytes32[i] != 0) { + i++; + } + bytes memory bytesArray = new bytes(i); + for (i = 0; i < 32 && _bytes32[i] != 0; i++) { + bytesArray[i] = _bytes32[i]; + } + return string(bytesArray); + } } diff --git a/contracts/test/TributeERC721V2.sol b/contracts/test/TributeERC721V2.sol index dc0339cba..bac725f0d 100644 --- a/contracts/test/TributeERC721V2.sol +++ b/contracts/test/TributeERC721V2.sol @@ -26,6 +26,10 @@ contract TributeERC721V2 is DaoRegistry public daoRegistry; + bytes32 constant TokenName = keccak256("dao-collection.TokenName"); + bytes32 constant TokenSymbol = keccak256("dao-collection.TokenSymbol"); + bytes32 constant TokenMediaPt1 = keccak256("dao-collection.TokenMediaPt1"); + bytes32 constant TokenMediaPt2 = keccak256("dao-collection.TokenMediaPt2"); bytes32 constant Transferable = keccak256("dao-collection.Transferable"); bytes32 constant CollectionSize = keccak256("dao-collection.CollectionSize"); @@ -49,17 +53,12 @@ contract TributeERC721V2 is } // https://docs.openzeppelin.com/contracts/4.x/api/proxy#Initializable - function initialize( - string memory name, - string memory symbol, - address daoAddress, - string memory newBaseURI - ) external initializer { - __ERC721_init(name, symbol); + function initialize(address daoAddress) external initializer { + __ERC721_init("", ""); __Ownable_init(); __UUPSUpgradeable_init(); daoRegistry = DaoRegistry(daoAddress); - setBaseURI(newBaseURI); + setBaseURI("ipfs://"); } function _authorizeUpgrade(address newImplementation) @@ -105,6 +104,16 @@ contract TributeERC721V2 is _createNewAmountCheckpoint(to); } + function name() public view virtual override returns (string memory) { + return + bytes32ToString(bytes32(daoRegistry.getConfiguration(TokenName))); + } + + function symbol() public view virtual override returns (string memory) { + return + bytes32ToString(bytes32(daoRegistry.getConfiguration(TokenSymbol))); + } + function _baseURI() internal view override returns (string memory) { return baseURI; } @@ -113,15 +122,25 @@ contract TributeERC721V2 is baseURI = newBaseURI; } - function tokenURI(uint256 tokenId) + function tokenURI(uint256) public view virtual override returns (string memory) { - _requireMinted(tokenId); - return _baseURI(); + return + string( + abi.encodePacked( + _baseURI(), + bytes32ToString( + bytes32(daoRegistry.getConfiguration(TokenMediaPt1)) + ), + bytes32ToString( + bytes32(daoRegistry.getConfiguration(TokenMediaPt2)) + ) + ) + ); } /** @@ -204,4 +223,20 @@ contract TributeERC721V2 is numCheckpoints[member] = nCheckpoints + 1; } } + + function bytes32ToString(bytes32 _bytes32) + public + pure + returns (string memory) + { + uint8 i = 0; + while (i < 32 && _bytes32[i] != 0) { + i++; + } + bytes memory bytesArray = new bytes(i); + for (i = 0; i < 32 && _bytes32[i] != 0; i++) { + bytesArray[i] = _bytes32[i]; + } + return string(bytesArray); + } } diff --git a/tasks/tributeERC721Tasks.js b/tasks/tributeERC721Tasks.js index 872612623..294aea031 100644 --- a/tasks/tributeERC721Tasks.js +++ b/tasks/tributeERC721Tasks.js @@ -7,30 +7,20 @@ task( "deployTributeERC721", "Task that deploys a TributeERC721 proxy collection and implementation (will reuse existing implementation)" ) - .addPositionalParam("name") - .addPositionalParam("symbol") .addPositionalParam("daoAddress") - .addPositionalParam("baseURI") .setAction(async (taskArgs, hre) => { - const { name, symbol, daoAddress, baseURI } = taskArgs; + const { daoAddress } = taskArgs; const { network } = hre.hardhatArguments; log(`Deployment started at ${new Date().toISOString()}`); - log(`Deploying TributeERC721 ${name} to ${network} network`); - log(`Constructor args: `); log( - `-- Name: ${name}, Symbol: ${symbol}, daoAddress: ${daoAddress}, baseURI: ${baseURI}\n` + `Deploying TributeERC721 for daoAddress ${daoAddress} to ${network} network` ); const TributeERC721 = await ethers.getContractFactory("TributeERC721"); // Deploying the proxy and implementation. Implementation can be reused. - const proxy = await hre.upgrades.deployProxy(TributeERC721, [ - name, - symbol, - daoAddress, - baseURI, - ]); + const proxy = await hre.upgrades.deployProxy(TributeERC721, [daoAddress]); await proxy.deployed(); log(`Proxy deployed: ${proxy.address}`); const implementation = await upgrades.erc1967.getImplementationAddress( diff --git a/test/tribute-erc721/tribute-erc721.test.js b/test/tribute-erc721/tribute-erc721.test.js index 1c63a6937..33e9de2c0 100644 --- a/test/tribute-erc721/tribute-erc721.test.js +++ b/test/tribute-erc721/tribute-erc721.test.js @@ -73,7 +73,11 @@ const setNftConfigurations = async ( manager, daoAddress, transferable, - collectionSize + collectionSize, + tokenName, + tokenSymbol, + tokenMediaPt1, + tokenMediaPt2 ) => { const nonce = (await manager.nonces(daoAddress)).toNumber() + 1; const proposal = { @@ -88,6 +92,30 @@ const setNftConfigurations = async ( }; const configs = [ + { + key: sha3("dao-collection.TokenName"), + numericValue: ethers.utils.formatBytes32String(tokenName), + addressValue: ZERO_ADDRESS, + configType: 0, + }, + { + key: sha3("dao-collection.TokenSymbol"), + numericValue: ethers.utils.formatBytes32String(tokenSymbol), + addressValue: ZERO_ADDRESS, + configType: 0, + }, + { + key: sha3("dao-collection.TokenMediaPt1"), + numericValue: ethers.utils.formatBytes32String(tokenMediaPt1), + addressValue: ZERO_ADDRESS, + configType: 0, + }, + { + key: sha3("dao-collection.TokenMediaPt2"), + numericValue: ethers.utils.formatBytes32String(tokenMediaPt2), + addressValue: ZERO_ADDRESS, + configType: 0, + }, { key: sha3("dao-collection.Transferable"), numericValue: transferable, @@ -126,7 +154,10 @@ const setNftConfigurations = async ( ); }; -const BASE_URI = "https://www.fakenfturi123.com/"; +const TOKEN_MEDIA = "QmdXQLJWkv27YFLxJ33piMBpxmL6236TdrsD1Lh8n33WXU"; +const TOKEN_URI = `ipfs://${TOKEN_MEDIA}`; +const COLLECTION_NAME = "Test DAO NFT"; +const COLLECTION_SYMBOL = "TDN"; const deployAndConfigureCollection = async ( manager, @@ -135,14 +166,19 @@ const deployAndConfigureCollection = async ( collectionSize ) => { const TributeERC721 = await hre.ethers.getContractFactory("TributeERC721"); - const proxy = await upgrades.deployProxy(TributeERC721, [ - "Test DAO NFT", - "TDN", - daoAddress, - BASE_URI, - ]); + const proxy = await upgrades.deployProxy(TributeERC721, [daoAddress]); await proxy.deployed(); - await setNftConfigurations(manager, daoAddress, transferable, collectionSize); + await setNftConfigurations( + manager, + daoAddress, + transferable, + collectionSize, + COLLECTION_NAME, + COLLECTION_SYMBOL, + TOKEN_MEDIA.slice(0, 23), + TOKEN_MEDIA.slice(23) + ); + return { proxy }; }; @@ -169,6 +205,21 @@ describe("nft test", () => { this.snapshotId = await takeChainSnapshot(); }); + it("Configured with name and symbol in DAO", async () => { + const { proxy } = await deployAndConfigureCollection( + this.adapters.manager, + daoAddress, + 1, + 100 + ); + + const name = await proxy.name(); + const symbol = await proxy.symbol(); + + expect(name).to.equal(COLLECTION_NAME); + expect(symbol).to.equal(COLLECTION_SYMBOL); + }); + it("Can upgrade proxy", async () => { const { proxy } = await deployAndConfigureCollection( this.adapters.manager, @@ -531,6 +582,31 @@ describe("nft test", () => { expect(uri1).to.equal(uri2); }); + + it("tokenURI correctly resolves tokenMedia parts to ipfs cid", async () => { + const { proxy } = await deployAndConfigureCollection( + this.adapters.manager, + daoAddress, + 1, + 100 + ); + const [collectionAddress, owner, nonce] = [proxy.address, accounts[0], 1]; + await proxy.mint( + owner, + nonce, + generateNFTCouponSignature({ + collectionAddress, + owner, + nonce, + chainId, + daoAddress, + }) + ); + + const uri = await proxy.tokenURI(1); + + expect(uri).to.equal(TOKEN_URI); + }); }); const generateManagerCouponSignature = ({