Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convertible Deposits #29

Draft
wants to merge 65 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
053cb32
Create CDsEmissions.sol
chefomi Nov 29, 2024
56b8f3b
Update CDsEmissions.sol
chefomi Nov 29, 2024
49ecd6d
Auctioneer + Facility
chefomi Dec 4, 2024
356af03
updates
chefomi Dec 6, 2024
4d55f12
integrate with emissions manager
chefomi Dec 7, 2024
61a7377
Fix compilation errors
0xJem Dec 9, 2024
9068ba0
chore: linting
0xJem Dec 9, 2024
e41276b
Add custom error to contracts. Add missing CDFacility parameter to CD…
0xJem Dec 9, 2024
74ebfa4
Extract CDAuctioneer into interface
0xJem Dec 9, 2024
fb68663
Fix compilation errors for EmissionManager test
0xJem Dec 9, 2024
2f2a0de
Add cdUSDS parameter to CDFacility. Extract cdUSDS into interface.
0xJem Dec 9, 2024
4797b13
Fix naming consistency with debt/deposit. Add missing parameter to de…
0xJem Dec 13, 2024
a6dacc8
Create interface for convertible deposit facility
0xJem Dec 13, 2024
6291399
Fix compile errors
0xJem Dec 13, 2024
55397f8
Fix licenses
0xJem Dec 13, 2024
7953b93
Interface for a proposed CDEPO module
0xJem Dec 13, 2024
43ca59c
Update description
0xJem Dec 13, 2024
c3a7ee8
Add module for convertible deposit terms
0xJem Dec 16, 2024
349c671
Add CTERM events
0xJem Dec 16, 2024
6b20f80
WIP implementation of CDEPO
0xJem Dec 16, 2024
5536d54
First pass a CDEPO
0xJem Dec 16, 2024
9fb539c
First pass at implementation of CTERM
0xJem Dec 16, 2024
e3dcecc
Add a permissioned function to CDEPO to redeem CD tokens for assets. …
0xJem Dec 16, 2024
42a6d68
Rename CTERM to CDPOS
0xJem Dec 16, 2024
be52b09
Shift CDAuctioneer to use new CDFacility interface
0xJem Dec 16, 2024
2c28d33
Check CD token before converting/reclaiming using a CD position
0xJem Dec 17, 2024
f1dbb75
First pass at SVG for CD positions
0xJem Dec 17, 2024
6b9888f
Extract previewBid functionality
0xJem Dec 17, 2024
fddaa3b
Docs
0xJem Dec 17, 2024
e3ad735
Add isExpired getter to CDPOS
0xJem Dec 18, 2024
77e2787
Adjust CDPOS SVG
0xJem Dec 18, 2024
abf0b3c
Wrote helper function to convert a fixed-point integer into a decimal…
0xJem Dec 18, 2024
7ba544a
Add conversion price and remaining deposit to the SVG and traits
0xJem Dec 18, 2024
724b2b5
Add setter
0xJem Dec 18, 2024
51648f0
Docs
0xJem Dec 18, 2024
1719af0
Add previewConvert to CDPOS
0xJem Dec 18, 2024
4be3322
Add stubs for CDPOS tests
0xJem Dec 18, 2024
497ed1c
Add owner back to position struct
0xJem Dec 18, 2024
f293e5e
Implement tests for CDPOS.create()
0xJem Dec 18, 2024
6571d51
CDPOS tests for create/update/split
0xJem Dec 19, 2024
6c4fcac
CDPOS tests for wrap/unwrap
0xJem Dec 19, 2024
95ad4be
Clarify decimal scale in docs, tests for previewConvert
0xJem Dec 19, 2024
0520eba
Tests for setDisplayDecimals and transferFrom
0xJem Dec 19, 2024
d8d89bf
Handling of transferFrom in CDPOS
0xJem Dec 19, 2024
27a3695
Fix handling of mint checks
0xJem Dec 19, 2024
875cdd9
Remove redundant tests
0xJem Dec 19, 2024
dbd3f6e
CDEPO: rename burn() to reclaim() and adjust logic around spending to…
0xJem Dec 19, 2024
fcd4e3d
CDEPO: test stubs
0xJem Dec 19, 2024
908a641
CDEPO: tests for mint/mintTo/previewMint, rename burn rate to reclaim…
0xJem Dec 19, 2024
fb9bf7e
CDEPO: setReclaimRate tests
0xJem Dec 19, 2024
56cabb2
CDEPO: mint test fixes
0xJem Dec 19, 2024
85e73e9
CDEPO: more tests
0xJem Dec 19, 2024
e73bf8e
CDEPO: more tests, use safeTransfer/safeApprove, clarify allowance
0xJem Dec 20, 2024
d9b3679
CDEPO: tests and impl fixes for rounding, redeem tests
0xJem Dec 20, 2024
4bbde8a
CDEPO: sweepYield tests
0xJem Dec 20, 2024
e98f502
CDFacility: test stubs
0xJem Dec 20, 2024
c4f56c2
CDEPO: add mint/reclaim/redeem -For functions, add spending allowance…
0xJem Dec 20, 2024
2aca51a
CDFacility: test updates
0xJem Dec 20, 2024
0cf6892
Add preview functions to CD facility
0xJem Dec 23, 2024
21580db
CD Facility: role must be lowercase. Add previewConvert and previewRe…
0xJem Dec 23, 2024
3fb895c
CDFacility: convert tests
0xJem Dec 23, 2024
1820d39
CDFacility: more comprehensive tests
0xJem Dec 23, 2024
34307b4
CDFacility: better handling of ownership check
0xJem Dec 23, 2024
e9146a3
CDFacility: WIP previewReclaim tests
0xJem Dec 23, 2024
20571b2
CDFacility: tests for convert, reclaim
0xJem Dec 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions src/libraries/DecimalString.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8;

