Skip to content

Commit

Permalink
erc20priceoraclereceipt
Browse files Browse the repository at this point in the history
  • Loading branch information
thedavidmeister committed Jan 1, 2025
1 parent 2762899 commit a3e4e7d
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 138 deletions.
99 changes: 99 additions & 0 deletions src/concrete/receipt/ERC20PriceOracleReceipt.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// 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 {LibFixedPointDecimalFormat} from "rain.math.fixedpoint/lib/format/LibFixedPointDecimalFormat.sol";
import {ZeroReceiptId} from "../../error/ErrReceipt.sol";
import {
LibFixedPointDecimalArithmeticOpenZeppelin,
Math
} from "rain.math.fixedpoint/lib/LibFixedPointDecimalArithmeticOpenZeppelin.sol";
import {FIXED_POINT_ONE} from "rain.math.fixedpoint/lib/FixedPointDecimalConstants.sol";

/// @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 = "";

contract ERC20PriceOracleReceipt is Receipt {
/// @inheritdoc Receipt
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));
}

/// Provides the SVG URI for the receipt. Can be overridden to provide a
/// custom SVG URI. Default is an empty string, which will not include an
/// image in the metadata json.
function _receiptSVGURI() internal view virtual returns (string memory) {
return DEFAULT_SVG_URI;
}

/// Provides the symbol of the reference asset that mint amounts are valued
/// in. Can be overridden to provide a custom reference asset symbol. Default
/// is "USD".
function _referenceAssetSymbol() internal view virtual returns (string memory) {
return DEFAULT_REFERENCE_ASSET_SYMBOL;
}

/// Provides the URL for redeeming receipts. Can be overridden to provide a
/// custom redeem URL. Default is an empty string, which will not include a
/// redeem URL in the metadata json.
function _redeemURL() internal view virtual returns (string memory) {
return DEFAULT_REDEEM_URL;
}

/// Provides the brand name for the receipt. Can be overridden to provide a
/// custom brand name. Default is an empty string, which will not include a
/// brand name in the metadata json.
function _brandName() internal view virtual returns (string memory) {
return DEFAULT_BRAND_NAME;
}
}
126 changes: 23 additions & 103 deletions src/concrete/receipt/Receipt.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,28 @@ import {ICloneableV2, ICLONEABLE_V2_SUCCESS} from "rain.factory/interface/IClone
import {IReceiptManagerV1} from "../../interface/IReceiptManagerV1.sol";
import {IReceiptV2} from "../../interface/IReceiptV2.sol";
import {IReceiptVaultV1} from "../../interface/IReceiptVaultV1.sol";
import {OnlyManager, ZeroReceiptId} from "../../error/ErrReceipt.sol";
import {OnlyManager} 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 of a `Receipt` is "<vault share symbol> RCPT".
string constant RECEIPT_SYMBOL_SUFFIX = " RCPT";

/// @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
/// receipt information to be emitted and mints/burns according to manager
Expand Down Expand Up @@ -81,47 +68,8 @@ contract Receipt is IReceiptV2, ERC1155, ICloneableV2 {
}

/// @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));
function uri(uint256) public view virtual override returns (string memory) {
return string.concat(DATA_URI_BASE64_PREFIX, RECEIPT_METADATA_DATA_URI);
}

/// @inheritdoc IReceiptV2
Expand All @@ -134,6 +82,20 @@ contract Receipt is IReceiptV2, ERC1155, ICloneableV2 {
return string.concat(_vaultShareSymbol(), RECEIPT_SYMBOL_SUFFIX);
}

/// Provides the symbol of the `ReceiptVault` ERC20 share token that manages
/// this `Receipt`. Can be overridden if the manager is not going to be
/// a `ReceiptVault`.
function _vaultShareSymbol() internal view virtual returns (string memory) {
return IERC20Metadata(payable(address(sManager))).symbol();
}

/// Provides the symbol of the ERC20 asset token that the `ReceiptVault`
/// managing this `Receipt` is accepting for mints. Can be overridden if the
/// manager is not going to be a `ReceiptVault`.
function _vaultAssetSymbol() internal view virtual returns (string memory) {
return IERC20Metadata(IReceiptVaultV1(payable(address(sManager))).asset()).symbol();
}

