Skip to content

Commit

Permalink
better json for receipt
Browse files Browse the repository at this point in the history
  • Loading branch information
thedavidmeister committed Dec 29, 2024
1 parent eef1064 commit 915a77a
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 26 deletions.
114 changes: 99 additions & 15 deletions src/concrete/receipt/Receipt.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,41 @@ import {ICloneableV2, ICLONEABLE_V2_SUCCESS} from "rain.factory/interface/IClone

import {IReceiptManagerV1} from "../../interface/IReceiptManagerV1.sol";
import {IReceiptV2} from "../../interface/IReceiptV2.sol";
import {OnlyManager} from "../../error/ErrReceipt.sol";
import {IReceiptVaultV1} from "../../interface/IReceiptVaultV1.sol";
import {OnlyManager, ZeroReceiptId} from "../../error/ErrReceipt.sol";
import {ERC1155Upgradeable as ERC1155} from
"openzeppelin-contracts-upgradeable/contracts/token/ERC1155/ERC1155Upgradeable.sol";
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";
import {LibFixedPointDecimalFormat} from "rain.math.fixedpoint/lib/format/LibFixedPointDecimalFormat.sol";
import {
LibFixedPointDecimalArithmeticOpenZeppelin,
Math
} from "rain.math.fixedpoint/lib/LibFixedPointDecimalArithmeticOpenZeppelin.sol";
import {FIXED_POINT_ONE} from "rain.math.fixedpoint/lib/FixedPointDecimalConstants.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";

/// @dev The symbol for the `Receipt` contract.
string constant RECEIPT_SYMBOL = "RECEIPT";
/// @dev The symbol of a `Receipt` is "<vault share symbol> RCPT".
string constant RECEIPT_SYMBOL_SUFFIX = " RCPT";

/// @dev The name for the `Receipt` contract.
string constant RECEIPT_NAME = "Receipt";
/// @dev The default symbol for the reference asset.
string constant DEFAULT_REFERENCE_ASSET_SYMBOL = "USD";

/// @dev The default URL for redeeming receipts.
string constant DEFAULT_REDEEM_URL = "";

/// @dev The default brand name for the receipt.
string constant DEFAULT_BRAND_NAME = "";

/// @dev The default SVG URI for the receipt.
string constant DEFAULT_SVG_URI = "";