import {uint2str} from "./Uint2Str.sol";
import {console2} from "forge-std/console2.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);
}
}
45 changes: 45 additions & 0 deletions src/libraries/Timestamp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import {uint2str} from "./Uint2Str.sol";

library Timestamp {
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(int48(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;

year = uint256(_year);
month = uint256(_month);
day = uint256(_day);
}

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);
}
}
27 changes: 27 additions & 0 deletions src/libraries/Uint2Str.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8;

// Some fancy math to convert a uint into a string, courtesy of Provable Things.
// Updated to work with solc 0.8.0.
// https://github.com/provable-things/ethereum-api/blob/master/provableAPI_0.6.sol
function uint2str(uint256 _i) pure returns (string memory) {
if (_i == 0) {
return "0";
}
uint256 j = _i;
uint256 len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint256 k = len;
while (_i != 0) {
k = k - 1;
uint8 temp = (48 + uint8(_i - (_i / 10) * 10));
bytes1 b1 = bytes1(temp);
bstr[k] = b1;
_i /= 10;
}
return string(bstr);
}
187 changes: 187 additions & 0 deletions src/modules/CDEPO/CDEPO.v1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.15;

import {Kernel, Module} from "src/Kernel.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {ERC4626} from "solmate/mixins/ERC4626.sol";

