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

Sky - SUSDS Plugin #1216

Merged
merged 14 commits into from
Nov 6, 2024
7 changes: 7 additions & 0 deletions common/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ export interface ITokens {
USDM?: string
wUSDM?: string

// Sky
USDS?: string
sUSDS?: string

// Aerodrome
AERO?: string
}
Expand Down Expand Up @@ -264,6 +268,8 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
sdUSDCUSDCPlus: '0x9bbF31E99F30c38a5003952206C31EEa77540BeF',
USDe: '0x4c9edd5852cd905f086c759e8383e09bff1e68b3',
sUSDe: '0x9D39A5DE30e57443BfF2A8307A4256c8797A3497',
USDS: '0xdC035D45d973E3EC169d2276DDab16f1e407384F',
sUSDS: '0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD',
},
chainlinkFeeds: {
RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02',
Expand Down Expand Up @@ -293,6 +299,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
pyUSD: '0x8f1dF6D7F2db73eECE86a18b4381F4707b918FB1',
apxETH: '0x19219BC90F48DeE4d5cF202E09c438FAacFd8Bea', // apxETH/ETH
USDe: '0xa569d910839Ae8865Da8F8e70FfFb0cBA869F961',
USDS: '0xfF30586cD0F29eD462364C7e81375FC0C71219b1',
},
AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5',
AAVE_EMISSIONS_MGR: '0xEE56e2B3D491590B5b31738cC34d5232F378a8D5',
Expand Down
28 changes: 28 additions & 0 deletions contracts/plugins/assets/sky/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# SUSDS SSR Collateral Plugin

## Summary

This plugin allows `sUSDS` (Sky) holders to use their tokens as collateral in the Reserve Protocol.

`sUSDS` token represents a tokenized implementation of the Sky Savings Rate for `USDS`, fully compliant with the ERC-4626 standard. It enables real-time share-to-asset conversions, ensuring accurate values even if the system's drip function hasn't been called recently.

These `sUSDS` tokens serve as a digital record of any value accrued to a specific position. The Sky Protocol dynamically and automatically adds USDS tokens to the entire pool of USDS supplied to the module every few seconds, in accordance with the Sky Savings Rate. As a result of the tokens auto-accumulating in the pool over time, the value tends to accrue within the sUSDS being held.

Since it is ERC4626, the redeemable USDS amount can be gotten by dividing `sUSDS.totalAssets()` by `sUSDS.totalSupply()`.
`sUSDS` contract: <https://etherscan.io/address/0xdC035D45d973E3EC169d2276DDab16f1e407384F#code>

Sky Money: https://sky.money/

## Implementation

### Units

| tok | ref | target | UoA |
| ----- | ---- | ------ | --- |
| sUSDS | USDS | USD | USD |

### Functions

#### refPerTok {ref/tok}

`return shiftl_toFix(IERC4626(address(erc20)).convertToAssets(oneShare), -refDecimals, FLOOR);`
21 changes: 21 additions & 0 deletions contracts/plugins/assets/sky/SUSDSCollateral.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import "../ERC4626FiatCollateral.sol";

/**
* @title SUSDS Collateral
* @notice Collateral plugin for the SSR wrapper sUSDS
* tok = SUSDS (transferrable SSR-locked USDS)
* ref = USDS
* tar = USD
* UoA = USD
*/
contract SUSDSCollateral is ERC4626FiatCollateral {
/// @param config.chainlinkFeed {UoA/ref} price of USDS in USD terms
constructor(CollateralConfig memory config, uint192 revenueHiding)
ERC4626FiatCollateral(config, revenueHiding)
{
require(config.defaultThreshold != 0, "defaultThreshold zero");
}
}
18 changes: 18 additions & 0 deletions contracts/plugins/assets/sky/vendor/ISUsds.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

interface ISUsds {
function vow() external view returns (address);

function usdsJoin() external view returns (address);

function usds() external view returns (address);

function ssr() external view returns (uint256);

function chi() external view returns (uint192);

function rho() external view returns (uint64);

function drip() external returns (uint256);
}
87 changes: 87 additions & 0 deletions contracts/plugins/assets/sky/vendor/SUsdcMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

interface VatLike {
function hope(address) external;

function suck(
address,
address,
uint256
) external;
}

interface UsdsJoinLike {
function vat() external view returns (address);

function usds() external view returns (address);

function exit(address, uint256) external;
}

interface UsdsLike {
function transfer(address, uint256) external;

function transferFrom(
address,
address,
uint256
) external;
}

contract SUsdsMock {
// --- Storage Variables ---

// Admin
mapping(address => uint256) public wards;
// ERC20
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
mapping(address => uint256) public nonces;
// Savings yield
uint192 public chi; // The Rate Accumulator [ray]
uint64 public rho; // Time of last drip [unix epoch time]
uint256 public ssr; // The USDS Savings Rate [ray]

// --- Constants ---

// ERC20
string public constant name = "Savings USDS";
string public constant symbol = "sUSDS";
string public constant version = "1";
uint8 public constant decimals = 18;
// Math
uint256 private constant RAY = 10**27;

// --- Immutables ---

// EIP712
bytes32 public constant PERMIT_TYPEHASH =
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
);
// Savings yield
UsdsJoinLike public immutable usdsJoin;
VatLike public immutable vat;
UsdsLike public immutable usds;
address public immutable vow;

