diff --git a/contracts/README.md b/contracts/README.md index 26b6ebf2..cf42d0ca 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -27,6 +27,8 @@ $ forge build $ forge test ``` +Run tests with `forge test -vvv` to see the console logs, which will show trove URI data. + ### Format ```shell diff --git a/contracts/src/NFTMetadata/MetadataNFT.sol b/contracts/src/NFTMetadata/MetadataNFT.sol index 80714a4a..eca5efa7 100644 --- a/contracts/src/NFTMetadata/MetadataNFT.sol +++ b/contracts/src/NFTMetadata/MetadataNFT.sol @@ -8,6 +8,8 @@ import "./utils/baseSVG.sol"; import "./utils/bauhaus.sol"; import "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; + import {ITroveManager} from "src/Interfaces/ITroveManager.sol"; interface IMetadataNFT { @@ -15,6 +17,7 @@ interface IMetadataNFT { uint256 _tokenId; address _owner; address _collToken; + address _boldToken; uint256 _collAmount; uint256 _debtAmount; uint256 _interestRate; @@ -35,7 +38,8 @@ contract MetadataNFT is IMetadataNFT { } function uri(TroveData memory _troveData) public view returns (string memory) { - return json.formattedMetadata(name, description, renderSVGImage(_troveData)); + string memory attr = attributes(_troveData); + return json.formattedMetadata(name, description, renderSVGImage(_troveData), attr); } function renderSVGImage(TroveData memory _troveData) internal view returns (string memory) { @@ -45,6 +49,27 @@ contract MetadataNFT is IMetadataNFT { ); } + function attributes(TroveData memory _troveData) public view returns (string memory) { + //include: collateral token address, collateral amount, debt token address, debt amount, interest rate, status + return string.concat( + '[{"trait_type": "Collateral Token", "value": "', + Strings.toHexString(_troveData._collToken), + '"}, {"trait_type": "Collateral Amount", "value": "', + Strings.toString(_troveData._collAmount), + '"}, {"trait_type": "Debt Token", "value": "', + Strings.toHexString(_troveData._boldToken), + '"}, {"trait_type": "Debt Amount", "value": "', + Strings.toString(_troveData._debtAmount), + '"}, {"trait_type": "Interest Rate", "value": "', + Strings.toString(_troveData._interestRate), + '"}, {"trait_type": "Status", "value": "', + _status2Str(_troveData._status), + '"} ]' + ); + + + } + function dynamicTextComponents(TroveData memory _troveData) public view returns (string memory) { string memory id = LibString.toHexString(_troveData._tokenId); id = string.concat(LibString.slice(id, 0, 6), "...", LibString.slice(id, 38, 42)); diff --git a/contracts/src/NFTMetadata/utils/JSON.sol b/contracts/src/NFTMetadata/utils/JSON.sol index 6a80d114..404009b9 100644 --- a/contracts/src/NFTMetadata/utils/JSON.sol +++ b/contracts/src/NFTMetadata/utils/JSON.sol @@ -10,7 +10,7 @@ library json { string constant DOUBLE_QUOTES = '\\"'; - function formattedMetadata(string memory name, string memory description, string memory svgImg) + function formattedMetadata(string memory name, string memory description, string memory svgImg, string memory attributes) internal pure returns (string memory) @@ -19,7 +19,12 @@ library json { "data:application/json;base64,", encode( bytes( - string.concat("{", _prop("name", name), _prop("description", description), _xmlImage(svgImg), "}") + string.concat("{", _prop("name", name), + _prop("description", description), + _xmlImage(svgImg), + ',"attributes":', + attributes, + "}") ) ) ); diff --git a/contracts/src/TroveNFT.sol b/contracts/src/TroveNFT.sol index c2ffd6ac..97440176 100644 --- a/contracts/src/TroveNFT.sol +++ b/contracts/src/TroveNFT.sol @@ -16,6 +16,7 @@ import {ITroveManager} from "./Interfaces/ITroveManager.sol"; contract TroveNFT is ERC721, ITroveNFT { ITroveManager public immutable troveManager; IERC20Metadata internal immutable collToken; + IBoldToken internal immutable boldToken; IMetadataNFT public immutable metadataNFT; @@ -28,6 +29,7 @@ contract TroveNFT is ERC721, ITroveNFT { troveManager = _addressesRegistry.troveManager(); collToken = _addressesRegistry.collToken(); metadataNFT = _addressesRegistry.metadataNFT(); + boldToken = _addressesRegistry.boldToken(); } function tokenURI(uint256 _tokenId) public view override(ERC721, IERC721Metadata) returns (string memory) { @@ -38,6 +40,7 @@ contract TroveNFT is ERC721, ITroveNFT { _tokenId: _tokenId, _owner: ownerOf(_tokenId), _collToken: address(collToken), + _boldToken: address(boldToken), _collAmount: coll, _debtAmount: debt, _interestRate: annualInterestRate, diff --git a/contracts/src/test/troveNFT.t.sol b/contracts/src/test/troveNFT.t.sol index 3059a039..41189358 100644 --- a/contracts/src/test/troveNFT.t.sol +++ b/contracts/src/test/troveNFT.t.sol @@ -33,4 +33,32 @@ contract troveNFTTest is DevTestSetup { emit log_string(uri); } + + function testTroveURIAttributes() public { + uint256 troveId = _openTrove(); + + TroveNFT troveNFT = TroveNFT(address(troveManager.troveNFT())); + + string memory uri = troveNFT.tokenURI(troveId); + + emit log_string(uri); + + /** TODO: validate each individual attribute, or manually make a json and validate it all at once + // Check for expected attributes + assertTrue(LibString.contains(uri, '"trait_type": "Collateral Token"'), "Collateral Token attribute missing"); + assertTrue(LibString.contains(uri, '"trait_type": "Collateral Amount"'), "Collateral Amount attribute missing"); + assertTrue(LibString.contains(uri, '"trait_type": "Debt Token"'), "Debt Token attribute missing"); + assertTrue(LibString.contains(uri, '"trait_type": "Debt Amount"'), "Debt Amount attribute missing"); + assertTrue(LibString.contains(uri, '"trait_type": "Interest Rate"'), "Interest Rate attribute missing"); + assertTrue(LibString.contains(uri, '"trait_type": "Status"'), "Status attribute missing"); + + // Check for expected values + //assertTrue(LibString.contains(uri, string.concat('"value": "', Strings.toHexString(address(collateral)))), "Incorrect Collateral Token value"); + assertTrue(LibString.contains(uri, '"value": "2000000000000000000"'), "Incorrect Collateral Amount value"); + assertTrue(LibString.contains(uri, string.concat('"value": "', Strings.toHexString(address(boldToken)))), "Incorrect Debt Token value"); + assertTrue(LibString.contains(uri, '"value": "1000000000000000000000"'), "Incorrect Debt Amount value"); + assertTrue(LibString.contains(uri, '"value": "5000000000000000"'), "Incorrect Interest Rate value"); + assertTrue(LibString.contains(uri, '"value": "Active"'), "Incorrect Status value"); + */ + } }