/// @title Receipt
/// @notice The `IReceiptV2` for a `ReceiptVault`. Standard implementation allows
Expand Down Expand Up @@ -54,22 +70,66 @@ contract Receipt is IReceiptV2, ERC1155, ICloneableV2 {
/// implementation in `ReceiptFactory`.
/// Compatible with `ICloneableV2`.
function initialize(bytes memory data) external override initializer returns (bytes32) {
__ERC1155_init(string.concat(DATA_URI_BASE64_PREFIX, RECEIPT_METADATA_DATA_URI));
__ERC1155_init("");

address receiptManager = abi.decode(data, (address));
sManager = IReceiptManagerV1(receiptManager);

return ICLONEABLE_V2_SUCCESS;
}

/// @inheritdoc ERC1155
function uri(uint256 id) public view virtual override returns (string memory) {
if (id == 0) {
revert ZeroReceiptId();
}
string memory redeemURL = _redeemURL();
string memory redeemURLPhrase = bytes(redeemURL).length > 0 ? string.concat(" Redeem at ", redeemURL, ".") : "";

string memory brandName = _brandName();
string memory brandNamePhrase = bytes(brandName).length > 0 ? string.concat(brandName, " ") : "";

string memory receiptSVGURI = _receiptSVGURI();
string memory receiptSVGURIPhrase =
bytes(receiptSVGURI).length > 0 ? string.concat("\"image\":\"", receiptSVGURI, "\",") : "";

bytes memory json = bytes(
string.concat(
"{\"decimals\":18,\"description\":\"1 of these receipts can be burned alongside 1 ",
_vaultShareSymbol(),
" to redeem ",
LibFixedPointDecimalFormat.fixedPointToDecimalString(
LibFixedPointDecimalArithmeticOpenZeppelin.fixedPointDiv(FIXED_POINT_ONE, id, Math.Rounding.Down)
),
" of ",
_vaultAssetSymbol(),
".",
redeemURLPhrase,
"\",",
receiptSVGURIPhrase,
"\"name\":\"Receipt for ",
brandNamePhrase,
"lock at ",
LibFixedPointDecimalFormat.fixedPointToDecimalString(id),
" ",
_referenceAssetSymbol(),
" per ",
_vaultAssetSymbol(),
".\"}"
)
);

return string.concat(DATA_URI_BASE64_PREFIX, Base64.encode(json));
}

/// @inheritdoc IReceiptV2
function name() external pure virtual returns (string memory) {
return RECEIPT_NAME;
function name() external view virtual returns (string memory) {
return string.concat(_vaultShareSymbol(), RECEIPT_NAME_SUFFIX);
}

/// @inheritdoc IReceiptV2
function symbol() external pure virtual returns (string memory) {
return RECEIPT_SYMBOL;
function symbol() external view virtual returns (string memory) {
return string.concat(_vaultShareSymbol(), RECEIPT_SYMBOL_SUFFIX);
}

/// @inheritdoc IReceiptV2
Expand Down Expand Up @@ -106,6 +166,30 @@ contract Receipt is IReceiptV2, ERC1155, ICloneableV2 {
_safeTransferFrom(from, to, id, amount, data);
}

function _vaultShareSymbol() internal view virtual returns (string memory) {
return IERC20Metadata(payable(address(sManager))).symbol();
}

function _vaultAssetSymbol() internal view virtual returns (string memory) {
return IERC20Metadata(IReceiptVaultV1(payable(address(sManager))).asset()).symbol();
}

function _receiptSVGURI() internal view virtual returns (string memory) {
return DEFAULT_SVG_URI;
}

function _referenceAssetSymbol() internal view virtual returns (string memory) {
return DEFAULT_REFERENCE_ASSET_SYMBOL;
}

function _redeemURL() internal view virtual returns (string memory) {
return DEFAULT_REDEEM_URL;
}

function _brandName() internal view virtual returns (string memory) {
return DEFAULT_BRAND_NAME;
}

/// Checks with the manager before authorizing transfer IN ADDITION to
/// `super` inherited checks.
/// @inheritdoc ERC1155
Expand Down
3 changes: 3 additions & 0 deletions src/error/ErrReceipt.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ pragma solidity ^0.8.25;

/// Thrown when the manager is not the caller.
error OnlyManager();

/// Thrown when the receipt ID is zero.
error ZeroReceiptId();
29 changes: 29 additions & 0 deletions test/concrete/TestReceiptManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,33 @@ import {IReceiptManagerV1} from "src/interface/IReceiptManagerV1.sol";
/// @param to The transfer attemped to this address.
error UnauthorizedTransfer(address from, address to);

contract TestReceiptManagerAsset {
function symbol() external pure returns (string memory) {
return "TRMAsset";
}

function name() external pure returns (string memory) {
return "TestReceiptManagerAsset";
}
}

/// @title TestReceiptManager
/// @notice TEST contract that can be the manager of an `IReceiptV2` and forward
/// function calls to the manager restricted functions on the receipt. Completely
/// insecure, intended for use only by the test harness to drive tests.
contract TestReceiptManager is IReceiptManagerV1 {
/// The address of the test asset.
address internal iAsset;

/// The address that is authorized to send transfers.
address internal sFrom;
/// The address that is authorized to receive transfers.
address internal sTo;

constructor() {
iAsset = address(new TestReceiptManagerAsset());
}

/// Anon can set the from address.
/// @param from The new `from` address.
function setFrom(address from) external {
Expand Down Expand Up @@ -86,4 +103,16 @@ contract TestReceiptManager is IReceiptManagerV1 {
) external {
receipt.managerTransferFrom(from, to, id, amount, data);
}

function name() external pure returns (string memory) {
return "TestReceiptManager";
}

function symbol() external pure returns (string memory) {
return "TRM";
}

function asset() external view returns (address) {
return iAsset;
}
}
38 changes: 27 additions & 11 deletions test/src/concrete/receipt/Receipt.metadata.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import {ReceiptFactoryTest} from "test/abstract/ReceiptFactoryTest.sol";
import {TestReceiptManager} from "test/concrete/TestReceiptManager.sol";
import {Receipt as ReceiptContract} from "src/concrete/receipt/Receipt.sol";
import {Base64} from "solady/utils/Base64.sol";
import {Receipt, DATA_URI_BASE64_PREFIX} from "src/concrete/receipt/Receipt.sol";
import {LibFixedPointDecimalFormat} from "rain.math.fixedpoint/lib/format/LibFixedPointDecimalFormat.sol";
import {
Receipt,
RECEIPT_METADATA_DATA_URI,
DATA_URI_BASE64_PREFIX,
RECEIPT_NAME,
RECEIPT_SYMBOL
} from "src/concrete/receipt/Receipt.sol";
LibFixedPointDecimalArithmeticOpenZeppelin,
Math
} from "rain.math.fixedpoint/lib/LibFixedPointDecimalArithmeticOpenZeppelin.sol";
import {FIXED_POINT_ONE} from "rain.math.fixedpoint/lib/FixedPointDecimalConstants.sol";

contract ReceiptMetadataTest is ReceiptFactoryTest {
struct Metadata {
Expand All @@ -22,6 +22,8 @@ contract ReceiptMetadataTest is ReceiptFactoryTest {
}

function testReceiptURI(uint256 id) external {
vm.assume(id != 0);

// Deploy the Receipt contract
TestReceiptManager testManager = new TestReceiptManager();
ReceiptContract receipt = createReceipt(address(testManager));
Expand All @@ -37,30 +39,44 @@ contract ReceiptMetadataTest is ReceiptFactoryTest {
uri := add(uri, 29)
mstore(uri, sub(uriLength, 29))
}
assertEq(uri, RECEIPT_METADATA_DATA_URI);

string memory uriDecoded = string(Base64.decode(uri));
bytes memory uriJsonData = vm.parseJson(uriDecoded);

Metadata memory metadataJson = abi.decode(uriJsonData, (Metadata));
assertEq(metadataJson.description, "A receipt for a ReceiptVault.");

string memory idInvFormatted = LibFixedPointDecimalFormat.fixedPointToDecimalString(
LibFixedPointDecimalArithmeticOpenZeppelin.fixedPointDiv(FIXED_POINT_ONE, id, Math.Rounding.Down)
);
assertEq(
metadataJson.description,
string.concat(
"1 of these receipts can be burned alongside 1 TRM to redeem ", idInvFormatted, " of TRMAsset."
)
);

assertEq(metadataJson.decimals, 18);
assertEq(metadataJson.name, RECEIPT_NAME);
assertEq(
metadataJson.name,
string.concat(
"Receipt for lock at ", LibFixedPointDecimalFormat.fixedPointToDecimalString(id), " USD per TRMAsset."
)
);
}

function testReceiptName() external {
// Deploy the Receipt contract
TestReceiptManager testManager = new TestReceiptManager();
ReceiptContract receipt = createReceipt(address(testManager));

assertEq(receipt.name(), RECEIPT_NAME);
assertEq(receipt.name(), "TRM Receipt");
}

function testReceiptSymbol() external {
// Deploy the Receipt contract
TestReceiptManager testManager = new TestReceiptManager();
ReceiptContract receipt = createReceipt(address(testManager));

assertEq(receipt.symbol(), RECEIPT_SYMBOL);
assertEq(receipt.symbol(), "TRM RCPT");
}
}

0 comments on commit 915a77a

Please sign in to comment.