Skip to content

Commit

Permalink
Merge pull request #3 from groLabs/compoundv2
Browse files Browse the repository at this point in the history
Compoundv2
  • Loading branch information
SHAKOTN authored Sep 25, 2023
2 parents bce3628 + 9c70965 commit 20eabcc
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 118 deletions.
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
url = https://github.com/foundry-rs/forge-std
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/openzeppelin/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
1 change: 0 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,3 @@ solc_version = "0.8.20"

[rpc_endpoints]
mainnet = "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}"

9 changes: 5 additions & 4 deletions src/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ pragma solidity 0.8.20;

library RedemptionErrors {
error DeadlineExceeded();
error DeadlineNotExceeded();
error UserBalanceToSmall();
error NoUserBalance();
error NoUserClaim();
error ClaimsPeriodNotStarted();
error GreaterThanZeroOnly();
error InvalidClaim();
error USDCRedeemFailed(uint256 redeemResult);

error NoSweepGro();
error InsufficientBalance();
}
157 changes: 97 additions & 60 deletions src/RedemptionPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,82 +3,112 @@ pragma solidity 0.8.20;

import {Ownable} from "@openzeppelin/access/Ownable.sol";
import {IERC20} from "@openzeppelin/token/ERC20/IERC20.sol";
// import "@openzeppelin/utils/math/SafeMath.sol";
import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {RedemptionErrors} from "./Errors.sol";

/////////////////////////////////////////////////////////////////////////////
// Interfaces //
/////////////////////////////////////////////////////////////////////////////
// Interface for Compound cToken (cUSDC)

interface ICERC20 is IERC20 {
function redeem(uint256 redeemTokens) external returns (uint256);

function exchangeRateStored() external view returns (uint256);
}

contract RedemptionPool is Ownable {
using SafeERC20 for IERC20;

/////////////////////////////////////////////////////////////////////////////
// Constants //
/////////////////////////////////////////////////////////////////////////////

uint256 public constant DURATION = 30 days;
uint256 public constant DURATION = 28 days;
uint256 public immutable DEADLINE;

uint256 internal constant BIPS = 10_000;
uint256 internal constant PRECISION = 1e18;
uint256 internal constant CUSDC_PRECISION = 1e8;

address internal constant DAO = address(0x359F4fe841f246a095a82cb26F5819E10a91fe0d);
address internal constant COMPTROLLER = address(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B);
address internal constant UNIV2ROUTER = address(0xE592427A0AEce92De3Edee1F18E0157C05861564);

// TOKENS
IERC20 public constant GRO = IERC20(0x3Ec8798B81485A254928B70CDA1cf0A2BB0B74D7);
IERC20 public constant CUSDC = IERC20(0x39AA39c021dfbaE8faC545936693aC917d5E7563);
ICERC20 public constant CUSDC = ICERC20(0x39AA39c021dfbaE8faC545936693aC917d5E7563);
IERC20 public constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);

address internal constant USDC = address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
address internal constant COMP = address(0xc00e94Cb662C3520282E6f5717214004A7f26888);
address internal constant WETH9 = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
/////////////////////////////////////////////////////////////////////////////
// Modifiers //
/////////////////////////////////////////////////////////////////////////////

modifier onlyBeforeDeadline() {
if (block.timestamp > DEADLINE) {
revert RedemptionErrors.DeadlineExceeded();
}
_;
}

modifier onlyAfterDeadline() {
if (block.timestamp <= DEADLINE) {
revert RedemptionErrors.ClaimsPeriodNotStarted();
}
_;
}
/////////////////////////////////////////////////////////////////////////////
// Storage //
/////////////////////////////////////////////////////////////////////////////

mapping(address => uint256) private _userBalance;
mapping(address => uint256) private _userGROBalance;
mapping(address => uint256) private _userClaims;
uint256 public totalGRO;
uint256 public totalAssetsDeposited;
uint256 public totalCUSDCDeposited;
uint256 public totalCUSDCWithdrawn;

/////////////////////////////////////////////////////////////////////////////
// Events //
/////////////////////////////////////////////////////////////////////////////

event Deposit(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 amount);
event Claim(address indexed user, uint256 amount);
event CUSDCDeposit(uint256 amount);
event PositionTransferred(address indexed from, address indexed to, uint256 amount);

