Skip to content

Commit

Permalink
feat: add fractional redeems
Browse files Browse the repository at this point in the history
  • Loading branch information
rndquu committed Apr 5, 2024
1 parent 535dafb commit 8b3bfb1
Show file tree
Hide file tree
Showing 5 changed files with 299 additions and 11 deletions.
15 changes: 14 additions & 1 deletion packages/contracts/src/dollar/facets/UbiquityPoolFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ contract UbiquityPoolFacet is IUbiquityPool, Modifiers {
);
}

/// @inheritdoc IUbiquityPool
function getRedeemGovernanceBalance(
address userAddress
) external view returns (uint256) {
return LibUbiquityPool.getRedeemGovernanceBalance(userAddress);
}

/// @inheritdoc IUbiquityPool
function governanceEthPoolAddress() external view returns (address) {
return LibUbiquityPool.governanceEthPoolAddress();
Expand Down Expand Up @@ -145,12 +152,18 @@ contract UbiquityPoolFacet is IUbiquityPool, Modifiers {
function redeemDollar(
uint256 collateralIndex,
uint256 dollarAmount,
uint256 governanceOutMin,
uint256 collateralOutMin
) external nonReentrant returns (uint256 collateralOut) {
)
external
nonReentrant
returns (uint256 collateralOut, uint256 governanceOut)
{
return
LibUbiquityPool.redeemDollar(
collateralIndex,
dollarAmount,
governanceOutMin,
collateralOutMin
);
}
Expand Down
13 changes: 12 additions & 1 deletion packages/contracts/src/dollar/interfaces/IUbiquityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ interface IUbiquityPool {
uint256 collateralIndex
) external view returns (uint256);

/**
* @notice Returns user's Governance tokens balance available for redemption
* @param userAddress User address
* @return User's Governance tokens balance available for redemption
*/
function getRedeemGovernanceBalance(
address userAddress
) external view returns (uint256);

/**
* @notice Returns pool address for Governance/ETH pair
* @return Pool address
Expand Down Expand Up @@ -151,14 +160,16 @@ interface IUbiquityPool {
* @dev This is done in order to prevent someone using a flash loan of a collateral token to mint, redeem, and collect in a single transaction/block
* @param collateralIndex Collateral token index being withdrawn
* @param dollarAmount Amount of Ubiquity Dollars being burned
* @param governanceOutMin Minimum amount of Governance tokens that'll be withdrawn, used to set acceptable slippage
* @param collateralOutMin Minimum amount of collateral tokens that'll be withdrawn, used to set acceptable slippage
* @return collateralOut Amount of collateral tokens ready for redemption
*/
function redeemDollar(
uint256 collateralIndex,
uint256 dollarAmount,
uint256 governanceOutMin,
uint256 collateralOutMin
) external returns (uint256 collateralOut);
) external returns (uint256 collateralOut, uint256 governanceOut);

/**
* @notice Used to collect collateral tokens after redeeming/burning Ubiquity Dollars
Expand Down
71 changes: 64 additions & 7 deletions packages/contracts/src/dollar/libraries/LibUbiquityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,14 @@ library LibUbiquityPool {
uint256 redeemPriceThreshold;
// address -> collateral index -> balance
mapping(address user => mapping(uint256 collateralIndex => uint256 amount)) redeemCollateralBalances;
// address -> balance
mapping(address user => uint256 amount) redeemGovernanceBalances;
// number of blocks to wait before being able to collectRedemption()
uint256 redemptionDelayBlocks;
// collateral index -> balance
uint256[] unclaimedPoolCollateral;
// total amount of unclaimed Governance tokens in the pool
uint256 unclaimedPoolGovernance;
//================
// Fees related
//================
Expand Down Expand Up @@ -442,6 +446,18 @@ library LibUbiquityPool {
poolStorage.redeemCollateralBalances[userAddress][collateralIndex];
}

/**
* @notice Returns user's Governance tokens balance available for redemption
* @param userAddress User address
* @return User's Governance tokens balance available for redemption
*/
function getRedeemGovernanceBalance(
address userAddress
) internal view returns (uint256) {
UbiquityPoolStorage storage poolStorage = ubiquityPoolStorage();
return poolStorage.redeemGovernanceBalances[userAddress];
}

/**
* @notice Returns pool address for Governance/ETH pair
* @return Pool address
Expand Down Expand Up @@ -571,17 +587,19 @@ library LibUbiquityPool {
* @dev This is done in order to prevent someone using a flash loan of a collateral token to mint, redeem, and collect in a single transaction/block
* @param collateralIndex Collateral token index being withdrawn
* @param dollarAmount Amount of Ubiquity Dollars being burned
* @param governanceOutMin Minimum amount of Governance tokens that'll be withdrawn, used to set acceptable slippage
* @param collateralOutMin Minimum amount of collateral tokens that'll be withdrawn, used to set acceptable slippage
* @return collateralOut Amount of collateral tokens ready for redemption
*/
function redeemDollar(
uint256 collateralIndex,
uint256 dollarAmount,
uint256 governanceOutMin,
uint256 collateralOutMin
)
internal
collateralEnabled(collateralIndex)
returns (uint256 collateralOut)
returns (uint256 collateralOut, uint256 governanceOut)
{
UbiquityPoolStorage storage poolStorage = ubiquityPoolStorage();

Expand All @@ -607,8 +625,33 @@ library LibUbiquityPool {
// update collateral price
updateChainLinkCollateralPrice(collateralIndex);

// get collateral output for incoming Dollars
collateralOut = getDollarInCollateral(collateralIndex, dollarAfterFee);
// get current collateral ratio
uint256 currentCollateralRatio = poolStorage.collateralRatio;

// fully collateralized
if (currentCollateralRatio >= UBIQUITY_POOL_PRICE_PRECISION) {
// get collateral output for incoming Dollars
collateralOut = getDollarInCollateral(
collateralIndex,
dollarAfterFee
);
governanceOut = 0;
} else if (currentCollateralRatio == 0) {
// algorithmic, fully covered by Governance tokens
collateralOut = 0;
governanceOut = dollarAfterFee
.mul(UBIQUITY_POOL_PRICE_PRECISION)
.div(getGovernancePriceUsd());
} else {
// fractional, partially covered by collateral and Governance tokens
collateralOut = getDollarInCollateral(
collateralIndex,
dollarAfterFee
).mul(currentCollateralRatio).div(UBIQUITY_POOL_PRICE_PRECISION);
governanceOut = dollarAfterFee
.mul(UBIQUITY_POOL_PRICE_PRECISION.sub(currentCollateralRatio))
.div(getGovernancePriceUsd());
}

// checks
require(
Expand All @@ -619,8 +662,9 @@ library LibUbiquityPool {
"Insufficient pool collateral"
);
require(collateralOut >= collateralOutMin, "Collateral slippage");
require(governanceOut >= governanceOutMin, "Governance slippage");

// account for the redeem delay
// increase collateral redemption balances
poolStorage.redeemCollateralBalances[msg.sender][
collateralIndex
] = poolStorage
Expand All @@ -631,13 +675,26 @@ library LibUbiquityPool {
.unclaimedPoolCollateral[collateralIndex]
.add(collateralOut);

// increase Governance redemption balances
poolStorage.redeemGovernanceBalances[msg.sender] = poolStorage
.redeemGovernanceBalances[msg.sender]
.add(governanceOut);
poolStorage.unclaimedPoolGovernance = poolStorage
.unclaimedPoolGovernance
.add(governanceOut);

poolStorage.lastRedeemedBlock[msg.sender] = block.number;

// burn Dollars
IERC20Ubiquity ubiquityDollarToken = IERC20Ubiquity(
LibAppStorage.appStorage().dollarTokenAddress
IERC20Ubiquity(LibAppStorage.appStorage().dollarTokenAddress).burnFrom(
msg.sender,
dollarAmount
);
// mint Governance tokens to this address
IERC20Ubiquity(LibAppStorage.appStorage().governanceTokenAddress).mint(
address(this),
governanceOut
);
ubiquityDollarToken.burnFrom(msg.sender, dollarAmount);
}

/**
Expand Down
6 changes: 5 additions & 1 deletion packages/contracts/test/diamond/DiamondTestSetup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -446,11 +446,15 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper {
CREDIT_TOKEN_BURNER_ROLE,
address(diamond)
);
// grant diamond Governance token admin and burner rights
// grant diamond Governance token admin, minter and burner rights
accessControlFacet.grantRole(
GOVERNANCE_TOKEN_MANAGER_ROLE,
address(diamond)
);
accessControlFacet.grantRole(
GOVERNANCE_TOKEN_MINTER_ROLE,
address(diamond)
);
accessControlFacet.grantRole(
GOVERNANCE_TOKEN_BURNER_ROLE,
address(diamond)
Expand Down
Loading

0 comments on commit 8b3bfb1

Please sign in to comment.