Skip to content

Commit

Permalink
feat: integrate USTB contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
yan-man committed Nov 29, 2024
1 parent d85915b commit 9209e59
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 108 deletions.
25 changes: 12 additions & 13 deletions src/contracts/facilitators/gsm/converter/USTBGsmConverter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {IGhoToken} from '../../../gho/interfaces/IGhoToken.sol';
import {IGsm} from '../interfaces/IGsm.sol';
import {IGsmConverter} from './interfaces/IGsmConverter.sol';
// TODO: replace with proper issuance implementation/interface later from USTB
import {ISubscriptionRedemption} from '../dependencies/USTB/ISubscriptionRedemption.sol';
import {ISubscription} from '../dependencies/USTB/ISubscription.sol';
import {MockBUIDLSubscription} from '../../../../test/mocks/MockBUIDLSubscription.sol';

import 'forge-std/console2.sol';
Expand Down Expand Up @@ -230,15 +230,15 @@ contract USTBGsmConverter is Ownable, EIP712, IGsmConverter {
uint256 initialIssuedAssetBalance = IERC20(ISSUED_ASSET).balanceOf(address(this));
uint256 initialRedeemedAssetBalance = IERC20(REDEEMED_ASSET).balanceOf(address(this));

(uint256 assetAmount, , , ) = IGsm(GSM).getGhoAmountForSellAsset(maxAmount); // asset is BUIDL
IERC20(REDEEMED_ASSET).transferFrom(originator, address(this), assetAmount);
IERC20(REDEEMED_ASSET).approve(SUBSCRIPTION_CONTRACT, assetAmount);
//TODO: replace with proper issuance implementation later
MockBUIDLSubscription(SUBSCRIPTION_CONTRACT).issuance(assetAmount);
uint256 subscribedAssetAmount = IERC20(ISSUED_ASSET).balanceOf(address(this)) -
initialIssuedAssetBalance;
// TODO: probably will be fees from issuance, so need to adjust the logic
// only use this require only if preview of issuance is possible, otherwise it is redundant
(uint256 redeemedAssetAmount, , , ) = IGsm(GSM).getGhoAmountForSellAsset(maxAmount); // asset is BUIDL
IERC20(REDEEMED_ASSET).transferFrom(originator, address(this), redeemedAssetAmount);
IERC20(REDEEMED_ASSET).approve(SUBSCRIPTION_CONTRACT, redeemedAssetAmount);

(uint256 subscribedAssetAmount, , ) = IERC20(SUBSCRIPTION_CONTRACT).calculateSuperstateTokenOut(
redeemedAssetAmount,
REDEEMED_ASSET
);
ISubscription(SUBSCRIPTION_CONTRACT).subscribe(redeemedAssetAmount, REDEEMED_ASSET);
require(
IERC20(ISSUED_ASSET).balanceOf(address(this)) ==
initialIssuedAssetBalance + subscribedAssetAmount,
Expand All @@ -247,9 +247,8 @@ contract USTBGsmConverter is Ownable, EIP712, IGsmConverter {
// reset approval after issuance
IERC20(REDEEMED_ASSET).approve(SUBSCRIPTION_CONTRACT, 0);

// TODO: account for fees for sellAsset amount param
(assetAmount, , , ) = IGsm(GSM).getGhoAmountForSellAsset(subscribedAssetAmount); // recalculate based on actual issuance amount, < maxAmount
IERC20(ISSUED_ASSET).approve(GSM, assetAmount);
(redeemedAssetAmount, , , ) = IGsm(GSM).getGhoAmountForSellAsset(subscribedAssetAmount); // recalculate based on actual issuance amount, < maxAmount
IERC20(ISSUED_ASSET).approve(GSM, redeemedAssetAmount);
(uint256 soldAssetAmount, uint256 ghoBought) = IGsm(GSM).sellAsset(
subscribedAssetAmount,
receiver
Expand Down
11 changes: 11 additions & 0 deletions src/contracts/facilitators/gsm/dependencies/USTB/ISubscription.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Simple minimal interface for subscriptions of USTB
pragma solidity ^0.8.10;

interface ISubscription {
/**
* @notice Subscribes an amount of USTB in exchange for USDC
* @param amount The amount of USDC to subscribe
* @param stablecoin The address of the stablecoin to calculate with
*/
function subscribe(uint256 inAmount, address stablecoin) external;
}
16 changes: 10 additions & 6 deletions src/test/TestGhoBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {MockRedemptionFailed} from './mocks/MockRedemptionFailed.sol';
import {MockBUIDLSubscription} from './mocks/MockBUIDLSubscription.sol';
import {MockBUIDLSubscriptionFailed} from './mocks/MockBUIDLSubscriptionFailed.sol';
import {MockBUIDLSubscriptionFailedInvalidUSDCAccepted} from './mocks/MockBUIDLSubscriptionFailedInvalidUSDCAccepted.sol';
import {MockUSTBSubscriptionRedemption} from './mocks/MockUSTBSubscriptionRedemption.sol';
import {MockUSTBSubscription} from './mocks/MockUSTBSubscription.sol';
import {MockPoolDataProvider} from './mocks/MockPoolDataProvider.sol';

// interfaces
Expand Down Expand Up @@ -121,6 +121,7 @@ contract TestGhoBase is Test, Constants, Events {
IStakedAaveV3 STK_TOKEN;
TestnetERC20 USDC_TOKEN;
TestnetERC20 BUIDL_TOKEN;
TestnetERC20 USTB_TOKEN;
MockERC4626 USDC_4626_TOKEN;
MockPool POOL;
MockAclManager ACL_MANAGER;
Expand All @@ -132,7 +133,7 @@ contract TestGhoBase is Test, Constants, Events {
MockBUIDLSubscription BUIDL_USDC_ISSUANCE;
MockBUIDLSubscriptionFailed BUIDL_USDC_ISSUANCE_FAILED;
MockBUIDLSubscriptionFailedInvalidUSDCAccepted BUIDL_USDC_ISSUANCE_FAILED_INVALID_USDC;
MockUSTBSubscriptionRedemption USTB_SUBCRIPTION_REDEMPTION;
MockUSTBSubscription USTB_SUBCRIPTION;
PriceOracle PRICE_ORACLE;
WETH9Mock WETH;
GhoVariableDebtToken GHO_DEBT_TOKEN;
Expand Down Expand Up @@ -216,6 +217,7 @@ contract TestGhoBase is Test, Constants, Events {
6,
FAUCET
);
USTB_TOKEN = new TestnetERC20('Superstate Token', 'USTB', 6, FAUCET);
USDC_4626_TOKEN = new MockERC4626('USD Coin 4626', '4626', address(USDC_TOKEN));
IPool iPool = IPool(address(POOL));
WETH = new WETH9Mock('Wrapped Ether', 'WETH', FAUCET);
Expand Down Expand Up @@ -291,6 +293,11 @@ contract TestGhoBase is Test, Constants, Events {
address(BUIDL_TOKEN),
6
);
GHO_USTB_GSM_FIXED_PRICE_STRATEGY = new FixedPriceStrategy(
DEFAULT_FIXED_PRICE,
address(BUIDL_TOKEN),
6
);
GHO_GSM_FIXED_FEE_STRATEGY = new FixedFeeStrategy(DEFAULT_GSM_BUY_FEE, DEFAULT_GSM_SELL_FEE);
GHO_GSM_LAST_RESORT_LIQUIDATOR = new SampleLiquidator();
GHO_GSM_SWAP_FREEZER = new SampleSwapFreezer();
Expand Down Expand Up @@ -428,10 +435,7 @@ contract TestGhoBase is Test, Constants, Events {
address(USDC_TOKEN)
);

USTB_SUBCRIPTION_REDEMPTION = new MockUSTBSubscriptionRedemption(
address(USDC_TOKEN),
address(USDC_TOKEN)
);
USTB_SUBCRIPTION = new MockUSTBSubscription(address(USDC_TOKEN), address(USDC_TOKEN));
}

function ghoFaucet(address to, uint256 amount) public {
Expand Down
94 changes: 94 additions & 0 deletions src/test/mocks/MockUSTBSubscription.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
pragma solidity ^0.8.10;

import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';

/**
* @title MockUSTBSubscription
*/
contract MockUSTBSubscription {
using SafeERC20 for IERC20;

uint256 constant USDC_PRECISION = 1e6;
uint256 constant SUPERSTATE_TOKEN_PRECISION = 1e6;
uint256 constant CHAINLINK_FEED_PRECISION = 1e8;

address public immutable asset;
address public immutable liquidity;
uint256 public USTBPrice; // 1_100_000_000 is 1 USTB = 11 USDC, including chainlink precision

/**
* @param _asset Address of asset token, ie USTB
* @param _liquidity Address of liquidity token, ie USDC
*/
constructor(address _asset, address _liquidity, uint256 _price) {
asset = _asset;
liquidity = _liquidity;
setUSTBPrice(_price);
}

function test_coverage_ignore() public virtual {
// Intentionally left blank.
// Excludes contract from coverage.
}

/**
* @notice The ```subscribe``` function takes in stablecoins and mints SuperstateToken in the proper amount for the msg.sender depending on the current Net Asset Value per Share.
* @param inAmount The amount of the stablecoin in
* @param stablecoin The address of the stablecoin to calculate with
*/
function subscribe(uint256 inAmount, address stablecoin) external {
(
uint256 superstateTokenOutAmount,
uint256 stablecoinInAmountAfterFee,

) = calculateSuperstateTokenOut({inAmount: inAmount, stablecoin: stablecoin});

IERC20(stablecoin).safeTransferFrom({from: msg.sender, to: address(this), value: inAmount});
IERC20(asset).safeTransfer(msg.sender, superstateTokenOutAmount);
}

/**
* @notice The ```calculateSuperstateTokenOut``` function calculates the total amount of Superstate tokens you'll receive for the inAmount of stablecoin. Treats all stablecoins as if they are always worth a dollar.
* @param inAmount The amount of the stablecoin in
* @param stablecoin The address of the stablecoin to calculate with
* @return superstateTokenOutAmount The amount of Superstate tokens received for inAmount of stablecoin
* @return stablecoinInAmountAfterFee The amount of the stablecoin in after any fees
* @return feeOnStablecoinInAmount The amount of the stablecoin taken in fees
*/
function calculateSuperstateTokenOut(
uint256 inAmount,
address stablecoin
)
public
view
returns (
uint256 superstateTokenOutAmount,
uint256 stablecoinInAmountAfterFee,
uint256 feeOnStablecoinInAmount
)
{
StablecoinConfig memory config = supportedStablecoins[stablecoin];

feeOnStablecoinInAmount = 0;
stablecoinInAmountAfterFee = inAmount - feeOnStablecoinInAmount;

usdPerSuperstateTokenChainlinkRaw = USTBPrice; // 9.5 USDC/SUPERSTATE_TOKEN

uint256 stablecoinPrecision = 10 ** 6;
uint256 chainlinkFeedPrecision = 10 ** 8;

// converts from a USD amount to a SUPERSTATE_TOKEN amount
superstateTokenOutAmount =
(stablecoinInAmountAfterFee * chainlinkFeedPrecision * SUPERSTATE_TOKEN_PRECISION) /
(usdPerSuperstateTokenChainlinkRaw * stablecoinPrecision);
}

/**
* @notice Set the price of USTB, amount of USDC for 1 USTB. USTB/USDC both have 6 decimals.
* @param newPrice The new price of USTB
*/
function setUSTBPrice(uint256 newPrice) public {
USTBPrice = newPrice;
}
}
89 changes: 0 additions & 89 deletions src/test/mocks/MockUSTBSubscriptionRedemption.sol

This file was deleted.

0 comments on commit 9209e59

Please sign in to comment.