Skip to content
This repository has been archived by the owner on Apr 22, 2024. It is now read-only.

Add USDT as a new collateral type #201

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
362 changes: 70 additions & 292 deletions src/DssSpell.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,88 +13,29 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity 0.8.16;

pragma solidity 0.6.12;
// Enable ABIEncoderV2 when onboarding collateral through `DssExecLib.addNewCollateral()`
// pragma experimental ABIEncoderV2;
import "dss-exec-lib/DssExec.sol";
import "dss-exec-lib/DssAction.sol";
import { CollateralOpts } from "dss-exec-lib/CollateralOpts.sol";
import "dss-interfaces/dss/IlkRegistryAbstract.sol";
import "dss-interfaces/dss/GemJoinAbstract.sol";
import "dss-interfaces/dss/MedianAbstract.sol";
import "dss-interfaces/ERC/GemAbstract.sol";

interface Initializable {
function init(bytes32 ilk) external;
}

interface VatLike {
function ilks(bytes32 ilk) external view returns (uint256 Art, uint256 rate, uint256 spot, uint256 line, uint256 dust);
}

interface RwaLiquidationLike {
function ilks(bytes32) external view returns (string memory doc, address pip, uint48 tau, uint48 toc);
function init(bytes32 ilk, uint256 val, string calldata doc, uint48 tau) external;
}

interface RwaUrnLike {
function lock(uint256 wad) external;
function hope(address usr) external;
}

interface RwaInputConduitLike {
function mate(address usr) external;
function file(bytes32 what, address data) external;
}

interface RwaOutputConduitLike {
function file(bytes32 what, address data) external;
function hope(address usr) external;
function mate(address usr) external;
function kiss(address who) external;
}

interface PoolConfiguratorLike {
struct InitReserveInput {
address aTokenImpl;
address stableDebtTokenImpl;
address variableDebtTokenImpl;
uint8 underlyingAssetDecimals;
address interestRateStrategyAddress;
address underlyingAsset;
address treasury;
address incentivesController;
string aTokenName;
string aTokenSymbol;
string variableDebtTokenName;
string variableDebtTokenSymbol;
string stableDebtTokenName;
string stableDebtTokenSymbol;
bytes params;
}
function initReserves(InitReserveInput[] calldata input) external;
function configureReserveAsCollateral(
address asset,
uint256 ltv,
uint256 liquidationThreshold,
uint256 liquidationBonus
) external;
function setBorrowableInIsolation(address asset, bool borrowable) external;
function setDebtCeiling(address asset, uint256 newDebtCeiling) external;
}
import "dss-exec-lib/DssAction.sol";
// Enable ABIEncoderV2 when onboarding collateral through `DssExecLib.addNewCollateral()`
pragma experimental ABIEncoderV2;

