From d9d252e74202bf16a42a03dec927ef98dbdc848a Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Wed, 1 Jan 2025 15:59:26 +0400 Subject: [PATCH] better uri for receipt --- .../receipt/ERC20PriceOracleReceipt.sol | 3 +- src/concrete/receipt/Receipt.sol | 24 ++++++--- test/abstract/ReceiptFactoryTest.sol | 51 +++++++++++++++++++ .../ERC20PriceOracleReceipt.metadata.t.sol | 51 ------------------- .../concrete/receipt/Receipt.metadata.t.sol | 45 ++++++++++++++++ 5 files changed, 113 insertions(+), 61 deletions(-) create mode 100644 test/src/concrete/receipt/Receipt.metadata.t.sol diff --git a/src/concrete/receipt/ERC20PriceOracleReceipt.sol b/src/concrete/receipt/ERC20PriceOracleReceipt.sol index b9aa9794..fa745241 100644 --- a/src/concrete/receipt/ERC20PriceOracleReceipt.sol +++ b/src/concrete/receipt/ERC20PriceOracleReceipt.sol @@ -2,8 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Receipt, DATA_URI_BASE64_PREFIX} from "./Receipt.sol"; -import {Base64Upgradeable as Base64} from "openzeppelin-contracts-upgradeable/contracts/utils/Base64Upgradeable.sol"; +import {Receipt, DATA_URI_BASE64_PREFIX, Base64} from "./Receipt.sol"; import {LibFixedPointDecimalFormat} from "rain.math.fixedpoint/lib/format/LibFixedPointDecimalFormat.sol"; import {ZeroReceiptId} from "../../error/ErrReceipt.sol"; import { diff --git a/src/concrete/receipt/Receipt.sol b/src/concrete/receipt/Receipt.sol index afb6ce5a..c3983cc4 100644 --- a/src/concrete/receipt/Receipt.sol +++ b/src/concrete/receipt/Receipt.sol @@ -13,16 +13,11 @@ import {ERC1155Upgradeable as ERC1155} from import {StringsUpgradeable as Strings} from "openzeppelin-contracts-upgradeable/contracts/utils/StringsUpgradeable.sol"; import {IERC20MetadataUpgradeable as IERC20Metadata} from "openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; +import {Base64Upgradeable as Base64} from "openzeppelin-contracts-upgradeable/contracts/utils/Base64Upgradeable.sol"; /// @dev The prefix for data URIs as base64 encoded JSON. string constant DATA_URI_BASE64_PREFIX = "data:application/json;base64,"; -/// @dev The URI for the metadata of the `Receipt` contract. -/// Decodes to a simple generic receipt metadata object. -/// `{"name":"Receipt","decimals":18,"description":"A receipt for a ReceiptVault."}` -string constant RECEIPT_METADATA_DATA_URI = - "eyJuYW1lIjoiUmVjZWlwdCIsImRlY2ltYWxzIjoxOCwiZGVzY3JpcHRpb24iOiJBIHJlY2VpcHQgZm9yIGEgUmVjZWlwdFZhdWx0LiJ9"; - /// @dev The name of a `Receipt` is " Receipt". string constant RECEIPT_NAME_SUFFIX = " Receipt"; @@ -69,11 +64,24 @@ contract Receipt is IReceiptV2, ERC1155, ICloneableV2 { /// @inheritdoc ERC1155 function uri(uint256) public view virtual override returns (string memory) { - return string.concat(DATA_URI_BASE64_PREFIX, RECEIPT_METADATA_DATA_URI); + bytes memory json = bytes( + string.concat( + "{\"decimals\":18,\"description\":\"1 of these receipts can be burned alongside 1 ", + _vaultShareSymbol(), + " to redeem ", + _vaultAssetSymbol(), + " from the vault.\",", + "\"name\":\"", + name(), + "\"}" + ) + ); + + return string.concat(DATA_URI_BASE64_PREFIX, Base64.encode(json)); } /// @inheritdoc IReceiptV2 - function name() external view virtual returns (string memory) { + function name() public view virtual returns (string memory) { return string.concat(_vaultShareSymbol(), RECEIPT_NAME_SUFFIX); } diff --git a/test/abstract/ReceiptFactoryTest.sol b/test/abstract/ReceiptFactoryTest.sol index 519d4558..4859a366 100644 --- a/test/abstract/ReceiptFactoryTest.sol +++ b/test/abstract/ReceiptFactoryTest.sol @@ -7,8 +7,23 @@ import {CloneFactory} from "rain.factory/concrete/CloneFactory.sol"; import {Test, Vm} from "forge-std/Test.sol"; import {Receipt as ReceiptContract} from "src/concrete/receipt/Receipt.sol"; import {ERC20PriceOracleReceipt} from "src/concrete/receipt/ERC20PriceOracleReceipt.sol"; +import {DATA_URI_BASE64_PREFIX} from "src/concrete/receipt/Receipt.sol"; +import {Base64} from "solady/utils/Base64.sol"; contract ReceiptFactoryTest is Test { + struct Metadata { + uint8 decimals; + string description; + string name; + } + + struct MetadataWithImage { + uint8 decimals; + string description; + string image; + string name; + } + ICloneableFactoryV2 internal immutable iFactory; ReceiptContract internal immutable iReceiptImplementation; ERC20PriceOracleReceipt internal immutable iERC20PriceOracleReceiptImplementation; @@ -18,4 +33,40 @@ contract ReceiptFactoryTest is Test { iReceiptImplementation = new ReceiptContract(); iERC20PriceOracleReceiptImplementation = new ERC20PriceOracleReceipt(); } + + function decodeMetadataURI(string memory uri) internal pure returns (Metadata memory) { + uint256 uriLength = bytes(uri).length; + assembly ("memory-safe") { + mstore(uri, 29) + } + assertEq(uri, DATA_URI_BASE64_PREFIX); + assembly ("memory-safe") { + uri := add(uri, 29) + mstore(uri, sub(uriLength, 29)) + } + + string memory uriDecoded = string(Base64.decode(uri)); + bytes memory uriJsonData = vm.parseJson(uriDecoded); + + Metadata memory metadataJson = abi.decode(uriJsonData, (Metadata)); + return metadataJson; + } + + function decodeMetadataURIWithImage(string memory uri) internal pure returns (MetadataWithImage memory) { + uint256 uriLength = bytes(uri).length; + assembly ("memory-safe") { + mstore(uri, 29) + } + assertEq(uri, DATA_URI_BASE64_PREFIX); + assembly ("memory-safe") { + uri := add(uri, 29) + mstore(uri, sub(uriLength, 29)) + } + + string memory uriDecoded = string(Base64.decode(uri)); + bytes memory uriJsonData = vm.parseJson(uriDecoded); + + MetadataWithImage memory metadataJson = abi.decode(uriJsonData, (MetadataWithImage)); + return metadataJson; + } } diff --git a/test/src/concrete/receipt/ERC20PriceOracleReceipt.metadata.t.sol b/test/src/concrete/receipt/ERC20PriceOracleReceipt.metadata.t.sol index d912fc61..81b6e28c 100644 --- a/test/src/concrete/receipt/ERC20PriceOracleReceipt.metadata.t.sol +++ b/test/src/concrete/receipt/ERC20PriceOracleReceipt.metadata.t.sol @@ -5,8 +5,6 @@ pragma solidity =0.8.25; import {ReceiptFactoryTest} from "test/abstract/ReceiptFactoryTest.sol"; import {TestReceiptManager} from "test/concrete/TestReceiptManager.sol"; import {ERC20PriceOracleReceipt} from "src/concrete/receipt/ERC20PriceOracleReceipt.sol"; -import {Base64} from "solady/utils/Base64.sol"; -import {DATA_URI_BASE64_PREFIX} from "src/concrete/receipt/Receipt.sol"; import {LibFixedPointDecimalFormat} from "rain.math.fixedpoint/lib/format/LibFixedPointDecimalFormat.sol"; import { LibFixedPointDecimalArithmeticOpenZeppelin, @@ -79,55 +77,6 @@ contract MutableMetadataReceipt is ERC20PriceOracleReceipt { } contract ERC20PriceOracleReceiptMetadataTest is ReceiptFactoryTest { - struct Metadata { - uint8 decimals; - string description; - string name; - } - - struct MetadataWithImage { - uint8 decimals; - string description; - string image; - string name; - } - - function decodeMetadataURI(string memory uri) private pure returns (Metadata memory) { - uint256 uriLength = bytes(uri).length; - assembly ("memory-safe") { - mstore(uri, 29) - } - assertEq(uri, DATA_URI_BASE64_PREFIX); - assembly ("memory-safe") { - uri := add(uri, 29) - mstore(uri, sub(uriLength, 29)) - } - - string memory uriDecoded = string(Base64.decode(uri)); - bytes memory uriJsonData = vm.parseJson(uriDecoded); - - Metadata memory metadataJson = abi.decode(uriJsonData, (Metadata)); - return metadataJson; - } - - function decodeMetadataURIWithImage(string memory uri) private pure returns (MetadataWithImage memory) { - uint256 uriLength = bytes(uri).length; - assembly ("memory-safe") { - mstore(uri, 29) - } - assertEq(uri, DATA_URI_BASE64_PREFIX); - assembly ("memory-safe") { - uri := add(uri, 29) - mstore(uri, sub(uriLength, 29)) - } - - string memory uriDecoded = string(Base64.decode(uri)); - bytes memory uriJsonData = vm.parseJson(uriDecoded); - - MetadataWithImage memory metadataJson = abi.decode(uriJsonData, (MetadataWithImage)); - return metadataJson; - } - function testReceiptURIZeroError() external { // Deploy the Receipt contract TestReceiptManager testManager = new TestReceiptManager(); diff --git a/test/src/concrete/receipt/Receipt.metadata.t.sol b/test/src/concrete/receipt/Receipt.metadata.t.sol new file mode 100644 index 00000000..2360714e --- /dev/null +++ b/test/src/concrete/receipt/Receipt.metadata.t.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity =0.8.25; + +import {ReceiptFactoryTest} from "test/abstract/ReceiptFactoryTest.sol"; +import {TestReceiptManager} from "test/concrete/TestReceiptManager.sol"; +import {Receipt as ReceiptContract} from "src/concrete/receipt/Receipt.sol"; + +contract ReceiptMetadataTest is ReceiptFactoryTest { + function testReceiptURI(uint256 id) external { + // Deploy the Receipt contract + TestReceiptManager testManager = new TestReceiptManager(); + ReceiptContract receipt = + ReceiptContract(iFactory.clone(address(iReceiptImplementation), abi.encode(address(testManager)))); + + string memory uri = receipt.uri(id); + + Metadata memory metadataJson = decodeMetadataURI(uri); + + assertEq( + metadataJson.description, + "1 of these receipts can be burned alongside 1 TRM to redeem TRMAsset from the vault." + ); + assertEq(metadataJson.decimals, 18); + assertEq(metadataJson.name, "TRM Receipt"); + } + + function testReceiptName() external { + // Deploy the Receipt contract + TestReceiptManager testManager = new TestReceiptManager(); + ReceiptContract receipt = + ReceiptContract(iFactory.clone(address(iReceiptImplementation), abi.encode(address(testManager)))); + + assertEq(receipt.name(), "TRM Receipt"); + } + + function testReceiptSymbol() external { + // Deploy the Receipt contract + TestReceiptManager testManager = new TestReceiptManager(); + ReceiptContract receipt = + ReceiptContract(iFactory.clone(address(iReceiptImplementation), abi.encode(address(testManager)))); + + assertEq(receipt.symbol(), "TRM RCPT"); + } +}