Description:
Governance contract for decentralized decision-making.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/modules/DEPOS/PositionTokenRenderer.sol": {
"content": "// SPDX-License-Identifier: MIT
/// forge-lint: disable-start(mixed-case-function)
pragma solidity >=0.8.15;
// Interfaces
import {IPositionTokenRenderer} from "src/modules/DEPOS/IPositionTokenRenderer.sol";
import {IDepositPositionManager} from "src/modules/DEPOS/IDepositPositionManager.sol";
import {IERC165} from "@openzeppelin-5.3.0/utils/introspection/IERC165.sol";
// Libraries
import {ERC20} from "@solmate-6.2.0/tokens/ERC20.sol";
import {Strings} from "@openzeppelin-5.3.0/utils/Strings.sol";
import {Base64} from "@openzeppelin-5.3.0/utils/Base64.sol";
import {Timestamp} from "src/libraries/Timestamp.sol";
import {DecimalString} from "src/libraries/DecimalString.sol";
import {FullMath} from "src/libraries/FullMath.sol";
// solhint-disable quotes
/// @title Position Token Renderer
/// @notice Implementation of the IPositionTokenRenderer interface
/// This contract implements a custom token renderer
/// for the Olympus Deposit Position Manager
contract PositionTokenRenderer is IPositionTokenRenderer {
// ========== STATE VARIABLES ========== //
/// @notice The number of decimal places to display when rendering values as decimal strings
uint8 public constant DISPLAY_DECIMALS = 2;
uint8 public constant OHM_DECIMALS = 9;
// ========== FUNCTIONS ========== //
/// @inheritdoc IPositionTokenRenderer
function tokenURI(
address positionManager_,
uint256 positionId_
) external view override returns (string memory) {
// Validate that the position manager supports the IDepositPositionManager interface
if (
!IERC165(positionManager_).supportsInterface(type(IDepositPositionManager).interfaceId)
) {
revert PositionTokenRenderer_InvalidAddress();
}
// Get the position data from the position manager
IDepositPositionManager.Position memory position = IDepositPositionManager(positionManager_)
.getPosition(positionId_);
// Get the decimals of the deposit token
uint8 depositDecimals = ERC20(position.asset).decimals();
string memory cdSymbol = ERC20(position.asset).symbol();
// Check if the position is convertible
bool positionIsConvertible = IDepositPositionManager(positionManager_).isConvertible(
positionId_
);
// Generate the JSON metadata
string memory jsonContent = string.concat(
"{",
'"name": "Olympus Deposit Position",',
'"symbol": "ODP",',
'"attributes": [',
string.concat(
'{"trait_type": "Position ID", "value": ',
Strings.toString(positionId_),
"},"
),
string.concat(
'{"trait_type": "Deposit Asset", "value": "',
Strings.toHexString(position.asset),
'"},'
),
string.concat(
'{"trait_type": "Deposit Period", "value": ',
Strings.toString(position.periodMonths),
"},"
),
string.concat(
'{"trait_type": "Expiry", "display_type": "date", "value": ',
Strings.toString(position.expiry),
"},"
),
positionIsConvertible
? string.concat(
'{"trait_type": "Conversion Price", "value": ',
DecimalString.toDecimalString(
position.conversionPrice,
depositDecimals,
DISPLAY_DECIMALS
),
"},"
)
: "",
string.concat(
'{"trait_type": "Remaining Deposit", "value": ',
DecimalString.toDecimalString(
position.remainingDeposit,
depositDecimals,
DISPLAY_DECIMALS
),
"}"
),
"],",
string.concat(
'"image": "',
"data:image/svg+xml;base64,",
Base64.encode(bytes(_renderSVG(position, cdSymbol, positionIsConvertible))),
'"'
),
"}"
);
return string.concat("data:application/json;base64,", Base64.encode(bytes(jsonContent)));
}
// ========== INTERNAL FUNCTIONS ========== //
function _getTimeString(uint48 time_) internal pure returns (string memory) {
(string memory year, string memory month, string memory day) = Timestamp.toPaddedString(
time_
);
return string.concat(year, "-", month, "-", day);
}
function _renderSVG(
IDepositPositionManager.Position memory position_,
string memory cdSymbol_,
bool positionIsConvertible_
) internal view returns (string memory) {
return
string.concat(
'<svg width="500" height="600" viewBox="0 0 500 600" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect width="500" height="600" rx="18" fill="#141722" /><rect x="73.0154" y="63" width="353" height="353" rx="176.5" fill="#708B96" /><path id="Vector" d="M233.546 286.32C213.544 281.175 197.413 260.273 197.413 236.476C197.413 209.142 221.287 185.346 249.676 185.346C280.002 185.989 301.617 209.464 301.617 236.476C301.617 260.273 287.1 278.924 265.807 286.32V317.513H324.523V294.36H284.196V298.862C310.65 290.501 324.845 263.81 324.845 236.476C324.845 195.958 291.939 162.193 249.676 162.193C208.705 162.193 174.185 195.958 174.185 236.476C174.185 263.81 188.057 289.214 213.544 299.183L214.189 294.36H174.185V317.513H233.546V286.32Z" fill="#EEE9E2" />',
string.concat(
'<text xml:space="preserve" class="heading"><tspan x="32" y="465.504">',
positionIsConvertible_
? string.concat(cdSymbol_, "-OHM Convertible Deposit")
: string.concat(cdSymbol_, " Yield-Bearing Deposit"),
"</tspan></text>"
),
'<rect x="33" y="480" width="434" height="110" rx="9" fill="#2C2E37" />',
string.concat(
'<text xml:space="preserve" class="standard-text"><tspan x="42" y="503">Expiry</tspan><tspan x="457" y="503" text-anchor="end">',
_getTimeString(position_.expiry),
"</tspan></text>"
),
string.concat(
'<text xml:space="preserve" class="standard-text"><tspan x="42" y="527">Deposit Period</tspan><tspan x="457" y="527" text-anchor="end">',
Strings.toString(position_.periodMonths),
" months",
"</tspan></text>"
),
string.concat(
'<text xml:space="preserve" class="standard-text"><tspan x="42" y="551">',
positionIsConvertible_ ? "Remaining Deposit" : "Deposit",
'</tspan><tspan x="457" y="551" text-anchor="end">',
DecimalString.toDecimalString(
position_.remainingDeposit,
ERC20(position_.asset).decimals(),
DISPLAY_DECIMALS
),
" ",
cdSymbol_,
"</tspan></text>"
),
positionIsConvertible_
? string.concat(
'<text xml:space="preserve" class="standard-text"><tspan x="42" y="575">Convertible To</tspan><tspan x="457" y="575" text-anchor="end">',
DecimalString.toDecimalString(
FullMath.mulDiv(
position_.remainingDeposit,
10 ** OHM_DECIMALS,
position_.conversionPrice
),
OHM_DECIMALS,
DISPLAY_DECIMALS
),
" OHM</tspan></text>"
)
: "",
'<defs><style type="text/css">.heading{fill:#F8CC82;font-family:"Helvetica Neue",Helvetica,-apple-system,BlinkMacSystemFont,Ubuntu,Jost,"DM Sans",sans-serif;font-size:24px;font-weight:500;letter-spacing:0em;white-space:pre;}.standard-text{fill:#EEE9E2;font-family:"Helvetica Neue",Helvetica,-apple-system,BlinkMacSystemFont,Ubuntu,Jost,"DM Sans",sans-serif;font-size:15px;font-weight:500;letter-spacing:0em;white-space:pre;}</style></defs></svg>'
);
}
// ========== ERC165 SUPPORT ========== //
function supportsInterface(bytes4 interfaceId_) external pure returns (bool) {
return
interfaceId_ == type(IERC165).interfaceId ||
interfaceId_ == type(IPositionTokenRenderer).interfaceId;
}
}
/// forge-lint: disable-end(mixed-case-function)
"
},
"src/modules/DEPOS/IPositionTokenRenderer.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;
/// @title IPositionTokenRenderer
/// @notice Interface for a contract that can render token URIs for deposit positions
interface IPositionTokenRenderer {
// ========== ERRORS ========== //
error PositionTokenRenderer_InvalidAddress();
// ========== FUNCTIONS ========== //
/// @notice Renders the token URI for a given position
/// @dev This function should return a valid JSON metadata string that follows the ERC721 metadata standard
///
/// @param positionManager_ The address of the position manager contract
/// @param positionId_ The ID of the position to render
/// @return uri The token URI as a string
function tokenURI(
address positionManager_,
uint256 positionId_
) external view returns (string memory uri);
}
"
},
"src/modules/DEPOS/IDepositPositionManager.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;
/// @title IDepositPositionManager
/// @notice This interface defines the functions for the DEPOS module.
/// The objective of this module is to track the terms of a deposit position.
interface IDepositPositionManager {
// ========== DATA STRUCTURES ========== //
/// @notice Data structure for the terms of a deposit position
///
/// @param operator Address of the operator/creator of the position
/// @param owner Address of the owner of the position
/// @param asset Address of the asset
/// @param periodMonths The period of the deposit
/// @param remainingDeposit Amount of reserve tokens remaining to be converted
/// @param conversionPrice The amount of asset tokens required to receive 1 OHM (scale: asset token decimals)
/// @param expiry Timestamp of the position expiry
/// @param wrapped Whether the term is wrapped
/// @param additionalData Additional data for the position
struct Position {
address operator;
address owner;
address asset;
uint8 periodMonths;
uint256 remainingDeposit;
uint256 conversionPrice;
uint48 expiry;
bool wrapped;
bytes additionalData;
}
/// @notice Parameters for the {mint} function
///
/// @param owner Address of the owner of the position
/// @param asset Address of the asset
/// @param periodMonths The period of the deposit
/// @param remainingDeposit Amount of reserve tokens remaining to be converted
/// @param conversionPrice The amount of asset tokens required to receive 1 OHM (scale: asset token decimals)
/// @param expiry Timestamp of the position expiry
/// @param wrapPosition Whether the position should be wrapped
/// @param additionalData Additional data for the position
struct MintParams {
address owner;
address asset;
uint8 periodMonths;
uint256 remainingDeposit;
uint256 conversionPrice;
uint48 expiry;
bool wrapPosition;
bytes additionalData;
}
// ========== EVENTS ========== //
/// @notice Emitted when a position is created
event PositionCreated(
uint256 indexed positionId,
address indexed owner,
address indexed asset,
uint8 periodMonths,
uint256 remainingDeposit,
uint256 conversionPrice,
uint48 expiry,
bool wrapped
);
/// @notice Emitted when a position's remaining deposit is updated
event PositionRemainingDepositUpdated(uint256 indexed positionId, uint256 remainingDeposit);
/// @notice Emitted when a position's additional data is updated
event PositionAdditionalDataUpdated(uint256 indexed positionId, bytes additionalData);
/// @notice Emitted when a position is split
event PositionSplit(
uint256 indexed positionId,
uint256 indexed newPositionId,
address indexed asset,
uint8 periodMonths,
uint256 amount,
address to,
bool wrap
);
/// @notice Emitted when a position is wrapped
event PositionWrapped(uint256 indexed positionId);
/// @notice Emitted when a position is unwrapped
event PositionUnwrapped(uint256 indexed positionId);
/// @notice Emitted when the token renderer is set
event TokenRendererSet(address indexed renderer);
// ========== ERRORS ========== //
/// @notice Error thrown when the caller is not the owner of the position
error DEPOS_NotOwner(uint256 positionId_);
/// @notice Error thrown when the caller is not the operator of the position
error DEPOS_NotOperator(uint256 positionId_);
/// @notice Error thrown when an invalid position ID is provided
error DEPOS_InvalidPositionId(uint256 id_);
/// @notice Error thrown when a position has already been wrapped
error DEPOS_AlreadyWrapped(uint256 positionId_);
/// @notice Error thrown when a position has not been wrapped
error DEPOS_NotWrapped(uint256 positionId_);
/// @notice Error thrown when an invalid parameter is provided
error DEPOS_InvalidParams(string reason_);
/// @notice Error thrown when a position does not support conversion
error DEPOS_NotConvertible(uint256 positionId_);
/// @notice Error thrown when the renderer contract does not implement the required interface
error DEPOS_InvalidRenderer(address renderer_);
// ========== WRAPPING ========== //
/// @notice Wraps a position into an ERC721 token
/// This is useful if the position owner wants a tokenized representation of their position. It is functionally equivalent to the position itself.
///
/// @dev The implementing function should do the following:
/// - Validate that the caller is the owner of the position
/// - Validate that the position is not already wrapped
/// - Mint an ERC721 token to the position owner
///
/// @param positionId_ The ID of the position to wrap
function wrap(uint256 positionId_) external;
/// @notice Unwraps/burns an ERC721 position token
/// This is useful if the position owner wants to unwrap their token back into the position.
///
/// @dev The implementing function should do the following:
/// - Validate that the caller is the owner of the position
/// - Validate that the position is already wrapped
/// - Burn the ERC721 token
///
/// @param positionId_ The ID of the position to unwrap
function unwrap(uint256 positionId_) external;
// ========== POSITION MANAGEMENT =========== //
/// @notice Creates a new deposit position
/// @dev The implementing function should do the following:
/// - Validate that the caller is permissioned
/// - Validate that the owner is not the zero address
/// - Validate that the convertible deposit token is not the zero address
/// - Validate that the remaining deposit is greater than 0
/// - Validate that the conversion price is greater than 0
/// - Validate that the expiry is in the future
/// - Create the position record
/// - Wrap the position if requested
///
/// @param params_ The parameters for the position creation
/// @return _positionId The ID of the new position
function mint(MintParams calldata params_) external returns (uint256 _positionId);
/// @notice Updates the remaining deposit of a position
/// @dev The implementing function should do the following:
/// - Validate that the caller is permissioned
/// - Validate that the position ID is valid
/// - Update the remaining deposit of the position
///
/// @param positionId_ The ID of the position to update
/// @param amount_ The new amount of the position
function setRemainingDeposit(uint256 positionId_, uint256 amount_) external;
/// @notice Updates the additional data of a position
/// @dev The implementing function should do the following:
/// - Validate that the caller is permissioned
/// - Validate that the position ID is valid
/// - Update the additional data of the position
///
/// @param positionId_ The ID of the position to update
/// @param additionalData_ The new additional data of the position
function setAdditionalData(uint256 positionId_, bytes calldata additionalData_) external;
/// @notice Splits the specified amount of the position into a new position
/// This is useful if the position owner wants to split their position into multiple smaller positions.
/// @dev The implementing function should do the following:
/// - Validate that the caller is the owner of the position
/// - Validate that the amount is greater than 0
/// - Validate that the amount is less than or equal to the remaining deposit
/// - Validate that `to_` is not the zero address
/// - Update the remaining deposit of the original position
/// - Create the new position record
/// - Wrap the new position if requested
///
/// @param positionId_ The ID of the position to split
/// @param amount_ The amount of the position to split
/// @param to_ The address to split the position to
/// @param wrap_ Whether the new position should be wrapped
/// @return newPositionId The ID of the new position
function split(
uint256 positionId_,
uint256 amount_,
address to_,
bool wrap_
) external returns (uint256 newPositionId);
// ========== POSITION INFORMATION ========== //
/// @notice Get the total number of positions
///
/// @return _count The total number of positions
function getPositionCount() external view returns (uint256 _count);
/// @notice Get the IDs of all positions for a given user
///
/// @param user_ The address of the user
/// @return _positionIds An array of position IDs
function getUserPositionIds(
address user_
) external view returns (uint256[] memory _positionIds);
/// @notice Get the position for a given ID
///
/// @param positionId_ The ID of the position
/// @return _position The position for the given ID
function getPosition(uint256 positionId_) external view returns (Position memory _position);
/// @notice Check if a position is expired
///
/// @param positionId_ The ID of the position
/// @return _expired Whether the position is expired
function isExpired(uint256 positionId_) external view returns (bool _expired);
/// @notice Check if a position is convertible
///
/// @param positionId_ The ID of the position
/// @return _convertible Whether the position is convertible
function isConvertible(uint256 positionId_) external view returns (bool _convertible);
/// @notice Preview the amount of OHM that would be received for a given amount of convertible deposit tokens
///
/// @param positionId_ The ID of the position
/// @param amount_ The amount of convertible deposit tokens to convert
/// @return _ohmOut The amount of OHM that would be received
function previewConvert(
uint256 positionId_,
uint256 amount_
) external view returns (uint256 _ohmOut);
// ========== TOKEN URI RENDERER ========== //
/// @notice Set the token renderer contract
/// @dev The implementing function should do the following:
/// - Validate that the caller is permissioned
/// - Validate that the renderer contract implements the required interface
/// - Set the renderer contract
/// - Emit an event
///
/// @param renderer_ The address of the renderer contract
function setTokenRenderer(address renderer_) external;
/// @notice Get the current token renderer contract
///
/// @return _renderer The address of the current renderer contract (or zero address if not set)
function getTokenRenderer() external view returns (address _renderer);
}
"
},
"dependencies/openzeppelin-new-5.3.0/contracts/utils/introspection/IERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
},
"dependencies/solmate-6.2.0/src/tokens/ERC20.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
"
},
"dependencies/openzeppelin-new-5.3.0/contracts/utils/Strings.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Strings.sol)
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SafeCast} from "./math/SafeCast.sol";
import {SignedMath} from "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
using SafeCast for *;
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
uint256 private constant SPECIAL_CHARS_LOOKUP =
(1 << 0x08) | // backspace
(1 << 0x09) | // tab
(1 << 0x0a) | // newline
(1 << 0x0c) | // form feed
(1 << 0x0d) | // carriage return
(1 << 0x22) | // double quote
(1 << 0x5c); // backslash
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @dev The string being parsed contains characters that are not in scope of the given base.
*/
error StringsInvalidChar();
/**
* @dev The string being parsed is not a properly formatted address.
*/
error StringsInvalidAddressFormat();
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
assembly ("memory-safe") {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
assembly ("memory-safe") {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
* representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal
* representation, according to EIP-55.
*/
function toChecksumHexString(address addr) internal pure returns (string memory) {
bytes memory buffer = bytes(toHexString(addr));
// hash the hex part of buffer (skip length + 2 bytes, length 40)
uint256 hashValue;
assembly ("memory-safe") {
hashValue := shr(96, keccak256(add(buffer, 0x22), 40))
}
for (uint256 i = 41; i > 1; --i) {
// possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)
if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {
// case shift by xoring with 0x20
buffer[i] ^= 0x20;
}
hashValue >>= 4;
}
return string(buffer);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
/**
* @dev Parse a decimal string and returns the value as a `uint256`.
*
* Requirements:
* - The string must be formatted as `[0-9]*`
* - The result must fit into an `uint256` type
*/
function parseUint(string memory input) internal pure returns (uint256) {
return parseUint(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseUint-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `[0-9]*`
* - The result must fit into an `uint256` type
*/
function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
(bool success, uint256 value) = tryParseUint(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid
* character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
uint256 result = 0;
for (uint256 i = begin; i < end; ++i) {
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
if (chr > 9) return (false, 0);
result *= 10;
result += chr;
}
return (true, result);
}
/**
* @dev Parse a decimal string and returns the value as a `int256`.
*
* Requirements:
* - The string must be formatted as `[-+]?[0-9]*`
* - The result must fit in an `int256` type.
*/
function parseInt(string memory input) internal pure returns (int256) {
return parseInt(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `[-+]?[0-9]*`
* - The result must fit in an `int256` type.
*/
function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {
(bool success, int256 value) = tryParseInt(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if
* the result does not fit in a `int256`.
*
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);
}
uint256 private constant ABS_MIN_INT256 = 2 ** 255;
/**
* @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid
* character or if the result does not fit in a `int256`.
*
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, int256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseIntUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseInt-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseIntUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, int256 value) {
bytes memory buffer = bytes(input);
// Check presence of a negative sign.
bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
bool positiveSign = sign == bytes1("+");
bool negativeSign = sign == bytes1("-");
uint256 offset = (positiveSign || negativeSign).toUint();
(bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end);
if (absSuccess && absValue < ABS_MIN_INT256) {
return (true, negativeSign ? -int256(absValue) : int256(absValue));
} else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {
return (true, type(int256).min);
} else return (false, 0);
}
/**
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`.
*
* Requirements:
* - The string must be formatted as `(0x)?[0-9a-fA-F]*`
* - The result must fit in an `uint256` type.
*/
function parseHexUint(string memory input) internal pure returns (uint256) {
return parseHexUint(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseHexUint-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `(0x)?[0-9a-fA-F]*`
* - The result must fit in an `uint256` type.
*/
function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
(bool success, uint256 value) = tryParseHexUint(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an
* invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseHexUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseHexUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseHexUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
// skip 0x prefix if present
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
uint256 offset = hasPrefix.toUint() * 2;
uint256 result = 0;
for (uint256 i = begin + offset; i < end; ++i) {
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
if (chr > 15) return (false, 0);
result *= 16;
unchecked {
// Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check).
// This guarantees that adding a value < 16 will not cause an overflow, hence the unchecked.
result += chr;
}
}
return (true, result);
}
/**
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`.
*
* Requirements:
* - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`
*/
function parseAddress(string memory input) internal pure returns (address) {
return parseAddress(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseAddress-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`
*/
function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {
(bool success, address value) = tryParseAddress(input, begin, end);
if (!success) revert StringsInvalidAddressFormat();
return value;
}
/**
* @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly
* formatted address. See {parseAddress-string} requirements.
*/
function tryParseAddress(string memory input) internal pure returns (bool success, address value) {
return tryParseAddress(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly
* formatted address. See {parseAddress-string-uint256-uint256} requirements.
*/
function tryParseAddress(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, address value) {
if (end > bytes(input).length || begin > end) return (false, address(0));
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
uint256 expectedLength = 40 + hasPrefix.toUint() * 2;
// check that input is the correct length
if (end - begin == expectedLength) {
// length guarantees that this does not overflow, and value is at most type(uint160).max
(bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);
return (s, address(uint160(v)));
} else {
return (false, address(0));
}
}
function _tryParseChr(bytes1 chr) private pure returns (uint8) {
uint8 value = uint8(chr);
// Try to parse `chr`:
// - Case 1: [0-9]
// - Case 2: [a-f]
// - Case 3: [A-F]
// - otherwise not supported
unchecked {
if (value > 47 && value < 58) value -= 48;
else if (value > 96 && value < 103) value -= 87;
else if (value > 64 && value < 71) value -= 55;
else return type(uint8).max;
}
return value;
}
/**
* @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata.
*
* WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped.
*
* NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of
* RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode
* characters that are not in this range, but other tooling may provide different results.
*/
function escapeJSON(string memory input) internal pure returns (string memory) {
bytes memory buffer = bytes(input);
bytes memory output = new bytes(2 * buffer.length); // worst case scenario
uint256 outputLength = 0;
for (uint256 i; i < buffer.length; ++i) {
bytes1 char = bytes1(_unsafeReadBytesOffset(buffer, i));
if (((SPECIAL_CHARS_LOOKUP & (1 << uint8(char))) != 0)) {
output[outputLength++] = "\\";
if (char == 0x08) output[outputLength++] = "b";
else if (char == 0x09) output[outputLength++] = "t";
else if (char == 0x0a) output[outputLength++] = "n";
else if (char == 0x0c) output[outputLength++] = "f";
else if (char == 0x0d) output[outputLength++] = "r";
else if (char == 0x5c) output[outputLength++] = "\\";
else if (char == 0x22) {
// solhint-disable-next-line quotes
output[outputLength++] = '"';
}
} else {
output[outputLength++] = char;
}
}
// write the actual length and deallocate unused memory
assembly ("memory-safe") {
mstore(output, outputLength)
mstore(0x40, add(output, shl(5, shr(5, add(outputLength, 63)))))
}
return string(output);
}
/**
* @dev Reads a bytes32 from a bytes array without bounds checking.
*
* NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
* assembly block as such would prevent some optimizations.
*/
function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {
// This is not memory safe in the general case, but all calls to this private function are within bounds.
assembly ("memory-safe") {
value := mload(add(buffer, add(0x20, offset)))
}
}
}
"
},
"dependencies/openzeppelin-new-5.3.0/contracts/utils/Base64.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Base64.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides a set of functions to operate with Base64 strings.
*/
library Base64 {
/**
* @dev Base64 Encoding/Decoding Table
* See sections 4 and 5 of https://datatracker.ietf.org/doc/html/rfc4648
*/
string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
string internal constant _TABLE_URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
/**
* @dev Converts a `bytes` to its Bytes64 `string` representation.
*/
function encode(bytes memory data) internal pure returns (string memory) {
return _encode(data, _TABLE, true);
}
/**
* @dev Converts a `bytes` to its Bytes64Url `string` representation.
* Output is not padded with `=` as specified in https://www.rfc-editor.org/rfc/rfc4648[rfc4648].
*/
function encodeURL(bytes memory data) internal pure returns (string memory) {
return _encode(data, _TABLE_URL, false);
}
/**
* @dev Internal table-agnostic conversion
*/
function _encode(bytes memory data, string memory table, bool withPadding) private pure returns (string memory) {
/**
* Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
* https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
*/
if (data.length == 0) return "";
// If padding is enabled, the final length should be `bytes` data length divided by 3 rounded up and then
// multiplied by 4 so that it leaves room for padding the last chunk
// - `data.length + 2` -> Prepare for division rounding up
// - `/ 3` -> Number of 3-bytes chunks (rounded up)
// - `4 *` -> 4 characters for each chunk
// This is equivalent to: 4 * Math.ceil(data.length / 3)
//
// If padding is disabled, the final length should be `bytes` data length multiplied by 4/3 rounded up as
// opposed to when padding is required to fill the last chunk.
// - `4 * data.length` -> 4 characters for each chunk
// - ` + 2` -> Prepare for division rounding up
// - `/ 3` -> Number of 3-bytes chunks (rounded up)
// This is equivalent to: Math.ceil((4 * data.length) / 3)
uint256 resultLength = withPadding ? 4 * ((data.length + 2) / 3) : (4 * data.length + 2) / 3;
string memory result = new string(resultLength);
assembly ("memory-safe") {
// Prepare the lookup table (skip the first "length" byte)
let tablePtr := add(table, 1)
// Prepare result pointer, jump over length
let resultPtr := add(result, 0x20)
let dataPtr := data
let endPtr := add(data, mload(data))
// In some cases, the last iteration will read bytes after the end of the data. We cache the value, and
// set it to zero to make sure no dirty bytes are read in that section.
let afterPtr := add(endPtr, 0x20)
let afterCache := mload(afterPtr)
mstore(afterPtr, 0x00)
// Run over the input, 3 bytes at a time
for {
} lt(dataPtr, endPtr) {
} {
// Advance 3 bytes
dataPtr := add(dataPtr, 3)
let input := mload(dataPtr)
// To write each character, shift the 3 byte (24 bits) chunk
// 4 times in blocks of 6 bits for each character (18, 12, 6, 0)
// and apply logical AND with 0x3F to bitmask the least significant 6 bits.
// Use this as an index into the lookup table, mload an entire word
// so the desired character is in the least significant byte, and
// mstore8 this least significant byte into the result and continue.
mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
}
// Reset the value that was cached
mstore(afterPtr, afterCache)
if withPadding {
// When data `bytes` is not exactly 3 bytes long
// it is padded with `=` characters at the end
switch mod(mload(data), 3)
case 1 {
mstore8(sub(resultPtr, 1), 0x3d)
mstore8(sub(resultPtr, 2), 0x3d)
}
case 2 {
mstore8(sub(resultPtr, 1), 0x3d)
}
}
}
return result;
}
}
"
},
"src/libraries/Timestamp.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;
import {uint2str} from "./Uint2Str.sol";
library Timestamp {
/// @notice Convert a timestamp to a padded string of the form "YYYY-MM-DD"
/// @dev This has only been tested up to year 2345.
///
/// @param timestamp The timestamp to convert
/// @return year Year as a zero-padded string
/// @return month Month as a zero-padded string
/// @return day Day as a zero-padded string
function toPaddedString(
uint48 timestamp
) internal pure returns (string memory, string memory, string memory) {
// Convert a number of days into a human-readable date, courtesy of BokkyPooBah.
// Source: https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary/blob/master/contracts/BokkyPooBahsDateTimeLibrary.sol
uint256 year;
uint256 month;
uint256 day;
{
int256 __days = int256(uint256(timestamp) / 1 days);
int256 num1 = __days + 68_569 + 2_440_588; // 2440588 = OFFSET19700101
int256 num2 = (4 * num1) / 146_097;
num1 = num1 - (146_097 * num2 + 3) / 4;
int256 _year = (4000 * (num1 + 1)) / 1_461_001;
num1 = num1 - (1461 * _year) / 4 + 31;
int256 _month = (80 * num1) / 2447;
int256 _day = num1 - (2447 * _month) / 80;
num1 = _month / 11;
_month = _month + 2 - 12 * num1;
_year = 100 * (num2 - 49) + _year + num1;
/// forge-lint: disable-start(unsafe-typecast)
year = uint256(_year);
month = uint256(_month);
day = uint256(_day);
/// forge-lint: disable-end(unsafe-typecast)
}
string memory yearStr = uint2str(year % 10_000);
string memory monthStr = month < 10
? string(abi.encodePacked("0", uint2str(month)))
: uint2str(month);
string memory dayStr = day < 10
? string(abi.encodePacked("0", uint2str(day)))
: uint2str(day);
return (yearStr, monthStr, dayStr);
}
}
"
},
"src/libraries/DecimalString.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8;
import {uint2str} from "./Uint2Str.sol";
library DecimalString {
/// @notice Converts a uint256 value to a string with a specified number of decimal places.
/// The value is adjusted by the scale factor and then formatted to the specified number of decimal places.
/// The decimal places are not zero-padded, so the result is not always the same length.
/// @dev This is inspired by code in [FixedStrikeOptionTeller](https://github.com/Bond-Protocol/option-contracts/blob/b8ce2ca2bae3bd06f0e7665c3aa8d827e4d8ca2c/src/fixed-strike/FixedStrikeOptionTeller.sol#L722).
///
/// @param value_ The uint256 value to convert to a string.
/// @param valueDecimals_ The scale factor of the value.
/// @param decimalPlaces_ The number of decimal places to format the value to.
/// @return result A string representation of the value with the specified number of decimal places.
function toDecimalString(
uint256 value_,
uint8 valueDecimals_,
uint8 decimalPlaces_
) internal pure returns (string memory) {
// Handle zero case
if (value_ == 0) return "0";
// Convert the entire number to string first
string memory str = uint2str(value_);
bytes memory bStr = bytes(str);
// If no decimal places requested, just handle the scaling and return
if (decimalPlaces_ == 0) {
if (bStr.length <= valueDecimals_) return "0";
return uint2str(value_ / (10 ** valueDecimals_));
}
// If value is a whole number, return as-is
if (valueDecimals_ == 0) return str;
// Calculate decimal places to show (limited by request and available decimals)
uint256 maxDecimalPlaces = valueDecimals_ > decimalPlaces_
? decimalPlaces_
: valueDecimals_;
// Handle numbers smaller than 1
if (bStr.length <= valueDecimals_) {
bytes memory smallResult = new bytes(2 + maxDecimalPlaces);
smallResult[0] = "0";
smallResult[1] = ".";
uint256 leadingZeros = valueDecimals_ - bStr.length;
uint256 zerosToAdd = leadingZeros > maxDecimalPlaces ? maxDecimalPlaces : leadingZeros;
// Add leading zeros after decimal
for (uint256 i = 0; i < zerosToAdd; i++) {
smallResult[i + 2] = "0";
}
// Add available digits
for (uint256 i = 0; i < maxDecimalPlaces - zerosToAdd && i < bStr.length; i++) {
smallResult[i + 2 + zerosToAdd] = bStr[i];
}
return string(smallResult);
}
// Find decimal position and last significant digit
uint256 decimalPosition = bStr.length - valueDecimals_;
uint256 lastNonZeroPos = decimalPosition;
for (uint256 i = 0; i < maxDecimalPlaces && i + decimalPosition < bStr.length; i++) {
if (bStr[decimalPosition + i] != "0") {
lastNonZeroPos = decimalPosition + i + 1;
}
}
// Create and populate result
bytes memory finalResult = new bytes(
lastNonZeroPos - decimalPosition > 0 ? lastNonZeroPos + 1 : lastNonZeroPos
);
for (uint256 i = 0; i < decimalPosition; i++) {
finalResult[i] = bStr[i];
}
if (lastNonZeroPos > decimalPosition) {
finalResult[decimalPosition] = ".";
for (uint256 i = 0; i < lastNonZeroPos - decimalPosition; i++) {
finalResult[decimalPosition + 1 + i] = bStr[decimalPosition + i];
}
}
return string(finalResult);
}
}
"
},
"src/libraries/FullMath.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
function mulDiv(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = a * b
// Compute the product mod 2**256 and mod 2**256 - 1
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2**256 + prod0
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(a, b, not(0))
prod0 := mul(a, b)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division
if (prod1 == 0) {
require(denominator > 0);
assembly {
result := div(prod0, denominator)
}
return result;
}
// Make sure the result is less than 2**256.
// Also prevents denominator == 0
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0]
// Compute remainder using mulmod
uint256 remainder;
assembly {
remainder := mulmod(a, b, denominator)
}
// Subtract 256 bit number from 512 bit number
assembly {
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator
// Compute largest power of two divisor of denominator.
// Always >= 1.
uint256 twos = (type(uint256).max - denominator + 1) & denominator;
// Divide denominator by power of two
assembly {
denominator := div(denominator, twos)
}
// Divide [prod1 prod0] by the factors of two
assembly {
prod0 := div(prod0, twos)
}
// Shift
Submitted on: 2025-11-07 16:14:27
Comments
Log in to comment.
No comments yet.