Skip to content

Commit c727c40

Browse files
julianmrodritbrent
andauthored
Sky - SUSDS Plugin (#1216)
Co-authored-by: Taylor Brent <taylor.w.brent@gmail.com>
1 parent cda7d8a commit c727c40

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+601
-58
lines changed

common/configuration.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ export interface ITokens {
119119
USDM?: string
120120
wUSDM?: string
121121

122+
// Sky
123+
USDS?: string
124+
sUSDS?: string
125+
122126
// Aerodrome
123127
AERO?: string
124128
}
@@ -265,6 +269,8 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
265269
sdUSDCUSDCPlus: '0x9bbF31E99F30c38a5003952206C31EEa77540BeF',
266270
USDe: '0x4c9edd5852cd905f086c759e8383e09bff1e68b3',
267271
sUSDe: '0x9D39A5DE30e57443BfF2A8307A4256c8797A3497',
272+
USDS: '0xdC035D45d973E3EC169d2276DDab16f1e407384F',
273+
sUSDS: '0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD',
268274
},
269275
chainlinkFeeds: {
270276
RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02',
@@ -294,6 +300,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
294300
pyUSD: '0x8f1dF6D7F2db73eECE86a18b4381F4707b918FB1',
295301
apxETH: '0x19219BC90F48DeE4d5cF202E09c438FAacFd8Bea', // apxETH/ETH
296302
USDe: '0xa569d910839Ae8865Da8F8e70FfFb0cBA869F961',
303+
USDS: '0xfF30586cD0F29eD462364C7e81375FC0C71219b1',
297304
},
298305
AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5',
299306
AAVE_EMISSIONS_MGR: '0xEE56e2B3D491590B5b31738cC34d5232F378a8D5',
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# SUSDS SSR Collateral Plugin
2+
3+
## Summary
4+
5+
This plugin allows `sUSDS` (Sky) holders to use their tokens as collateral in the Reserve Protocol.
6+
7+
`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.
8+
9+
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.
10+
11+
Since it is ERC4626, the redeemable USDS amount can be gotten by dividing `sUSDS.totalAssets()` by `sUSDS.totalSupply()`.
12+
`sUSDS` contract: <https://etherscan.io/address/0xdC035D45d973E3EC169d2276DDab16f1e407384F#code>
13+
14+
Sky Money: https://sky.money/
15+
16+
## Implementation
17+
18+
### Units
19+
20+
| tok | ref | target | UoA |
21+
| ----- | ---- | ------ | --- |
22+
| sUSDS | USDS | USD | USD |
23+
24+
### Functions
25+
26+
#### refPerTok {ref/tok}
27+
28+
`return shiftl_toFix(IERC4626(address(erc20)).convertToAssets(oneShare), -refDecimals, FLOOR);`
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// SPDX-License-Identifier: BlueOak-1.0.0
2+
pragma solidity 0.8.19;
3+
4+
import "../ERC4626FiatCollateral.sol";
5+
6+
/**
7+
* @title SUSDS Collateral
8+
* @notice Collateral plugin for the SSR wrapper sUSDS
9+
* tok = SUSDS (transferrable SSR-locked USDS)
10+
* ref = USDS
11+
* tar = USD
12+
* UoA = USD
13+
*/
14+
contract SUSDSCollateral is ERC4626FiatCollateral {
15+
/// @param config.chainlinkFeed {UoA/ref} price of USDS in USD terms
16+
constructor(CollateralConfig memory config, uint192 revenueHiding)
17+
ERC4626FiatCollateral(config, revenueHiding)
18+
{
19+
require(config.defaultThreshold != 0, "defaultThreshold zero");
20+
}
21+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: BlueOak-1.0.0
2+
pragma solidity 0.8.19;
3+
4+
interface ISUsds {
5+
function vow() external view returns (address);
6+
7+
function usdsJoin() external view returns (address);
8+
9+
function usds() external view returns (address);
10+
11+
function ssr() external view returns (uint256);
12+
13+
function chi() external view returns (uint192);
14+
15+
function rho() external view returns (uint64);
16+
17+
function drip() external returns (uint256);
18+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// SPDX-License-Identifier: BlueOak-1.0.0
2+
pragma solidity 0.8.19;
3+
4+
interface VatLike {
5+
function hope(address) external;
6+
7+
function suck(
8+
address,
9+
address,
10+
uint256
11+
) external;
12+
}
13+
14+
interface UsdsJoinLike {
15+
function vat() external view returns (address);
16+
17+
function usds() external view returns (address);
18+
19+
function exit(address, uint256) external;
20+
}
21+
22+
interface UsdsLike {
23+
function transfer(address, uint256) external;
24+
25+
function transferFrom(
26+
address,
27+
address,
28+
uint256
29+
) external;
30+
}
31+
32+
contract SUsdsMock {
33+
// --- Storage Variables ---
34+
35+
// Admin
36+
mapping(address => uint256) public wards;
37+
// ERC20
38+
uint256 public totalSupply;
39+
mapping(address => uint256) public balanceOf;
40+
mapping(address => mapping(address => uint256)) public allowance;
41+
mapping(address => uint256) public nonces;
42+
// Savings yield
43+
uint192 public chi; // The Rate Accumulator [ray]
44+
uint64 public rho; // Time of last drip [unix epoch time]
45+
uint256 public ssr; // The USDS Savings Rate [ray]
46+
47+
// --- Constants ---
48+
49+
// ERC20
50+
string public constant name = "Savings USDS";
51+
string public constant symbol = "sUSDS";
52+
string public constant version = "1";
53+
uint8 public constant decimals = 18;
54+
// Math
55+
uint256 private constant RAY = 10**27;
56+
57+
// --- Immutables ---
58+
59+
// EIP712
60+
bytes32 public constant PERMIT_TYPEHASH =
61+
keccak256(
62+
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
63+
);
64+
// Savings yield
65+
UsdsJoinLike public immutable usdsJoin;
66+
VatLike public immutable vat;
67+
UsdsLike public immutable usds;
68+
address public immutable vow;
69+
70+
constructor(address usdsJoin_, address vow_) {
71+
usdsJoin = UsdsJoinLike(usdsJoin_);
72+
vat = VatLike(UsdsJoinLike(usdsJoin_).vat());
73+
usds = UsdsLike(UsdsJoinLike(usdsJoin_).usds());
74+
vow = vow_;
75+
76+
chi = uint192(RAY);
77+
rho = uint64(block.timestamp);
78+
ssr = RAY;
79+
vat.hope(address(usdsJoin));
80+
wards[msg.sender] = 1;
81+
}
82+
83+
// Mock function to be able to override chi in tests
84+
function setChi(uint192 newValue) external {
85+
chi = newValue;
86+
}
87+
}

scripts/addresses/1-tmp-assets-collateral.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@
6363
"apxETH": "0x05ffDaAA2aF48e1De1CE34d633db018a28e3B3F5",
6464
"sUSDe": "0x35081Ca24319835e5f759163F7e75eaB753e0b7E",
6565
"pyUSD": "0xa5cde4fB1132daF8f4a0D3140859271208d944E9",
66-
"cUSDTv3": "0x1B2256a88Bb9F2E54cC8D355D3161a2F069a320B"
66+
"cUSDTv3": "0x1B2256a88Bb9F2E54cC8D355D3161a2F069a320B",
67+
"sUSDS": "0xaFf578165bEA370D16d8AC61A4C8c6D435785d58"
6768
},
6869
"erc20s": {
6970
"stkAAVE": "0x4da27a545c0c5B758a6BA100e3a049001de870f5",
@@ -127,6 +128,7 @@
127128
"apxETH": "0x9Ba021B0a9b958B5E75cE9f6dff97C7eE52cb3E6",
128129
"sUSDe": "0x9D39A5DE30e57443BfF2A8307A4256c8797A3497",
129130
"pyUSD": "0x6c3ea9036406852006290770bedfcaba0e23a0e8",
130-
"cUSDTv3": "0xEB74EC1d4C1DAB412D5d6674F6833FD19d3118Ce"
131+
"cUSDTv3": "0xEB74EC1d4C1DAB412D5d6674F6833FD19d3118Ce",
132+
"sUSDS": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD"
131133
}
132134
}

scripts/addresses/mainnet-4.0.0/1-tmp-assets-collateral.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
"assets": {},
33
"collateral": {
44
"ETHx": "0x73a36258E6A48D0095D1997Fec7F51e191B4Ec81",
5-
"cUSDTv3": "0x1B2256a88Bb9F2E54cC8D355D3161a2F069a320B"
5+
"cUSDTv3": "0x1B2256a88Bb9F2E54cC8D355D3161a2F069a320B",
6+
"sUSDS": "0xaFf578165bEA370D16d8AC61A4C8c6D435785d58"
67
},
78
"erc20s": {
89
"ETHx": "0xA35b1B31Ce002FBF2058D22F30f95D405200A15b",
9-
"cUSDTv3": "0xEB74EC1d4C1DAB412D5d6674F6833FD19d3118Ce"
10+
"cUSDTv3": "0xEB74EC1d4C1DAB412D5d6674F6833FD19d3118Ce",
11+
"sUSDS": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD"
1012
}
1113
}

scripts/deploy.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ async function main() {
9696
'phase2-assets/collaterals/deploy_USDe.ts',
9797
'phase2-assets/assets/deploy_crv.ts',
9898
'phase2-assets/assets/deploy_cvx.ts',
99-
'phase2-assets/collaterals/deploy_pyusd.ts'
99+
'phase2-assets/collaterals/deploy_pyusd.ts',
100+
'phase2-assets/collaterals/deploy_sky_susds.ts'
100101
)
101102
} else if (chainId == '8453' || chainId == '84531') {
102103
// Base L2 chains
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import fs from 'fs'
2+
import hre from 'hardhat'
3+
import { getChainId } from '../../../../common/blockchain-utils'
4+
import { networkConfig } from '../../../../common/configuration'
5+
import { bn, fp } from '../../../../common/numbers'
6+
import { expect } from 'chai'
7+
import { CollateralStatus } from '../../../../common/constants'
8+
import {
9+
getDeploymentFile,
10+
getAssetCollDeploymentFilename,
11+
IAssetCollDeployments,
12+
getDeploymentFilename,
13+
fileExists,
14+
} from '../../common'
15+
import { priceTimeout } from '../../utils'
16+
import { SUSDSCollateral } from '../../../../typechain'
17+
import { ContractFactory } from 'ethers'
18+
import { ORACLE_ERROR, ORACLE_TIMEOUT } from '#/test/plugins/individual-collateral/sky/constants'
19+
20+
async function main() {
21+
// ==== Read Configuration ====
22+
const [deployer] = await hre.ethers.getSigners()
23+
24+
const chainId = await getChainId(hre)
25+
26+
console.log(`Deploying Collateral to network ${hre.network.name} (${chainId})
27+
with burner account: ${deployer.address}`)
28+
29+
if (!networkConfig[chainId]) {
30+
throw new Error(`Missing network configuration for ${hre.network.name}`)
31+
}
32+
33+
// Get phase1 deployment
34+
const phase1File = getDeploymentFilename(chainId)
35+
if (!fileExists(phase1File)) {
36+
throw new Error(`${phase1File} doesn't exist yet. Run phase 1`)
37+
}
38+
// Check previous step completed
39+
const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId)
40+
const assetCollDeployments = <IAssetCollDeployments>getDeploymentFile(assetCollDeploymentFilename)
41+
42+
const deployedCollateral: string[] = []
43+
44+
/******** Deploy SUSDS Collateral - sUSDS **************************/
45+
46+
const SUSDSCollateralFactory: ContractFactory = await hre.ethers.getContractFactory(
47+
'SUSDSCollateral'
48+
)
49+
50+
const collateral = <SUSDSCollateral>await SUSDSCollateralFactory.connect(deployer).deploy(
51+
{
52+
priceTimeout: priceTimeout.toString(),
53+
chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDS,
54+
oracleError: ORACLE_ERROR.toString(), // 0.3%
55+
erc20: networkConfig[chainId].tokens.sUSDS,
56+
maxTradeVolume: fp('1e6').toString(), // $1m,
57+
oracleTimeout: ORACLE_TIMEOUT.toString(), // 24h
58+
targetName: hre.ethers.utils.formatBytes32String('USD'),
59+
defaultThreshold: ORACLE_ERROR.add(fp('0.01')).toString(), // 1.3%
60+
delayUntilDefault: bn('86400').toString(), // 24h
61+
},
62+
bn(0)
63+
)
64+
await collateral.deployed()
65+
66+
console.log(`Deployed sUSDS to ${hre.network.name} (${chainId}): ${collateral.address}`)
67+
68+
await (await collateral.refresh()).wait()
69+
expect(await collateral.status()).to.equal(CollateralStatus.SOUND)
70+
71+
assetCollDeployments.collateral.sUSDS = collateral.address
72+
assetCollDeployments.erc20s.sUSDS = networkConfig[chainId].tokens.sUSDS
73+
deployedCollateral.push(collateral.address.toString())
74+
75+
fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2))
76+
77+
console.log(`Deployed collateral to ${hre.network.name} (${chainId})
78+
New deployments: ${deployedCollateral}
79+
Deployment file: ${assetCollDeploymentFilename}`)
80+
}
81+
82+
main().catch((error) => {
83+
console.error(error)
84+
process.exitCode = 1
85+
})
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import hre from 'hardhat'
2+
import { getChainId } from '../../../common/blockchain-utils'
3+
import { developmentChains, networkConfig } from '../../../common/configuration'
4+
import { fp, bn } from '../../../common/numbers'
5+
import {
6+
getDeploymentFile,
7+
getAssetCollDeploymentFilename,
8+
IAssetCollDeployments,
9+
} from '../../deployment/common'
10+
import { priceTimeout, verifyContract } from '../../deployment/utils'
11+
import {
12+
ORACLE_ERROR,
13+
ORACLE_TIMEOUT,
14+
} from '../../../test/plugins/individual-collateral/sky/constants'
15+
16+
let deployments: IAssetCollDeployments
17+
18+
async function main() {
19+
// ********** Read config **********
20+
const chainId = await getChainId(hre)
21+
if (!networkConfig[chainId]) {
22+
throw new Error(`Missing network configuration for ${hre.network.name}`)
23+
}
24+
25+
if (developmentChains.includes(hre.network.name)) {
26+
throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`)
27+
}
28+
29+
const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId)
30+
deployments = <IAssetCollDeployments>getDeploymentFile(assetCollDeploymentFilename)
31+
32+
/******** Verify sUSDS **************************/
33+
await verifyContract(
34+
chainId,
35+
deployments.collateral.sUSDS,
36+
[
37+
{
38+
priceTimeout: priceTimeout.toString(),
39+
chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDS,
40+
oracleError: ORACLE_ERROR.toString(), // 0.3%
41+
erc20: networkConfig[chainId].tokens.sUSDS,
42+
maxTradeVolume: fp('1e6').toString(), // $1m,
43+
oracleTimeout: ORACLE_TIMEOUT.toString(), // 24h
44+
targetName: hre.ethers.utils.formatBytes32String('USD'),
45+
defaultThreshold: ORACLE_ERROR.add(fp('0.01')).toString(), // 1.3%
46+
delayUntilDefault: bn('86400').toString(), // 24h
47+
},
48+
bn(0),
49+
],
50+
'contracts/plugins/assets/sky/SUSDSCollateral.sol:SUSDSCollateral'
51+
)
52+
}
53+
54+
main().catch((error) => {
55+
console.error(error)
56+
process.exitCode = 1
57+
})

scripts/verify_etherscan.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ async function main() {
8181
'collateral-plugins/verify_ethx.ts',
8282
'collateral-plugins/verify_apxeth.ts',
8383
'collateral-plugins/verify_USDe.ts',
84-
'collateral-plugins/verify_pyusd.ts'
84+
'collateral-plugins/verify_pyusd.ts',
85+
'collateral-plugins/verify_susds.ts'
8586
)
8687
} else if (chainId == '8453' || chainId == '84531') {
8788
// Base L2 chains

test/integration/fork-block-numbers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const forkBlockNumber = {
1111
'mainnet-3.4.0': 20328530, // Ethereum
1212

1313
// TODO add all the block numbers we fork from to benefit from caching
14-
default: 20679946, // Ethereum
14+
default: 20890018, // Ethereum
1515
}
1616

1717
export default forkBlockNumber

0 commit comments

Comments
 (0)