Skip to content

Commit

Permalink
better uri for receipt
Browse files Browse the repository at this point in the history
  • Loading branch information
thedavidmeister committed Jan 1, 2025
1 parent 3a1ecde commit d9d252e
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 61 deletions.
3 changes: 1 addition & 2 deletions src/concrete/receipt/ERC20PriceOracleReceipt.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
24 changes: 16 additions & 8 deletions src/concrete/receipt/Receipt.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<vault share symbol> Receipt".
string constant RECEIPT_NAME_SUFFIX = " Receipt";

Expand Down Expand Up @@ -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);
}

Expand Down
51 changes: 51 additions & 0 deletions test/abstract/ReceiptFactoryTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
}
51 changes: 0 additions & 51 deletions test/src/concrete/receipt/ERC20PriceOracleReceipt.metadata.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand Down
45 changes: 45 additions & 0 deletions test/src/concrete/receipt/Receipt.metadata.t.sol
Original file line number Diff line number Diff line change
@@ -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");
}
}

0 comments on commit d9d252e

Please sign in to comment.