/// @inheritdoc IReceiptV2
function manager() external view virtual returns (address) {
return address(sManager);
Expand Down Expand Up @@ -168,48 +130,6 @@ contract Receipt is IReceiptV2, ERC1155, ICloneableV2 {
_safeTransferFrom(from, to, id, amount, data);
}

/// Provides the symbol of the `ReceiptVault` ERC20 share token that manages
/// this `Receipt`. Can be overridden if the manager is not going to be
/// a `ReceiptVault`.
function _vaultShareSymbol() internal view virtual returns (string memory) {
return IERC20Metadata(payable(address(sManager))).symbol();
}

/// Provides the symbol of the ERC20 asset token that the `ReceiptVault`
/// managing this `Receipt` is accepting for mints. Can be overridden if the
/// manager is not going to be a `ReceiptVault`.
function _vaultAssetSymbol() internal view virtual returns (string memory) {
return IERC20Metadata(IReceiptVaultV1(payable(address(sManager))).asset()).symbol();
}

/// Provides the SVG URI for the receipt. Can be overridden to provide a
/// custom SVG URI. Default is an empty string, which will not include an
/// image in the metadata json.
function _receiptSVGURI() internal view virtual returns (string memory) {
return DEFAULT_SVG_URI;
}

/// Provides the symbol of the reference asset that mint amounts are valued
/// in. Can be overridden to provide a custom reference asset symbol. Default
/// is "USD".
function _referenceAssetSymbol() internal view virtual returns (string memory) {
return DEFAULT_REFERENCE_ASSET_SYMBOL;
}

/// Provides the URL for redeeming receipts. Can be overridden to provide a
/// custom redeem URL. Default is an empty string, which will not include a
/// redeem URL in the metadata json.
function _redeemURL() internal view virtual returns (string memory) {
return DEFAULT_REDEEM_URL;
}

/// Provides the brand name for the receipt. Can be overridden to provide a
/// custom brand name. Default is an empty string, which will not include a
/// brand name in the metadata json.
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
18 changes: 5 additions & 13 deletions test/abstract/ReceiptFactoryTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,16 @@ import {ICloneableFactoryV2} from "rain.factory/interface/ICloneableFactoryV2.so
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";

contract ReceiptFactoryTest is Test {
ICloneableFactoryV2 internal immutable iFactory;
ReceiptContract internal immutable receiptImplementation;
ReceiptContract internal immutable iReceiptImplementation;
ERC20PriceOracleReceipt internal immutable iERC20PriceOracleReceiptImplementation;

constructor() {
iFactory = new CloneFactory();
receiptImplementation = new ReceiptContract();
}

/// Creates a new `ReceiptContract` clone with the specified manager.
/// @param manager The address to set as the manager of the new ReceiptContract
/// @return The address of the newly created `ReceiptContract` clone
function createReceipt(address manager) internal returns (ReceiptContract) {
// Clone ReceiptContract using the factory and initialize it with the
// manager.
address clone = iFactory.clone(address(receiptImplementation), abi.encode(manager));
// Return the clone cast to ReceiptContract type
return ReceiptContract(clone);
iReceiptImplementation = new ReceiptContract();
iERC20PriceOracleReceiptImplementation = new ERC20PriceOracleReceipt();
}
}
22 changes: 15 additions & 7 deletions test/src/concrete/receipt/Receipt.metadata.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ 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";
import {ERC20PriceOracleReceipt} from "src/concrete/receipt/ERC20PriceOracleReceipt.sol";
import {Base64} from "solady/utils/Base64.sol";
import {Receipt, DATA_URI_BASE64_PREFIX} from "src/concrete/receipt/Receipt.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 All @@ -21,7 +21,7 @@ import {CMASK_QUOTATION_MARK, CMASK_PRINTABLE, CMASK_BACKSLASH} from "rain.strin
/// As all the overridden functions are internal, we need to create a new
/// contract that inherits from `Receipt` and exposes these functions; we can't
/// just mock `Receipt`.
contract MutableMetadataReceipt is Receipt {
contract MutableMetadataReceipt is ERC20PriceOracleReceipt {
string internal sVaultShareSymbol;
string internal sVaultAssetSymbol;
string internal sReceiptSVGURI;
Expand Down Expand Up @@ -131,7 +131,9 @@ contract ReceiptMetadataTest is ReceiptFactoryTest {
function testReceiptURIZeroError() external {
// Deploy the Receipt contract
TestReceiptManager testManager = new TestReceiptManager();
ReceiptContract receipt = createReceipt(address(testManager));
ERC20PriceOracleReceipt receipt = ERC20PriceOracleReceipt(
iFactory.clone(address(iERC20PriceOracleReceiptImplementation), abi.encode(address(testManager)))
);

vm.expectRevert(ZeroReceiptId.selector);
receipt.uri(0);
Expand All @@ -142,7 +144,9 @@ contract ReceiptMetadataTest is ReceiptFactoryTest {

// Deploy the Receipt contract
TestReceiptManager testManager = new TestReceiptManager();
ReceiptContract receipt = createReceipt(address(testManager));
ERC20PriceOracleReceipt receipt = ERC20PriceOracleReceipt(
iFactory.clone(address(iERC20PriceOracleReceiptImplementation), abi.encode(address(testManager)))
);

string memory uri = receipt.uri(id);

Expand Down Expand Up @@ -170,15 +174,19 @@ contract ReceiptMetadataTest is ReceiptFactoryTest {
function testReceiptName() external {
// Deploy the Receipt contract
TestReceiptManager testManager = new TestReceiptManager();
ReceiptContract receipt = createReceipt(address(testManager));
ERC20PriceOracleReceipt receipt = ERC20PriceOracleReceipt(
iFactory.clone(address(iERC20PriceOracleReceiptImplementation), abi.encode(address(testManager)))
);

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

function testReceiptSymbol() external {
// Deploy the Receipt contract
TestReceiptManager testManager = new TestReceiptManager();
ReceiptContract receipt = createReceipt(address(testManager));
ERC20PriceOracleReceipt receipt = ERC20PriceOracleReceipt(
iFactory.clone(address(iERC20PriceOracleReceiptImplementation), abi.encode(address(testManager)))
);

assertEq(receipt.symbol(), "TRM RCPT");
}
Expand Down
Loading

0 comments on commit a3e4e7d

Please sign in to comment.