constructor(address usdsJoin_, address vow_) {
usdsJoin = UsdsJoinLike(usdsJoin_);
vat = VatLike(UsdsJoinLike(usdsJoin_).vat());
usds = UsdsLike(UsdsJoinLike(usdsJoin_).usds());
vow = vow_;

chi = uint192(RAY);
rho = uint64(block.timestamp);
ssr = RAY;
vat.hope(address(usdsJoin));
wards[msg.sender] = 1;
}

// Mock function to be able to override chi in tests
function setChi(uint192 newValue) external {
chi = newValue;
}
}
6 changes: 4 additions & 2 deletions scripts/addresses/1-tmp-assets-collateral.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
"apxETH": "0x05ffDaAA2aF48e1De1CE34d633db018a28e3B3F5",
"sUSDe": "0x35081Ca24319835e5f759163F7e75eaB753e0b7E",
"pyUSD": "0xa5cde4fB1132daF8f4a0D3140859271208d944E9",
"cUSDTv3": "0x1B2256a88Bb9F2E54cC8D355D3161a2F069a320B"
"cUSDTv3": "0x1B2256a88Bb9F2E54cC8D355D3161a2F069a320B",
"sUSDS": "0xaFf578165bEA370D16d8AC61A4C8c6D435785d58"
},
"erc20s": {
"stkAAVE": "0x4da27a545c0c5B758a6BA100e3a049001de870f5",
Expand Down Expand Up @@ -127,6 +128,7 @@
"apxETH": "0x9Ba021B0a9b958B5E75cE9f6dff97C7eE52cb3E6",
"sUSDe": "0x9D39A5DE30e57443BfF2A8307A4256c8797A3497",
"pyUSD": "0x6c3ea9036406852006290770bedfcaba0e23a0e8",
"cUSDTv3": "0xEB74EC1d4C1DAB412D5d6674F6833FD19d3118Ce"
"cUSDTv3": "0xEB74EC1d4C1DAB412D5d6674F6833FD19d3118Ce",
"sUSDS": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD"
}
}
6 changes: 4 additions & 2 deletions scripts/addresses/mainnet-4.0.0/1-tmp-assets-collateral.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
"assets": {},
"collateral": {
"ETHx": "0x73a36258E6A48D0095D1997Fec7F51e191B4Ec81",
"cUSDTv3": "0x1B2256a88Bb9F2E54cC8D355D3161a2F069a320B"
"cUSDTv3": "0x1B2256a88Bb9F2E54cC8D355D3161a2F069a320B",
"sUSDS": "0xaFf578165bEA370D16d8AC61A4C8c6D435785d58"
},
"erc20s": {
"ETHx": "0xA35b1B31Ce002FBF2058D22F30f95D405200A15b",
"cUSDTv3": "0xEB74EC1d4C1DAB412D5d6674F6833FD19d3118Ce"
"cUSDTv3": "0xEB74EC1d4C1DAB412D5d6674F6833FD19d3118Ce",
"sUSDS": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD"
}
}
3 changes: 2 additions & 1 deletion scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ async function main() {
'phase2-assets/collaterals/deploy_USDe.ts',
'phase2-assets/assets/deploy_crv.ts',
'phase2-assets/assets/deploy_cvx.ts',
'phase2-assets/collaterals/deploy_pyusd.ts'
'phase2-assets/collaterals/deploy_pyusd.ts',
'phase2-assets/collaterals/deploy_sky_susds.ts'
)
} else if (chainId == '8453' || chainId == '84531') {
// Base L2 chains
Expand Down
85 changes: 85 additions & 0 deletions scripts/deployment/phase2-assets/collaterals/deploy_sky_susds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import fs from 'fs'
import hre from 'hardhat'
import { getChainId } from '../../../../common/blockchain-utils'
import { networkConfig } from '../../../../common/configuration'
import { bn, fp } from '../../../../common/numbers'
import { expect } from 'chai'
import { CollateralStatus } from '../../../../common/constants'
import {
getDeploymentFile,
getAssetCollDeploymentFilename,
IAssetCollDeployments,
getDeploymentFilename,
fileExists,
} from '../../common'
import { priceTimeout } from '../../utils'
import { SUSDSCollateral } from '../../../../typechain'
import { ContractFactory } from 'ethers'
import { ORACLE_ERROR, ORACLE_TIMEOUT } from '#/test/plugins/individual-collateral/sky/constants'

async function main() {
// ==== Read Configuration ====
const [deployer] = await hre.ethers.getSigners()

const chainId = await getChainId(hre)

console.log(`Deploying Collateral to network ${hre.network.name} (${chainId})
with burner account: ${deployer.address}`)

if (!networkConfig[chainId]) {
throw new Error(`Missing network configuration for ${hre.network.name}`)
}

// Get phase1 deployment
const phase1File = getDeploymentFilename(chainId)
if (!fileExists(phase1File)) {
throw new Error(`${phase1File} doesn't exist yet. Run phase 1`)
}
// Check previous step completed
const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId)
const assetCollDeployments = <IAssetCollDeployments>getDeploymentFile(assetCollDeploymentFilename)

const deployedCollateral: string[] = []

/******** Deploy SUSDS Collateral - sUSDS **************************/

const SUSDSCollateralFactory: ContractFactory = await hre.ethers.getContractFactory(
'SUSDSCollateral'
)

const collateral = <SUSDSCollateral>await SUSDSCollateralFactory.connect(deployer).deploy(
{
priceTimeout: priceTimeout.toString(),
chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDS,
oracleError: ORACLE_ERROR.toString(), // 0.3%
erc20: networkConfig[chainId].tokens.sUSDS,
maxTradeVolume: fp('1e6').toString(), // $1m,
oracleTimeout: ORACLE_TIMEOUT.toString(), // 24h
targetName: hre.ethers.utils.formatBytes32String('USD'),
defaultThreshold: ORACLE_ERROR.add(fp('0.01')).toString(), // 1.3%
delayUntilDefault: bn('86400').toString(), // 24h
},
bn(0)
)
await collateral.deployed()

console.log(`Deployed sUSDS to ${hre.network.name} (${chainId}): ${collateral.address}`)

await (await collateral.refresh()).wait()
expect(await collateral.status()).to.equal(CollateralStatus.SOUND)

assetCollDeployments.collateral.sUSDS = collateral.address
assetCollDeployments.erc20s.sUSDS = networkConfig[chainId].tokens.sUSDS
deployedCollateral.push(collateral.address.toString())

fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2))