/// @title CDEPOv1
/// @notice This is a base contract for a custodial convertible deposit token. It is designed to be used in conjunction with an ERC4626 vault.
abstract contract CDEPOv1 is Module, ERC20 {
// ========== EVENTS ========== //

/// @notice Emitted when the reclaim rate is updated
event ReclaimRateUpdated(uint16 newReclaimRate);

/// @notice Emitted when the yield is swept
event YieldSwept(address receiver, uint256 reserveAmount, uint256 sReserveAmount);

// ========== ERRORS ========== //

/// @notice Thrown when the caller provides invalid arguments
error CDEPO_InvalidArgs(string reason);

// ========== CONSTANTS ========== //

/// @notice Equivalent to 100%
uint16 public constant ONE_HUNDRED_PERCENT = 100e2;

// ========== STATE VARIABLES ========== //

/// @notice The reclaim rate of the convertible deposit token
/// @dev A reclaim rate of 99e2 (99%) means that for every 100 convertible deposit tokens burned, 99 underlying asset tokens are returned
uint16 internal _reclaimRate;

/// @notice The total amount of vault shares in the contract
uint256 public totalShares;

// ========== ERC20 OVERRIDES ========== //

/// @notice Mint tokens to the caller in exchange for the underlying asset
/// @dev The implementing function should perform the following:
/// - Transfers the underlying asset from the caller to the contract
/// - Mints the corresponding amount of convertible deposit tokens to the caller
/// - Deposits the underlying asset into the ERC4626 vault
/// - Emits a `Transfer` event
///
/// @param amount_ The amount of underlying asset to transfer
function mint(uint256 amount_) external virtual;

/// @notice Mint tokens to `account_` in exchange for the underlying asset
/// This function behaves the same as `mint`, but allows the caller to
/// specify the address to mint the tokens to and pull the asset from.
/// The `account_` address must have approved the contract to spend the underlying asset.
/// @dev The implementing function should perform the following:
/// - Transfers the underlying asset from the `account_` address to the contract
/// - Mints the corresponding amount of convertible deposit tokens to the `account_` address
/// - Deposits the underlying asset into the ERC4626 vault
/// - Emits a `Transfer` event
///
/// @param account_ The address to mint the tokens to and pull the asset from
/// @param amount_ The amount of asset to transfer
function mintFor(address account_, uint256 amount_) external virtual;

/// @notice Preview the amount of convertible deposit tokens that would be minted for a given amount of underlying asset
/// @dev The implementing function should perform the following:
/// - Computes the amount of convertible deposit tokens that would be minted for the given amount of underlying asset
/// - Returns the computed amount
///
/// @param amount_ The amount of underlying asset to transfer
/// @return tokensOut The amount of convertible deposit tokens that would be minted
function previewMint(uint256 amount_) external view virtual returns (uint256 tokensOut);

/// @notice Burn tokens from the caller and reclaim the underlying asset
/// The amount of underlying asset may not be 1:1 with the amount of
/// convertible deposit tokens, depending on the value of `burnRate`
/// @dev The implementing function should perform the following:
/// - Withdraws the underlying asset from the ERC4626 vault
/// - Transfers the underlying asset to the caller
/// - Burns the corresponding amount of convertible deposit tokens from the caller
/// - Marks the forfeited amount of the underlying asset as yield
///
/// @param amount_ The amount of convertible deposit tokens to burn
function reclaim(uint256 amount_) external virtual;

/// @notice Burn tokens from `account_` and reclaim the underlying asset
/// This function behaves the same as `reclaim`, but allows the caller to
/// specify the address to burn the tokens from and transfer the underlying
/// asset to.
/// The `account_` address must have approved the contract to spend the convertible deposit tokens.
/// @dev The implementing function should perform the following:
/// - Validates that the `account_` address has approved the contract to spend the convertible deposit tokens
/// - Withdraws the underlying asset from the ERC4626 vault
/// - Transfers the underlying asset to the `account_` address
/// - Burns the corresponding amount of convertible deposit tokens from the `account_` address
/// - Marks the forfeited amount of the underlying asset as yield
///
/// @param account_ The address to burn the convertible deposit tokens from and transfer the underlying asset to
/// @param amount_ The amount of convertible deposit tokens to burn
function reclaimFor(address account_, uint256 amount_) external virtual;

/// @notice Preview the amount of underlying asset that would be reclaimed for a given amount of convertible deposit tokens
/// @dev The implementing function should perform the following:
/// - Computes the amount of underlying asset that would be returned for the given amount of convertible deposit tokens
/// - Returns the computed amount
///
/// @param amount_ The amount of convertible deposit tokens to burn
/// @return assetsOut The amount of underlying asset that would be reclaimed
function previewReclaim(uint256 amount_) external view virtual returns (uint256 assetsOut);

/// @notice Redeem convertible deposit tokens for the underlying asset
/// This differs from the reclaim function, in that it is an admin-level and permissioned function that does not apply the burn rate.
/// @dev The implementing function should perform the following:
/// - Validates that the caller is permissioned
/// - Transfers the corresponding underlying assets to the caller
/// - Burns the corresponding amount of convertible deposit tokens from the caller
///
/// @param amount_ The amount of convertible deposit tokens to burn
/// @return tokensOut The amount of underlying assets that were transferred to the caller
function redeem(uint256 amount_) external virtual returns (uint256 tokensOut);

/// @notice Redeem convertible deposit tokens for the underlying asset
/// This differs from the redeem function, in that it allows the caller to specify the address to burn the convertible deposit tokens from.
/// The `account_` address must have approved the contract to spend the convertible deposit tokens.
/// @dev The implementing function should perform the following:
/// - Validates that the caller is permissioned
/// - Validates that the `account_` address has approved the contract to spend the convertible deposit tokens
/// - Burns the corresponding amount of convertible deposit tokens from the `account_` address
/// - Transfers the corresponding underlying assets to the caller (not the `account_` address)
///
/// @param account_ The address to burn the convertible deposit tokens from
/// @param amount_ The amount of convertible deposit tokens to burn
/// @return tokensOut The amount of underlying assets that were transferred to the caller
function redeemFor(
address account_,
uint256 amount_
) external virtual returns (uint256 tokensOut);

// ========== YIELD MANAGER ========== //

/// @notice Claim the yield accrued on the reserve token
/// @dev The implementing function should perform the following:
/// - Validating that the caller has the correct role
/// - Withdrawing the yield from the sReserve token
/// - Transferring the yield to the caller
/// - Emitting an event
///
/// @param to_ The address to sweep the yield to
/// @return yieldReserve The amount of reserve token that was swept
/// @return yieldSReserve The amount of sReserve token that was swept
function sweepYield(
address to_
) external virtual returns (uint256 yieldReserve, uint256 yieldSReserve);

/// @notice Preview the amount of yield that would be swept
///
/// @return yieldReserve The amount of reserve token that would be swept
/// @return yieldSReserve The amount of sReserve token that would be swept
function previewSweepYield()
external
view
virtual
returns (uint256 yieldReserve, uint256 yieldSReserve);

// ========== ADMIN ========== //

/// @notice Set the reclaim rate of the convertible deposit token
/// @dev The implementing function should perform the following:
/// - Validating that the caller has the correct role
/// - Validating that the new rate is within bounds
/// - Setting the new reclaim rate
/// - Emitting an event
///
/// @param newReclaimRate_ The new reclaim rate
function setReclaimRate(uint16 newReclaimRate_) external virtual;

// ========== STATE VARIABLES ========== //

/// @notice The ERC4626 vault that holds the underlying asset
function vault() external view virtual returns (ERC4626);

/// @notice The underlying ERC20 asset
function asset() external view virtual returns (ERC20);

/// @notice The reclaim rate of the convertible deposit token
/// @dev A reclaim rate of 99e2 (99%) means that for every 100 convertible deposit tokens burned, 99 underlying asset tokens are returned
function reclaimRate() external view virtual returns (uint16);
}
Loading
Loading