interface AaveOracleLike {
function setAssetSources(address[] calldata assets, address[] calldata sources) external;
interface Authorizable {
function rely(address) external;
function deny(address) external;
function setAuthority(address) external;
}

contract DssSpellAction is DssAction {
// Provides a descriptive tag for bot consumption
string public constant override description = "Goerli Spell";

// Always keep office hours off on goerli
function officeHours() public pure override returns (bool) {
// Turn office hours off
function officeHours() public override returns (bool) {
return false;
}

// Many of the settings that change weekly rely on the rate accumulator
// described at https://docs.makerdao.com/smart-contract-modules/rates-module
// To check this yourself, use the following rate calculation (example 8%):
Expand All @@ -104,228 +45,65 @@ contract DssSpellAction is DssAction {
// A table of rates can be found at
// https://ipfs.io/ipfs/QmVp4mhhbwWGTfbh2BzwQB9eiBrQBKiqcPRZCaAxNUaar6
//
// uint256 internal constant X_PCT_RATE = ;

uint256 internal constant RAD = 10 ** 45;
uint256 internal constant WAD = 10 ** 18;
uint256 internal constant MILLION = 10 ** 6;
uint256 internal constant HUNDRED = 10 ** 2;

// -- RWA014 MIP21 components --
address internal constant RWA014 = 0x22a7440DCfF0E8881Ec93cE519c34C15feB2A09a;
address internal constant MCD_JOIN_RWA014_A = 0xc7Ba0aBa8512199c816834351CC978cf684D7fD9;
address internal constant RWA014_A_URN = 0xb475F63163aE3b0D5f6e30Dd914F5aA7204B1169;
address internal constant RWA014_A_JAR = 0x398E36Ed3c6bEf85f78b03d08b1980c6c3dd5357;
address internal constant RWA014_A_INPUT_CONDUIT_URN = 0x3b749869f62694804B0411DA77F13e816C49A25F;
address internal constant RWA014_A_INPUT_CONDUIT_JAR = 0xa9C909eDD4ee06D625EaDD546CccDB1BB3e02D02;
address internal constant RWA014_A_OUTPUT_CONDUIT = 0x563c3CD928DB7cAf5B9872bFa2dd0E4F31158256;

string internal constant RWA014_DOC = "QmT2Dr1tTw4idtVXZHxjT5Cs22KsNJyZgmYy9LGf9kR7vU";
uint256 internal constant RWA014_A_INITIAL_PRICE = 500 * MILLION * WAD;
uint48 internal constant RWA014_A_TAU = 0;
// Ilk registry params
uint256 internal constant RWA014_REG_CLASS_RWA = 3;
// Remaining params
uint256 internal constant RWA014_A_LINE = 500 * MILLION;
uint256 internal constant RWA014_A_MAT = 100_00;
// Operator address
address internal constant RWA014_A_OPERATOR = 0x3064D13712338Ee0E092b66Afb3B054F0b7779CB;
// Custody address
address internal constant RWA014_A_COINBASE_CUSTODY = 0x2E5F1f08EBC01d6136c95a40e19D4c64C0be772c;
// -- RWA014 END --

// -- Spark GNO Onboarding components --
address internal constant SPARK_POOL_CONFIGURATOR = 0xe0C7ec61cC47e7c02b9B24F03f75C7BC406CCA98;
address internal constant SPARK_AAVE_ORACLE = 0x5Cd822d9a4421be687930498ec4B498EB972ad29;
address internal constant SPARK_ATOKEN_IMPL = 0x35542cbc5730d5e39CF79dDBd8976ac984ca109b;
address internal constant SPARK_STABLE_DEBT_TOKEN_IMPL = 0x571501be53711c372cE69De51865dD34B87698D5;
address internal constant SPARK_VARIABLE_DEBT_TOKEN_IMPL = 0xb9E6DBFa4De19CCed908BcbFe1d015190678AB5f;
address internal constant SPARK_INTEREST_RATE_STRATEGY = 0xE7Fe5041ec55c229fb41fD9183E5bc24B5E34959;
address internal constant SPARK_TREASURY = 0x0D56700c90a690D8795D6C148aCD94b12932f4E3;
address internal constant SPARK_GNO_ORACLE = 0xF7CBEF92ef380502dCe9DE8A654A5c1d2A513E78;
address internal constant GNO_MEDIANIZER = 0x0cd01b018C355a60B2Cc68A1e3d53853f05A7280;

address internal immutable REGISTRY = DssExecLib.reg();
address internal immutable MIP21_LIQUIDATION_ORACLE = DssExecLib.getChangelogAddress("MIP21_LIQUIDATION_ORACLE");
address internal immutable ESM = DssExecLib.getChangelogAddress("MCD_ESM");
address internal immutable MCD_VAT = DssExecLib.vat();
address internal immutable MCD_JUG = DssExecLib.jug();
address internal immutable MCD_SPOT = DssExecLib.spotter();
address internal immutable MCD_DAI = DssExecLib.dai();

function onboardRWA014() internal {
bytes32 ilk = "RWA014-A";

// Init the RwaLiquidationOracle
RwaLiquidationLike(MIP21_LIQUIDATION_ORACLE).init(ilk, RWA014_A_INITIAL_PRICE, RWA014_DOC, RWA014_A_TAU);
(, address pip, , ) = RwaLiquidationLike(MIP21_LIQUIDATION_ORACLE).ilks(ilk);

// Init RWA014 in Vat
Initializable(MCD_VAT).init(ilk);
// Init RWA014 in Jug
Initializable(MCD_JUG).init(ilk);

// Allow RWA014 Join to modify Vat registry
DssExecLib.authorize(MCD_VAT, MCD_JOIN_RWA014_A);

// 500m debt ceiling
DssExecLib.increaseIlkDebtCeiling(ilk, RWA014_A_LINE, /* _global = */ true);

// Set price feed for RWA014
DssExecLib.setContract(MCD_SPOT, ilk, "pip", pip);

// Set minimum collateralization ratio
DssExecLib.setIlkLiquidationRatio(ilk, RWA014_A_MAT);

// Poke the spotter to pull in a price
DssExecLib.updateCollateralPrice(ilk);

// Give the urn permissions on the join adapter
DssExecLib.authorize(MCD_JOIN_RWA014_A, RWA014_A_URN);

// MCD_PAUSE_PROXY and OPERATOR permission on URN
RwaUrnLike(RWA014_A_URN).hope(address(this));
RwaUrnLike(RWA014_A_URN).hope(address(RWA014_A_OPERATOR));

// MCD_PAUSE_PROXY and OPERATOR permission on RWA014_A_OUTPUT_CONDUIT
RwaOutputConduitLike(RWA014_A_OUTPUT_CONDUIT).hope(address(this));
RwaOutputConduitLike(RWA014_A_OUTPUT_CONDUIT).mate(address(this));
RwaOutputConduitLike(RWA014_A_OUTPUT_CONDUIT).hope(RWA014_A_OPERATOR);
RwaOutputConduitLike(RWA014_A_OUTPUT_CONDUIT).mate(RWA014_A_OPERATOR);
// Coinbase custody whitelist for URN destination address
RwaOutputConduitLike(RWA014_A_OUTPUT_CONDUIT).kiss(address(RWA014_A_COINBASE_CUSTODY));
// Set "quitTo" address for RWA014_A_OUTPUT_CONDUIT
RwaOutputConduitLike(RWA014_A_OUTPUT_CONDUIT).file("quitTo", RWA014_A_URN);

// MCD_PAUSE_PROXY and OPERATOR permission on RWA014_A_INPUT_CONDUIT_URN
RwaInputConduitLike(RWA014_A_INPUT_CONDUIT_URN).mate(address(this));
RwaInputConduitLike(RWA014_A_INPUT_CONDUIT_URN).mate(RWA014_A_OPERATOR);
// Set "quitTo" address for RWA014_A_INPUT_CONDUIT_URN
RwaInputConduitLike(RWA014_A_INPUT_CONDUIT_URN).file("quitTo", RWA014_A_COINBASE_CUSTODY);

// MCD_PAUSE_PROXY and OPERATOR permission on RWA014_A_INPUT_CONDUIT_JAR
RwaInputConduitLike(RWA014_A_INPUT_CONDUIT_JAR).mate(address(this));
RwaInputConduitLike(RWA014_A_INPUT_CONDUIT_JAR).mate(RWA014_A_OPERATOR);
// Set "quitTo" address for RWA014_A_INPUT_CONDUIT_JAR
RwaInputConduitLike(RWA014_A_INPUT_CONDUIT_JAR).file("quitTo", RWA014_A_COINBASE_CUSTODY);

// Add RWA014 contract to the changelog
DssExecLib.setChangelogAddress("RWA014", RWA014);
DssExecLib.setChangelogAddress("PIP_RWA014", pip);
DssExecLib.setChangelogAddress("MCD_JOIN_RWA014_A", MCD_JOIN_RWA014_A);
DssExecLib.setChangelogAddress("RWA014_A_URN", RWA014_A_URN);
DssExecLib.setChangelogAddress("RWA014_A_JAR", RWA014_A_JAR);
DssExecLib.setChangelogAddress("RWA014_A_INPUT_CONDUIT_URN", RWA014_A_INPUT_CONDUIT_URN);
DssExecLib.setChangelogAddress("RWA014_A_INPUT_CONDUIT_JAR", RWA014_A_INPUT_CONDUIT_JAR);
DssExecLib.setChangelogAddress("RWA014_A_OUTPUT_CONDUIT", RWA014_A_OUTPUT_CONDUIT);

// Add RWA014 to ILK REGISTRY
IlkRegistryAbstract(REGISTRY).put(
ilk,
MCD_JOIN_RWA014_A,
RWA014,
GemAbstract(RWA014).decimals(),
RWA014_REG_CLASS_RWA,
pip,
address(0),
"RWA014-A: Coinbase Custody",
GemAbstract(RWA014).symbol()
);
}
uint256 internal constant TWO_FIVE_PCT_RATE = 1000000000782997609082909351;
uint256 constant THOUSAND = 10 ** 3;
uint256 constant MILLION = 10 ** 6;
// --- DEPLOYED COLLATERAL ADDRESSES ---
address internal constant USDT = 0xac0d203a8d9dD6EAf444d3DA95a0cfB3dcf9DBc9;
address internal constant VAL_USDT = 0xab6f09c3c3f0d25379690308fd63321bD655d218;
address internal constant PIP_USDT = 0x58b3586bF716e78D3036Af45A3d63a9fAd520f76;
address internal constant MCD_JOIN_USDT_A = 0xC186C13Eca0937697899018AF62B40460d3Cd148;
address internal constant MCD_CLIP_USDT_A = 0x812105Ff1A7546fB5160445E24b96fEf25980Fe8;
address internal constant MCD_CLIP_CALC_USDT_A = 0xecAFd89A7bf2d878aBf3fE903C4e806d8284fd80;
address internal constant ETH_FROM = 0x125fC0CcCDee5ac474062F6358d4d056b0430b84;

function actions() public override {

// ---------- RWA014-A Onboarding ----------
// Poll: https://vote.makerdao.com/polling/QmdRELY7#poll-detail
// Forum: https://forum.makerdao.com/t/coinbase-custody-legal-assessment/20384

onboardRWA014();
// Lock RWA014 Token in the URN
GemAbstract(RWA014).approve(RWA014_A_URN, 1 * WAD);
RwaUrnLike(RWA014_A_URN).lock(1 * WAD);

// ----- Additional ESM authorization -----
DssExecLib.authorize(MCD_JOIN_RWA014_A, ESM);
DssExecLib.authorize(RWA014_A_URN, ESM);
DssExecLib.authorize(RWA014_A_OUTPUT_CONDUIT, ESM);
DssExecLib.authorize(RWA014_A_INPUT_CONDUIT_URN, ESM);
DssExecLib.authorize(RWA014_A_INPUT_CONDUIT_JAR, ESM);

// --------- Keeper Network Amendments ---------
// Poll: https://vote.makerdao.com/polling/QmZZJcCj#poll-detail
// NOTE: ignore in goerli

// GELATO | 1,500 DAI/day | 3 years | Vest Target: 0x0B5a34D084b6A5ae4361de033d1e6255623b41eD | Treasury: 0xbfDC6b9944B7EFdb1e2Bc9D55ae9424a2a55b206
// KEEP3R | 1,500 DAI/day | 3 years | Vest Target: 0xaeFed819b6657B3960A8515863abe0529Dfc444A | Treasury: 0x4DfC6DA2089b0dfCF04788b341197146Ea97f743
// CHAINLINK | 1,500 DAI/day | 3 years | Vest Target: 0xfB5e1D841BDA584Af789bDFABe3c6419140EC065
// TECHOPS | 1,000 DAI/day | 1 years | Vest Target: 0x5A6007d17302238D63aB21407FF600a67765f982


// --------- CAIS Bootstrap Funding ---------
// Poll: https://vote.makerdao.com/polling/Qmc6Wqrc#poll-detail
// NOTE: ignore in goerli


// --------- Onboard GNO to Spark ---------
// Poll: https://vote.makerdao.com/polling/QmXdGdxS#poll-detail
// Forum: https://forum.makerdao.com/t/onboarding-of-gno-to-spark/20831
// List of addresses: https://github.com/marsfoundation/sparklend/blob/master/script/output/5/spark-latest.json
{
// Whitelist the GNO Fig adapter
MedianAbstract(GNO_MEDIANIZER).kiss(SPARK_GNO_ORACLE);

// Set DAI as a borrowable asset in isolation mode
PoolConfiguratorLike(SPARK_POOL_CONFIGURATOR).setBorrowableInIsolation(MCD_DAI, true);

// Add GNO
address token = DssExecLib.getChangelogAddress("GNO");
PoolConfiguratorLike.InitReserveInput[] memory input = new PoolConfiguratorLike.InitReserveInput[](1);
input[0] = PoolConfiguratorLike.InitReserveInput({
aTokenImpl: SPARK_ATOKEN_IMPL,
stableDebtTokenImpl: SPARK_STABLE_DEBT_TOKEN_IMPL,
variableDebtTokenImpl: SPARK_VARIABLE_DEBT_TOKEN_IMPL,
underlyingAssetDecimals: GemAbstract(token).decimals(),
interestRateStrategyAddress: SPARK_INTEREST_RATE_STRATEGY, // Dummy strategy - compare to other borrow-disabled asset like sDAI
underlyingAsset: token,
treasury: SPARK_TREASURY,
incentivesController: address(0),
aTokenName: "Spark GNO",
aTokenSymbol: "spGNO",
variableDebtTokenName: "Spark Variable Debt GNO",
variableDebtTokenSymbol: "variableDebtGNO",
stableDebtTokenName: "Spark Stable Debt GNO",
stableDebtTokenSymbol: "stableDebtGNO",
params: ""
});
PoolConfiguratorLike(SPARK_POOL_CONFIGURATOR).initReserves(input);
PoolConfiguratorLike(SPARK_POOL_CONFIGURATOR).configureReserveAsCollateral({
asset: token,
ltv: 20_00,
liquidationThreshold: 25_00,
liquidationBonus: 110_00
});
PoolConfiguratorLike(SPARK_POOL_CONFIGURATOR).setDebtCeiling(token, 5 * MILLION * HUNDRED);

address[] memory tokens = new address[](1);
tokens[0] = token;
address[] memory oracles = new address[](1);
oracles[0] = SPARK_GNO_ORACLE;
AaveOracleLike(SPARK_AAVE_ORACLE).setAssetSources(
tokens,
oracles
);
}

// Reduce Maker Protocol GNO Debt Ceiling to Zero
(,,,uint256 line,) = VatLike(MCD_VAT).ilks("GNO-A");
DssExecLib.removeIlkFromAutoLine("GNO-A");
DssExecLib.decreaseIlkDebtCeiling("GNO-A", line / RAD, true);

// Bump the chainlog
DssExecLib.setChangelogVersion("1.14.12");
// ----------------------------- Collateral onboarding -----------------------------
// Add USDT-A as a new Vault Type
// Poll Link: TODO
// Forum Post: https://forum.makerdao.com/t/USDT-collateral-onboarding-risk-evaluation/18820
CollateralOpts memory co = CollateralOpts({
ilk: "USDT-D",
gem: USDT,
join: MCD_JOIN_USDT_A,
clip: MCD_CLIP_USDT_A,
calc: MCD_CLIP_CALC_USDT_A,
pip: PIP_USDT,
isLiquidatable: true,
isOSM: true,
whitelistOSM: true,
ilkDebtCeiling: 100 * MILLION,
minVaultAmount: 7500,
maxLiquidationAmount: 25 * MILLION,
liquidationPenalty: 1300, // 13% penalty fee
ilkStabilityFee: TWO_FIVE_PCT_RATE, // 1.5% stability fee
startingPriceFactor: 12000, // Auction price begins at 120% of oracle
breakerTolerance: 5000, // Allows for a 50% hourly price drop before disabling liquidations
auctionDuration: 90 minutes,
permittedDrop: 4000, // 40% price drop before reset
liquidationRatio: 10100, // 175% collateralization
kprFlatReward: 300, // 300 Dai
kprPctReward: 10 // 0.1%
});

DssExecLib.addNewCollateral(co);
DssExecLib.setStairstepExponentialDecrease(MCD_CLIP_CALC_USDT_A, 130 seconds, 9900);
DssExecLib.setIlkAutoLineParameters("USDT-D", 20 * MILLION, 3 * MILLION, 8 hours);
IlkRegistryAbstract(DssExecLib.reg()).update("USDT-D");
DssExecLib.setChangelogAddress("USDT", USDT);
DssExecLib.setChangelogAddress("PIP_USDT", PIP_USDT);
DssExecLib.setChangelogAddress("MCD_JOIN_USDT_D", MCD_JOIN_USDT_A);
DssExecLib.setChangelogAddress("MCD_CLIP_USDT_D", MCD_CLIP_USDT_A);
DssExecLib.setChangelogAddress("MCD_CLIP_CALC_USDT_D", MCD_CLIP_CALC_USDT_A);
// Denying the ETH_FROM Address
Authorizable(VAL_USDT).deny(ETH_FROM);
Authorizable(PIP_USDT).deny(ETH_FROM);
Authorizable(MCD_JOIN_USDT_A).deny(ETH_FROM);
Authorizable(MCD_CLIP_USDT_A).deny(ETH_FROM);
Authorizable(MCD_CLIP_CALC_USDT_A).deny(ETH_FROM);
}
}

contract DssSpell is DssExec {
constructor() DssExec(block.timestamp + 30 days, address(new DssSpellAction())) {}
}
constructor() DssExec(block.timestamp + 30 days, address(new DssSpellAction())) public {
}
}