console.log(`Deployed collateral to ${hre.network.name} (${chainId})
New deployments: ${deployedCollateral}
Deployment file: ${assetCollDeploymentFilename}`)
}

main().catch((error) => {
console.error(error)
process.exitCode = 1
})
57 changes: 57 additions & 0 deletions scripts/verification/collateral-plugins/verify_susds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import hre from 'hardhat'
import { getChainId } from '../../../common/blockchain-utils'
import { developmentChains, networkConfig } from '../../../common/configuration'
import { fp, bn } from '../../../common/numbers'
import {
getDeploymentFile,
getAssetCollDeploymentFilename,
IAssetCollDeployments,
} from '../../deployment/common'
import { priceTimeout, verifyContract } from '../../deployment/utils'
import {
ORACLE_ERROR,
ORACLE_TIMEOUT,
} from '../../../test/plugins/individual-collateral/sky/constants'

let deployments: IAssetCollDeployments

async function main() {
// ********** Read config **********
const chainId = await getChainId(hre)
if (!networkConfig[chainId]) {
throw new Error(`Missing network configuration for ${hre.network.name}`)
}

if (developmentChains.includes(hre.network.name)) {
throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`)
}

const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId)
deployments = <IAssetCollDeployments>getDeploymentFile(assetCollDeploymentFilename)

/******** Verify sUSDS **************************/
await verifyContract(
chainId,
deployments.collateral.sUSDS,
[
{
priceTimeout: priceTimeout.toString(),
chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDS,
oracleError: ORACLE_ERROR.toString(), // 0.3%
erc20: networkConfig[chainId].tokens.sUSDS,
maxTradeVolume: fp('1e6').toString(), // $1m,
oracleTimeout: ORACLE_TIMEOUT.toString(), // 24h
targetName: hre.ethers.utils.formatBytes32String('USD'),
defaultThreshold: ORACLE_ERROR.add(fp('0.01')).toString(), // 1.3%
delayUntilDefault: bn('86400').toString(), // 24h
},
bn(0),
],
'contracts/plugins/assets/sky/SUSDSCollateral.sol:SUSDSCollateral'
)
}

main().catch((error) => {
console.error(error)
process.exitCode = 1
})
3 changes: 2 additions & 1 deletion scripts/verify_etherscan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ async function main() {
'collateral-plugins/verify_ethx.ts',
'collateral-plugins/verify_apxeth.ts',
'collateral-plugins/verify_USDe.ts',
'collateral-plugins/verify_pyusd.ts'
'collateral-plugins/verify_pyusd.ts',
'collateral-plugins/verify_susds.ts'
)
} else if (chainId == '8453' || chainId == '84531') {
// Base L2 chains
Expand Down
2 changes: 1 addition & 1 deletion test/integration/fork-block-numbers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const forkBlockNumber = {
'mainnet-3.4.0': 20328530, // Ethereum

// TODO add all the block numbers we fork from to benefit from caching
default: 20679946, // Ethereum
default: 20890018, // Ethereum
}

export default forkBlockNumber
Loading
Loading