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

Stargate Asset Plugin #863

Merged
merged 11 commits into from
Jul 18, 2023
8 changes: 3 additions & 5 deletions .github/workflows/4bytes.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Tests
name: Sync 4byte Signatures

on:
push:
Expand All @@ -9,9 +9,8 @@ on:
- closed

jobs:
deployment-scripts:
if: github.event.pull_request.merged == true
name: '4Bytes Sync'
sync-signatures:
name: '4byte Sync'
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -31,4 +30,3 @@ jobs:
commit_message: 4bytes-syncced.json
commit_options: '--no-verify --signoff'
file_pattern: 'scripts/4bytes-syncced.json'

22 changes: 4 additions & 18 deletions .openzeppelin/goerli-old.json
Original file line number Diff line number Diff line change
Expand Up @@ -858,10 +858,7 @@
},
"t_enum(TradeKind)20270": {
"label": "enum TradeKind",
"members": [
"DUTCH_AUCTION",
"BATCH_AUCTION"
],
"members": ["DUTCH_AUCTION", "BATCH_AUCTION"],
"numberOfBytes": "1"
},
"t_mapping(t_contract(IERC20)11487,t_contract(ITrade)22280)": {
Expand Down Expand Up @@ -1162,11 +1159,7 @@
},
"t_enum(CollateralStatus)9907": {
"label": "enum CollateralStatus",
"members": [
"SOUND",
"IFFY",
"DISABLED"
],
"members": ["SOUND", "IFFY", "DISABLED"],
"numberOfBytes": "1"
},
"t_mapping(t_bytes32,t_bytes32)": {
Expand Down Expand Up @@ -3959,10 +3952,7 @@
},
"t_enum(TradeKind)21047": {
"label": "enum TradeKind",
"members": [
"DUTCH_AUCTION",
"BATCH_AUCTION"
],
"members": ["DUTCH_AUCTION", "BATCH_AUCTION"],
"numberOfBytes": "1"
},
"t_mapping(t_contract(IERC20)11635,t_contract(ITrade)23084)": {
Expand Down Expand Up @@ -4255,11 +4245,7 @@
},
"t_enum(CollateralStatus)20505": {
"label": "enum CollateralStatus",
"members": [
"SOUND",
"IFFY",
"DISABLED"
],
"members": ["SOUND", "IFFY", "DISABLED"],
"numberOfBytes": "1"
},
"t_mapping(t_bytes32,t_bytes32)": {
Expand Down
2,314 changes: 1,245 additions & 1,069 deletions audits/Code4rena Reserve Audit Report.md

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions common/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export interface ITokens {
ONDO?: string
sDAI?: string
cbETH?: string
STG?: string
sUSDC?: string
sUSDT?: string
sETH?: string
}

export interface IFeeds {
Expand Down Expand Up @@ -139,6 +143,10 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
ONDO: '0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3',
sDAI: '0x83f20f44975d03b1b09e64809b757c47f942beea',
cbETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704',
STG: '0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6',
sUSDC: '0xdf0770dF86a8034b3EFEf0A1Bb3c889B8332FF56',
sUSDT: '0x38EA452219524Bb87e18dE1C24D3bB59510BD783',
sETH: '0x101816545F6bd2b1076434B54383a1E633390A2E',
},
chainlinkFeeds: {
RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02',
Expand Down Expand Up @@ -231,6 +239,10 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
ONDO: '0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3',
sDAI: '0x83f20f44975d03b1b09e64809b757c47f942beea',
cbETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704',
STG: '0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6',
sUSDC: '0xdf0770dF86a8034b3EFEf0A1Bb3c889B8332FF56',
sUSDT: '0x38EA452219524Bb87e18dE1C24D3bB59510BD783',
sETH: '0x101816545F6bd2b1076434B54383a1E633390A2E',
},
chainlinkFeeds: {
RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02',
Expand Down
7 changes: 6 additions & 1 deletion contracts/libraries/test/FixedCallerMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,12 @@ contract FixedCallerMock {
) public pure returns (uint192) {
return FixLib.safeMul(a, b, rnd);
}
function safeDiv_(uint192 a, uint192 b, RoundingMode rnd) public pure returns (uint192) {

function safeDiv_(
uint192 a,
uint192 b,
RoundingMode rnd
) public pure returns (uint192) {
return FixLib.safeDiv(a, b, rnd);
}

Expand Down
61 changes: 61 additions & 0 deletions contracts/plugins/assets/stargate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Stargate Finance as collateral

This documentation outlines the configuration and deployment of the Collateral Plugin, which is designed to enable Stargate Liquidity pool tokens as Collateral for an Token. It also defines the collateral token, reference unit, and target unit, and provides guidelines for integrating price feeds.

The Stargate Finance documentation will be a good starting point to understand this plugin. Some quick links:

- [Stargate User documentation](https://stargateprotocol.gitbook.io/stargate/v/user-docs/)
- [Stargate Developer documentation](https://stargateprotocol.gitbook.io/stargate/)
- [Stargate LP Staking contract source code](https://etherscan.io/address/0xB0D502E938ed5f4df2E681fE6E419ff29631d62b#code#F1#L1)

> Users can add liquidity to token-chain pools (i.e. USDC-Ethereum) and receive either farm-based or transfer-based rewards
>
> _[From The Stargate User Docs](https://stargateprotocol.gitbook.io/stargate/v/user-docs/stargate-features/pool#overview)_

These rewards are added to the total available liquidity, thereby increasing the amount of the underlying token the LP token can be redeemed for. Users can also further stake their LP tokens with the [LPStaking](https://github.com/stargate-protocol/stargate/blob/main/contracts/LPStaking.sol) contract to receive $STG rewards.

It's therefore required to have a wrapper token that'll automatically stake and collect these rewards so it can be used as additional revenue for the collateral.

## Wrapper Token for Automatic Staking and Reward Collection

The wrapper token for automatic staking and reward collection is a smart contract that enables LP token holders to earn rewards automatically without the need to manually stake and collect rewards. It works by automatically staking the LP tokens deposited into the contract, and then collecting rewards in the form of additional tokens, which are then distributed proportionally to the token holders.

### Methods

The wrapper token has all ERC20 methods with the following additions:

- `deposit(uint amount)`: Allows users to deposit LP tokens into the wrapper token contract. The amount parameter specifies the number of LP tokens to deposit.
- `withdraw(uint amount)`: Allows users to withdraw LP tokens from the wrapper token contract. The amount parameter specifies the number of LP tokens to withdraw.

### Usage

To use the wrapper token for automatic staking and reward collection, follow these steps:

1. Obtain LP tokens from the Stargate Finance protocol.
2. Approve the wrapper token contract to spend your LP tokens by calling the `approve` function on the LP token contract with the wrapper token contract address as the parameter.
3. Deposit your LP tokens into the wrapper token contract by calling the `deposit` function with the amount parameter set to the number of LP tokens you wish to deposit.
4. The tokens are automatically staked and rewards start to accrue.
5. Withdraw your LP tokens from the wrapper token contract by calling the `withdraw` function with the amount parameter set to the number of LP tokens you wish to withdraw.

### Notes

- Pending rewards are automatically collected upon deposit and withdrawal.
- Token transfers don't transfer pending rewards along with the token.
- Always verify the contract address before interacting with it to avoid phishing attacks.

## Collateral plugin

There are 2 variants of this plugin that can be deployed.

1. **`StargatePoolFiatCollateral`**: This contract serves for the USDC, USDT and any other USD-pegged token. The target for these collaterals is **USD**.
2. **`StargatePoolETHCollateral`**: This contract serves the ETH pool. The underlying token for the Stargate ETH pool is SGETH which is mapped 1:1 with ETH. The chainlink feed that will then be provided during deployment would be an ETH-USD oracle. The target for this collateral is **ETH**.

The **`{ref/tok}`** is computed as the ratio of the total liquidity to the total LP tokens in circulation. This ratio never drops except for a very rare occasion where the pool's total supply drops to zero, in which case the **`{ref/tok}`** falls back to 1 and the plugin will default under such circumstances.

### Acounting units

| Unit | Description |
| :------------------- | ---------------------------------------------------------- |
| **Collateral token** | The wrapper token deployed for that pool. |
| **Target unit** | `USD` for the USD-pegged pools and `ETH` for the ETH pool. |
| **Reference unit** | The pool's underlying token. |
50 changes: 50 additions & 0 deletions contracts/plugins/assets/stargate/StargatePoolETHCollateral.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import "../../../libraries/Fixed.sol";
import "../OracleLib.sol";
import "./interfaces/IStargatePool.sol";
import "./interfaces/IStargatePoolWrapper.sol";
import "./StargatePoolFiatCollateral.sol";

contract StargatePoolETHCollateral is StargatePoolFiatCollateral {
using FixLib for uint192;
using OracleLib for AggregatorV3Interface;

/// @param config.chainlinkFeed Feed units: {UoA/target}
// solhint-disable no-empty-blocks
constructor(CollateralConfig memory config) StargatePoolFiatCollateral(config) {}

/// Can revert, used by other contract functions in order to catch errors
/// Should not return FIX_MAX for low
/// Should only return FIX_MAX for high if low is 0
/// @dev Override this when pricing is more complicated than just a single oracle
/// @param low {UoA/tok} The low price estimate
/// @param high {UoA/tok} The high price estimate
/// @param pegPrice {target/ref} The actual price observed in the peg
function tryPrice()
external
view
virtual
override
returns (
uint192 low,
uint192 high,
uint192 pegPrice
)
{
// Assumption: {target/ref} = 1; SGETH unwraps to ETH at 1:1
pegPrice = FIX_ONE; // {target/ref}

// {UoA/target}
uint192 pricePerTarget = chainlinkFeed.price(oracleTimeout);

// {UoA/tok} = {UoA/target} * {ref/tok} * {target/ref} (1)
uint192 p = pricePerTarget.mul(refPerTok());

// this oracleError is already the combined total oracle error
uint192 delta = p.mul(oracleError);
low = p - delta;
high = p + delta;
}
}
121 changes: 121 additions & 0 deletions contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import "../../../libraries/Fixed.sol";
import "../OracleLib.sol";
import "../FiatCollateral.sol";
import "./interfaces/IStargatePool.sol";
import "./interfaces/IStargatePoolWrapper.sol";

contract StargatePoolFiatCollateral is FiatCollateral {
using FixLib for uint192;
using OracleLib for AggregatorV3Interface;

// does not become nonzero until after first refresh()
uint192 public lastReferencePrice; // {ref/tok} last ref price observed

/// @param config.chainlinkFeed Feed units: {UoA/ref}
// solhint-disable no-empty-blocks
constructor(CollateralConfig memory config) FiatCollateral(config) {}

/// Can revert, used by other contract functions in order to catch errors
/// Should not return FIX_MAX for low
/// Should only return FIX_MAX for high if low is 0
/// @dev Override this when pricing is more complicated than just a single oracle
/// @param low {UoA/tok} The low price estimate
/// @param high {UoA/tok} The high price estimate
/// @param pegPrice {target/tok} The actual price observed in the peg
function tryPrice()
external
view
virtual
override
returns (
uint192 low,
uint192 high,
uint192 pegPrice
)
{
pegPrice = chainlinkFeed.price(oracleTimeout); // {target/ref}

// Assumption: {UoA/target} = 1; target is same as UoA
// {UoA/tok} = {UoA/target} * {target/ref} * {ref/tok}
uint192 p = pegPrice.mul(refPerTok());

// {UoA/tok} = {UoA/tok} * {1}
uint192 delta = p.mul(oracleError);

low = p - delta;
high = p + delta;
}

/// Should not revert
/// Refresh exchange rates and update default status.
/// @dev Should not need to override: can handle collateral with variable refPerTok()
function refresh() public virtual override {
if (alreadyDefaulted()) return;

CollateralStatus oldStatus = status();

// Check for hard default

uint192 referencePrice = refPerTok();
uint192 lastReferencePrice_ = lastReferencePrice;

// uint192(<) is equivalent to Fix.lt
if (referencePrice < lastReferencePrice_) {
markStatus(CollateralStatus.DISABLED);
} else if (referencePrice > lastReferencePrice_) {
lastReferencePrice = referencePrice;
}

// Check for soft default + save prices
try this.tryPrice() returns (uint192 low, uint192 high, uint192 pegPrice) {
// {UoA/tok}, {UoA/tok}, {target/ref}
// (0, 0) is a valid price; (0, FIX_MAX) is unpriced

// Save prices if priced
if (high < FIX_MAX) {
savedLowPrice = low;
savedHighPrice = high;
lastSave = uint48(block.timestamp);
} else {
// must be unpriced
assert(low == 0);
}

// If the price is below the default-threshold price, default eventually
// uint192(+/-) is the same as Fix.plus/minus
if (pegPrice < pegBottom || pegPrice > pegTop || low == 0) {
markStatus(CollateralStatus.IFFY);
} else {
markStatus(CollateralStatus.SOUND);
}
} catch (bytes memory errData) {
// see: docs/solidity-style.md#Catching-Empty-Data
if (errData.length == 0) revert(); // solhint-disable-line reason-string
markStatus(CollateralStatus.IFFY);
}

CollateralStatus newStatus = status();
if (oldStatus != newStatus) {
emit CollateralStatusChanged(oldStatus, newStatus);
}
}

/// @return _rate {ref/tok} Quantity of whole reference units per whole collateral tokens
function refPerTok() public view virtual override returns (uint192 _rate) {
IStargatePoolWrapper poolWrapper = IStargatePoolWrapper(address(erc20));
IStargatePool pool = IStargatePool(address(poolWrapper.pool()));
akshatmittal marked this conversation as resolved.
Show resolved Hide resolved
uint256 _totalSupply = pool.totalSupply();
akshatmittal marked this conversation as resolved.
Show resolved Hide resolved
if (_totalSupply != 0) {
_rate = shiftl_toFix(pool.totalLiquidity(), -int8(erc20Decimals)).div(
shiftl_toFix(_totalSupply, -int8(erc20Decimals))
);
}
}

function claimRewards() external override(Asset, IRewardable) {
IStargatePoolWrapper(address(erc20)).claimRewards();
}
}
Loading