/////////////////////////////////////////////////////////////////////////////
// CONSTRUCTOR //
/////////////////////////////////////////////////////////////////////////////

constructor() {
transferOwnership(DAO);
// Sets the DEADLINE to 30 days from now
// Sets the DEADLINE to 28 days from now
DEADLINE = block.timestamp + DURATION;
}

/////////////////////////////////////////////////////////////////////////////
// VIEWS //
/////////////////////////////////////////////////////////////////////////////

/// @notice Returns the price per share of the pool
/// @notice Returns the price per share of the pool in terms of USDC
function getPricePerShare() public view returns (uint256) {
return totalAssetsDeposited * PRECISION / totalGRO;
return (totalCUSDCDeposited * PRECISION) / totalGRO;
}

/// @notice Returns the price per share of the pool in terms of USDC
function getUSDCperGRO() public view returns (uint256) {
// Get the exchange rate from cUSDC to USDC (18 decimals)
uint256 USDCperCUSDC = ICERC20(CUSDC).exchangeRateStored();

// Calculate USDC per GRO (result will have 6 decimals)
return (totalCUSDCDeposited * USDCperCUSDC) / totalGRO;
}

/// @notice Returns the amount of cUSDC available for a user
/// @notice Returns user's share of the claims pot
/// @param user address of the user
function getSharesAvailable(address user) public view returns (uint256) {
return _userBalance[user] * totalAssetsDeposited / totalGRO - _userClaims[user];
return (_userGROBalance[user] * totalCUSDCDeposited) / totalGRO - _userClaims[user];
}

/// @notice Returns the amount of GRO user has deposited
/// @param user address of the user
function getUserBalance(address user) external view returns (uint256) {
return _userBalance[user];
return _userGROBalance[user];
}

