Skip to content

Commit

Permalink
Merge pull request #62 from Sperax/dev
Browse files Browse the repository at this point in the history
USDs-v2 contract deployment
  • Loading branch information
YashP16 authored Jan 19, 2024
2 parents 603ad62 + 64b48d8 commit ff71faf
Show file tree
Hide file tree
Showing 56 changed files with 3,569 additions and 1,272 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The project uses foundry framework for compiling, developing and testing contrac

## Project Summary:
* [Summary](/docs/src/SUMMARY.md)
* [Sequence Diagrams](/docs/arch/sequentialDiagrams/README.md)

## Project Setup:
* [Install Foundry](https://book.getfoundry.sh/getting-started/installation)
Expand Down
29 changes: 20 additions & 9 deletions contracts/buyback/SPABuyback.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@ import {ERC20BurnableUpgradeable} from
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IveSPARewarder} from "../interfaces/IveSPARewarder.sol";
import {IOracle} from "../interfaces/IOracle.sol";
import {Helpers} from "../libraries/Helpers.sol";

/// @title Buyback contract of the USDs Buyback protocol.
/// @notice Users can give their SPA and get USDs in return.
/// @notice The SPA in the contract is distributed as rewards based on the rewardPercentage and the rest is burned.
/// @title SPABuyback of USDs Protocol.
/// @author Sperax Foundation
/// @notice This contract allows users to exchange SPA tokens for USDs tokens.
/// @dev Users can provide SPA tokens and receive USDs tokens in return based on the current exchange rate.
/// @dev A percentage of the provided SPA tokens are distributed as rewards, and the rest are burned.
contract SPABuyback is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable {
using SafeERC20Upgradeable for ERC20BurnableUpgradeable;

address public veSpaRewarder;
uint256 public rewardPercentage;
address public oracle;
address public veSpaRewarder; // Rewarder contract.
uint256 public rewardPercentage; // Share of rewards to be distributed with veSPA holders.
address public oracle; // Price oracle for SPA and USDs

// Events
event BoughtBack(
address indexed receiverOfUSDs,
address indexed senderOfSPA,
Expand All @@ -35,6 +38,7 @@ contract SPABuyback is Initializable, OwnableUpgradeable, ReentrancyGuardUpgrade
event VeSpaRewarderUpdated(address newVeSpaRewarder);
event OracleUpdated(address newOracle);

// Custom error messages
error CannotWithdrawSPA();
error InsufficientUSDsBalance(uint256 toSend, uint256 bal);

Expand Down Expand Up @@ -126,6 +130,7 @@ contract SPABuyback is Initializable, OwnableUpgradeable, ReentrancyGuardUpgrade
(uint256 usdsToSend, uint256 spaPrice) = _getUsdsOutForSpa(_spaIn);
Helpers._isNonZeroAmt(usdsToSend, "SPA Amount too low");

// Slippage check
if (usdsToSend < _minUSDsOut) {
revert Helpers.MinSlippageError(usdsToSend, _minUSDsOut);
}
Expand Down Expand Up @@ -154,8 +159,9 @@ contract SPABuyback is Initializable, OwnableUpgradeable, ReentrancyGuardUpgrade
// Calculating the amount to reward based on rewardPercentage
uint256 toReward = (balance * rewardPercentage) / Helpers.MAX_PERCENTAGE;

// Transferring SPA tokens
ERC20BurnableUpgradeable(Helpers.SPA).safeTransfer(veSpaRewarder, toReward);
// Transferring SPA tokens to rewarder
ERC20BurnableUpgradeable(Helpers.SPA).forceApprove(veSpaRewarder, toReward);
IveSPARewarder(veSpaRewarder).addRewards(Helpers.SPA, toReward, 1);
emit SPARewarded(toReward);

// Remaining balance will be burned
Expand Down Expand Up @@ -188,14 +194,19 @@ contract SPABuyback is Initializable, OwnableUpgradeable, ReentrancyGuardUpgrade
return (usdsOut, spaPrice);
}

/// @dev Retrieves price data from the oracle contract for SPA and USDS tokens.
/// @return The price of USDS in SPA, the price of SPA in USDS, and their respective precisions.
function _getOracleData() private view returns (uint256, uint256, uint256, uint256) {
// Fetches the price for SPA and USDS from oracle
// Fetches the price for SPA and USDS from the oracle contract
IOracle.PriceData memory usdsData = IOracle(oracle).getPrice(Helpers.USDS);
IOracle.PriceData memory spaData = IOracle(oracle).getPrice(Helpers.SPA);

return (usdsData.price, spaData.price, usdsData.precision, spaData.precision);
}

/// @dev Checks if the provided reward percentage is valid.
/// @param _rewardPercentage The reward percentage to validate.
/// @dev The reward percentage must be a non-zero value and should not exceed the maximum percentage value.
function _isValidRewardPercentage(uint256 _rewardPercentage) private pure {
Helpers._isNonZeroAmt(_rewardPercentage);
Helpers._isLTEMaxPercentage(_rewardPercentage);
Expand Down
169 changes: 95 additions & 74 deletions contracts/buyback/YieldReserve.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import {IOracle} from "../interfaces/IOracle.sol";
import {Helpers} from "../libraries/Helpers.sol";
import {IDripper} from "../interfaces/IDripper.sol";

/// @title YieldReserve of USDs protocol
/// @notice The contract allows users to swap the supported stable coins against yield earned by USDs protocol
/// It sends USDs to dripper for rebase, and to Buyback Contract for buyback.
/// @title YieldReserve of USDs Protocol.
/// @author Sperax Foundation
/// @notice This contract allows users to swap supported stable-coins for yield earned by the USDs protocol.
/// It sends USDs to the Dripper contract for rebase and to the Buyback Contract for buyback.
contract YieldReserve is ReentrancyGuard, Ownable {
using SafeERC20 for IERC20;

Expand All @@ -23,15 +23,18 @@ contract YieldReserve is ReentrancyGuard, Ownable {
uint160 conversionFactor;
}

address public vault;
address public oracle;
address public buyback;
address public dripper;
// Percentage of USDs to be sent to Buyback 5000 means 50%
// Addresses of key contracts
address public vault; // Address of the Vault contract
address public oracle; // Address of the Oracle contract
address public buyback; // Address of the Buyback contract
address public dripper; // Address of the Dripper contract

// Percentage of USDs to be sent to Buyback (e.g., 5000 for 50%)
uint256 public buybackPercentage;

mapping(address => TokenData) public tokenData;

// Events
event Swapped(
address indexed srcToken,
address indexed dstToken,
Expand All @@ -42,41 +45,42 @@ contract YieldReserve is ReentrancyGuard, Ownable {
event USDsMintedViaSwapper(address indexed collateralAddr, uint256 usdsMinted);
event Withdrawn(address indexed token, address indexed receiver, uint256 amount);
event BuybackPercentageUpdated(uint256 toBuyback);
event BuybackAddressUpdated(address newBuyback);
event BuybackUpdated(address newBuyback);
event OracleUpdated(address newOracle);
event VaultAddressUpdated(address newVault);
event DripperAddressUpdated(address newDripper);
event USDsSent(uint256 toBuyback, uint256 toVault);
event VaultUpdated(address newVault);
event DripperUpdated(address newDripper);
event USDsSent(uint256 toBuyback, uint256 toDripper);
event SrcTokenPermissionUpdated(address indexed token, bool isAllowed);
event DstTokenPermissionUpdated(address indexed token, bool isAllowed);

// Custom error messages
error InvalidSourceToken();
error InvalidDestinationToken();
error AlreadyInDesiredState();
error TokenPriceFeedMissing();

/// @notice Constructor of the contract
/// @param _buyback Address of Buyback contract
/// @param _vault Address of Vault
/// @param _oracle Address of Oracle
/// @param _dripper Address of the dripper contract
/// @notice Constructor of the YieldReserve contract.
/// @param _buyback Address of the Buyback contract.
/// @param _vault Address of the Vault.
/// @param _oracle Address of the Oracle.
/// @param _dripper Address of the Dripper contract.
constructor(address _buyback, address _vault, address _oracle, address _dripper) {
updateBuybackAddress(_buyback);
updateVaultAddress(_vault);
updateOracleAddress(_oracle);
updateDripperAddress(_dripper);
updateBuyback(_buyback);
updateVault(_vault);
updateOracle(_oracle);
updateDripper(_dripper);

/// @dev buybackPercentage is initialized to 50%
updateBuybackPercentage(uint256(5000));
// Initialize buybackPercentage to 50%
updateBuybackPercentage(5000);
}

// OPERATION FUNCTIONS

/// @notice Swap function to be called by front end
/// @param _srcToken Source / Input token
/// @param _dstToken Destination / Output token
/// @param _amountIn Input token amount
/// @param _minAmountOut Minimum output tokens expected
/// @notice Swap function to be called by frontend users.
/// @param _srcToken Source/Input token.
/// @param _dstToken Destination/Output token.
/// @param _amountIn Input token amount.
/// @param _minAmountOut Minimum output tokens expected.
function swap(address _srcToken, address _dstToken, uint256 _amountIn, uint256 _minAmountOut) external {
return swap({
_srcToken: _srcToken,
Expand All @@ -89,9 +93,9 @@ contract YieldReserve is ReentrancyGuard, Ownable {

// ADMIN FUNCTIONS

/// @notice A function to allow or disallow a `_token` as source token
/// @param _token Address of the token
/// @param _isAllowed If True, allow it to be used as src token / input token else don't allow
/// @notice Allow or disallow a specific `token` for use as a source/input token.
/// @param _token Address of the token to be allowed or disallowed.
/// @param _isAllowed If set to `true`, the token will be allowed as a source/input token; otherwise, it will be disallowed.
function toggleSrcTokenPermission(address _token, bool _isAllowed) external onlyOwner {
TokenData storage data = tokenData[_token];
if (data.srcAllowed == _isAllowed) revert AlreadyInDesiredState();
Expand All @@ -107,9 +111,10 @@ contract YieldReserve is ReentrancyGuard, Ownable {
emit SrcTokenPermissionUpdated(_token, _isAllowed);
}

/// @notice A function to allow or disallow a `_token` as output/destination token.
/// @param _token Address of the token
/// @param _isAllowed If True, allow it to be used as src token / input token else don't allow
/// @notice Allow or disallow a specific `token` for use as a destination/output token.
/// @dev Reverts if caller is not owner.
/// @param _token Address of the token to be allowed or disallowed.
/// @param _isAllowed If set to `true`, the token will be allowed as a destination/output token; otherwise, it will be disallowed.
function toggleDstTokenPermission(address _token, bool _isAllowed) external onlyOwner {
TokenData storage data = tokenData[_token];
if (data.dstAllowed == _isAllowed) revert AlreadyInDesiredState();
Expand All @@ -125,66 +130,69 @@ contract YieldReserve is ReentrancyGuard, Ownable {
emit DstTokenPermissionUpdated(_token, _isAllowed);
}

/// @notice Emergency withdrawal function for unexpected situations
/// @param _token Address of the asset to be withdrawn
/// @param _receiver Address of the receiver of tokens
/// @param _amount Amount of tokens to be withdrawn
/// @notice Emergency withdrawal function for unexpected situations.
/// @param _token Address of the asset to be withdrawn.
/// @param _receiver Address of the receiver of tokens.
/// @param _amount Amount of tokens to be withdrawn.
function withdraw(address _token, address _receiver, uint256 _amount) external onlyOwner {
Helpers._isNonZeroAmt(_amount);
IERC20(_token).safeTransfer(_receiver, _amount);
emit Withdrawn(_token, _receiver, _amount);
}

/// @notice Set the % of minted USDs sent Buyback
/// @param _toBuyback % of USDs sent to Buyback
/// @dev The rest of the USDs is sent to VaultCore
/// @dev E.g. _toBuyback == 3000 means 30% of the newly
/// minted USDs would be sent to Buyback; the rest 70% to VaultCore
/// @notice Set the percentage of newly minted USDs to be sent to the Buyback contract.
/// @dev Reverts if caller is not owner.
/// @param _toBuyback The percentage of USDs sent to Buyback (e.g., 3000 for 30%).
/// @dev The remaining USDs are sent to VaultCore for rebase.
function updateBuybackPercentage(uint256 _toBuyback) public onlyOwner {
Helpers._isNonZeroAmt(_toBuyback);
Helpers._isLTEMaxPercentage(_toBuyback);
buybackPercentage = _toBuyback;
emit BuybackPercentageUpdated(_toBuyback);
}

/// @notice Update Buyback contract's address
/// @param _newBuyBack New address of Buyback contract
function updateBuybackAddress(address _newBuyBack) public onlyOwner {
/// @notice Update the address of the Buyback contract.
/// @dev Reverts if caller is not owner.
/// @param _newBuyBack New address of the Buyback contract.
function updateBuyback(address _newBuyBack) public onlyOwner {
Helpers._isNonZeroAddr(_newBuyBack);
buyback = _newBuyBack;
emit BuybackAddressUpdated(_newBuyBack);
emit BuybackUpdated(_newBuyBack);
}

/// @notice Update Oracle's address
/// @param _newOracle New address of Oracle
function updateOracleAddress(address _newOracle) public onlyOwner {
/// @notice Update the address of the Oracle contract.
/// @dev Reverts if caller is not owner.
/// @param _newOracle New address of the Oracle contract.
function updateOracle(address _newOracle) public onlyOwner {
Helpers._isNonZeroAddr(_newOracle);
oracle = _newOracle;
emit OracleUpdated(_newOracle);
}

/// @notice Update Dripper's address
/// @param _newDripper New address of Dripper
function updateDripperAddress(address _newDripper) public onlyOwner {
/// @notice Update the address of the Dripper contract.
/// @dev Reverts if caller is not owner.
/// @param _newDripper New address of the Dripper contract.
function updateDripper(address _newDripper) public onlyOwner {
Helpers._isNonZeroAddr(_newDripper);
dripper = _newDripper;
emit DripperAddressUpdated(_newDripper);
emit DripperUpdated(_newDripper);
}

/// @notice Update VaultCore's address
/// @param _newVault New address of VaultCore
function updateVaultAddress(address _newVault) public onlyOwner {
/// @notice Update the address of the VaultCore contract.
/// @dev Reverts if caller is not owner.
/// @param _newVault New address of the VaultCore contract.
function updateVault(address _newVault) public onlyOwner {
Helpers._isNonZeroAddr(_newVault);
vault = _newVault;
emit VaultAddressUpdated(_newVault);
emit VaultUpdated(_newVault);
}

/// @notice Swap allowed src token to allowed dst token.
/// @param _srcToken Source / Input token
/// @param _dstToken Destination / Output token
/// @param _amountIn Input token amount
/// @param _minAmountOut Minimum output tokens expected
/// @param _receiver Receiver of the tokens
/// @notice Swap allowed source token for allowed destination token.
/// @param _srcToken Source/Input token.
/// @param _dstToken Destination/Output token.
/// @param _amountIn Input token amount.
/// @param _minAmountOut Minimum output tokens expected.
/// @param _receiver Receiver of the tokens.
function swap(address _srcToken, address _dstToken, uint256 _amountIn, uint256 _minAmountOut, address _receiver)
public
nonReentrant
Expand All @@ -199,8 +207,7 @@ contract YieldReserve is ReentrancyGuard, Ownable {
// Mint USDs
IERC20(_srcToken).forceApprove(vault, _amountIn);
IVault(vault).mint(_srcToken, _amountIn, 0, block.timestamp);
// No need to do slippage check as it is our contract
// and the vault does that.
// No need to do a slippage check as it is our contract, and the vault does that.
}
IERC20(_dstToken).safeTransfer(_receiver, amountToSend);
_sendUSDs();
Expand All @@ -213,10 +220,24 @@ contract YieldReserve is ReentrancyGuard, Ownable {
});
}

/// @notice A `view` function to get estimated output
/// @param _srcToken Input token address
/// @param _dstToken Output token address
/// @param _amountIn Input amount of _srcToken
/// @notice Mints USDs directly with the allowed collaterals for USDs.
/// @param _token Address of token to mint USDs with
/// @dev Only collaterals configured in USDs vault are allowed to be used for minting.
function mintUSDs(address _token) public nonReentrant {
Helpers._isNonZeroAddr(_token);
uint256 bal = IERC20(_token).balanceOf(address(this));
IERC20(_token).forceApprove(vault, bal);
IVault(vault).mint(_token, bal, 0, block.timestamp);
// No need to do slippage check as it is our contract
// and the vault does that.
_sendUSDs();
}

/// @notice Get an estimate of the output token amount for a given input token amount.
/// @param _srcToken Input token address.
/// @param _dstToken Output token address.
/// @param _amountIn Input amount of _srcToken.
/// @return Estimated output token amount.
function getTokenBForTokenA(address _srcToken, address _dstToken, uint256 _amountIn)
public
view
Expand All @@ -239,15 +260,15 @@ contract YieldReserve is ReentrancyGuard, Ownable {

// UTILITY FUNCTIONS

/// @notice Sends USDs to buyback as per buybackPercentage
/// and rest to VaultCore for rebase
/// @notice Distributes USDs to the Buyback and Dripper contracts based on buybackPercentage.
/// @dev Sends a portion of the USDs balance to the Buyback contract and the remaining to the Dripper contract for rebase.
function _sendUSDs() private {
uint256 balance = IERC20(Helpers.USDS).balanceOf(address(this));

// Calculating the amount to send to Buyback based on buybackPercentage
// Calculate the amount to send to Buyback based on buybackPercentage
uint256 toBuyback = (balance * buybackPercentage) / Helpers.MAX_PERCENTAGE;

// Remaining balance will be sent to Dripper for rebase
// The remaining balance is sent to the Dripper for rebase
uint256 toDripper = balance - toBuyback;

emit USDsSent(toBuyback, toDripper);
Expand Down
4 changes: 3 additions & 1 deletion contracts/interfaces/IOracle.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface IOracle {
Expand All @@ -13,12 +14,13 @@ interface IOracle {

/// @notice Validates if price feed exists for a `_token`
/// @param _token address of the desired token.
/// @return bool if price feed exists.
/// @dev Function reverts if price feed not set.
function priceFeedExists(address _token) external view returns (bool);

/// @notice Gets the price feed for `_token`.
/// @param _token address of the desired token.
/// @dev Function reverts if the price feed does not exists.
/// @return (uint256 price, uint256 precision).
/// @dev Function reverts if the price feed does not exists.
function getPrice(address _token) external view returns (PriceData memory);
}
Loading

0 comments on commit ff71faf

Please sign in to comment.