/// @notice Returns claimed cUSDC for a user
Expand All @@ -98,73 +128,80 @@ contract RedemptionPool is Ownable {

/// @notice deposit GRO tokens to the pool before the deadline
/// @param _amount amount of GRO tokens to deposit
function deposit(uint256 _amount) external {
// Checks that the DEADLINE has not passed
if (block.timestamp > DEADLINE) {
revert RedemptionErrors.DeadlineExceeded();
}
function deposit(uint256 _amount) external onlyBeforeDeadline {
// Transfers the GRO tokens from the sender to this contract
GRO.safeTransferFrom(msg.sender, address(this), _amount);
// Increases the balance of the sender by the amount
_userBalance[msg.sender] += _amount;
_userGROBalance[msg.sender] += _amount;
// Increases the total deposited by the amount
totalGRO += _amount;
// Emits the Deposit event
emit Deposit(msg.sender, _amount);
}

/// @notice withdraw deposited GRO tokens before the deadline
/// @param _amount amount of GRO tokens to withdraw
function withdraw(uint256 _amount) external {
if (block.timestamp > DEADLINE) {
revert RedemptionErrors.DeadlineExceeded();
function withdraw(uint256 _amount) external onlyBeforeDeadline {
if (_userGROBalance[msg.sender] < _amount) {
revert RedemptionErrors.InsufficientBalance();
}
if (_userBalance[msg.sender] > _amount) {
revert RedemptionErrors.UserBalanceToSmall();
}
_userBalance[msg.sender] -= _amount;

_userGROBalance[msg.sender] -= _amount;
totalGRO -= _amount;
GRO.safeTransfer(msg.sender, _amount);
emit Withdraw(msg.sender, _amount);
}

/// @notice Allow to withdraw cUSDC based on amount of GRO tokens deposited per user
function claim() external {
if (block.timestamp <= DEADLINE) {
revert RedemptionErrors.DeadlineExceeded();
}
if (_userBalance[msg.sender] <= 0) {
revert RedemptionErrors.NoUserBalance();
}
/**
* @notice Allow users to claim their share of USDC tokens based on the amount of GRO tokens they have deposited
* @dev Users must have a positive GRO token balance and a non-zero claim available to make a claim
* @dev The deadline for making GRO deposits must have passed
* @dev Redeems the user's cUSDC tokens for an equivalent amount of USDC tokens and transfers them to the user's address
* @dev Decreases the user's claims and contract accounting by the amount claimed
*/
function claim(uint256 _amount) external onlyAfterDeadline {
// Get the amount of cUSDC tokens available for the user to claim
uint256 userClaim = getSharesAvailable(msg.sender);
if (userClaim == 0) {
revert RedemptionErrors.NoUserClaim();

// Check that _amount is greater than 0 and smaller (or equal to) than userClaim
if (!(_amount > 0 && _amount <= userClaim)) {
revert RedemptionErrors.InvalidClaim();
}

// Redeem the user's cUSDC tokens for USDC tokens
// and transfer the USDC tokens to the user's address
uint256 usdcBalanceBefore = USDC.balanceOf(address(this));
uint256 redeemResult = ICERC20(CUSDC).redeem(_amount);
if (redeemResult != 0) {
revert RedemptionErrors.USDCRedeemFailed(redeemResult);
}
_userClaims[msg.sender] += userClaim;
IERC20(CUSDC).transfer(msg.sender, userClaim);
emit Claim(msg.sender, userClaim);
uint256 usdcRedeemed = USDC.balanceOf(address(this)) - usdcBalanceBefore;
USDC.safeTransfer(msg.sender, usdcRedeemed);

// Adjust the user's and the cumulative tally of claimed cUSDC
_userClaims[msg.sender] += _amount;
totalCUSDCWithdrawn += _amount;
emit Claim(msg.sender, _amount);
}

/////////////////////////////////////////////////////////////////////////////
// Permissioned funcs //
/////////////////////////////////////////////////////////////////////////////

/// @notice Pulls assets from the msig
/// @param amount amount of cUSDC to pull
function depositCUSDC(uint256 amount) external onlyOwner {
require(amount > 0, "amount must be greater than 0");
require(IERC20(CUSDC).transferFrom(msg.sender, address(this), amount), "transfer failed");
totalAssetsDeposited += amount;
emit CUSDCDeposit(totalAssetsDeposited);
/// @notice Pulls assets from the DAO msig
/// @param _amount amount of cUSDC to pull
function depositCUSDC(uint256 _amount) external onlyOwner {
// Transfer cUSDC from the caller to this contract
IERC20(CUSDC).safeTransferFrom(msg.sender, address(this), _amount);

totalCUSDCDeposited += _amount;
emit CUSDCDeposit(totalCUSDCDeposited);
}

/// @notice Allow to withdraw any tokens except GRO back to the owner
/// @notice Allow to withdraw any tokens except GRO back to the owner, as long as the deadline has not passed
/// @param _token address of the token to sweep
function sweep(address _token) external onlyOwner {
function sweep(address _token) external onlyOwner onlyBeforeDeadline {
// Do not allow to sweep GRO tokens
if (_token == address(GRO)) {
revert RedemptionErrors.NoSweepGro();
}
if (_token == address(GRO)) revert RedemptionErrors.NoSweepGro();

// Transfers the tokens to the owner
IERC20(_token).safeTransfer(owner(), IERC20(_token).balanceOf(address(this)));
}
Expand Down
6 changes: 4 additions & 2 deletions test/BaseFixture.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ contract BaseFixture is Test {
using stdStorage for StdStorage;

ERC20 public constant GRO = ERC20(0x3Ec8798B81485A254928B70CDA1cf0A2BB0B74D7);
ERC20 public constant CUSDC = ERC20(0x39AA39c021dfbaE8faC545936693aC917d5E7563);
ICERC20 public constant CUSDC = ICERC20(0x39AA39c021dfbaE8faC545936693aC917d5E7563);
ERC20 public constant USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
address public constant DAO = address(0x359F4fe841f246a095a82cb26F5819E10a91fe0d);

Utils internal utils;
Expand Down Expand Up @@ -40,9 +41,10 @@ contract BaseFixture is Test {
vm.store(_contract, bytes32(slot), bytes32(value));
}

/// @dev Helper function to
/// @dev Helper function to pull cUSDC from DAO and fill Compound with USDC
function pullCUSDC(uint256 amount) public {
setStorage(DAO, CUSDC.balanceOf.selector, address(CUSDC), type(uint96).max);
setStorage(address(CUSDC), USDC.balanceOf.selector, address(USDC), type(uint96).max);
vm.startPrank(DAO);
CUSDC.approve(address(redemptionPool), amount);
redemptionPool.depositCUSDC(amount);
Expand Down
Loading

0 comments on commit 20eabcc

Please sign in to comment.