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

IIP-39: Add AA_mmSteakUSDC to IdleUSDC and remove AA_cpPOR_USDC. Stop IDLE emissions. Transfer IDLE for ROX deal #22

Merged
merged 5 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
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
9 changes: 8 additions & 1 deletion common/addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,10 @@ const AA_eWETHStaking = {
'live': '0x2B7Da260F101Fb259710c0a4f2EfEf59f41C0810',
'local': '0x2B7Da260F101Fb259710c0a4f2EfEf59f41C0810',
};
const AA_steakUSDC = {
'live': '0x2B0E31B8EE653D2077db86dea3ACf3F34ae9d5D2',
'local': '0x2B0E31B8EE653D2077db86dea3ACf3F34ae9d5D2',
};
const aFEI = {
'live': '0x683923dB55Fead99A79Fa01A27EeC3cB19679cC3',
'local': '0x683923dB55Fead99A79Fa01A27EeC3cB19679cC3',
Expand Down Expand Up @@ -777,6 +781,7 @@ module.exports = {
AA_cpPOR_USDC,
AA_cpFAS_USDT,
AA_cpFAS_USDC,
AA_steakUSDC,
idleDAIV4: idleDAIV4,
idleUSDCV4: idleUSDCV4,
idleUSDTV4: idleUSDTV4,
Expand Down Expand Up @@ -834,8 +839,10 @@ module.exports = {
idleController: '0x275DA8e61ea8E02d51EDd8d0DC5c0E62b4CDB0BE',
governorAlpha: '0x2256b25CFC8E35c3135664FD03E77595042fe31B',
ecosystemFund: '0xb0aA1f98523Ec15932dd5fAAC5d86e57115571C7',
longTermFund: '0x107a369bc066c77ff061c7d2420618a6ce31b925',
vesterFactory: '0xbF875f2C6e4Cc1688dfe4ECf79583193B6089972',
whale: '0x99c9fc46f92e8a1c0dec1b1747d010903e884be1',
whale: '0xD6153F5af5679a75cC85D8974463545181f48772',
// whale: '0x99c9fc46f92e8a1c0dec1b1747d010903e884be1',
SUSDwhale: '0xa5f7a39e55d7878bc5bd754ee5d6bd7a7662355b',
TUSDwhale: '0xf977814e90da44bfa03b6295a0616a897441acec',
WETHwhale: '0x8EB8a3b98659Cce290402893d0123abb75E3ab28',
Expand Down
5 changes: 4 additions & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import "./scripts/iip-34"
import "./scripts/iip-35"
import "./scripts/iip-36"
import "./scripts/iip-37"
import "./scripts/iip-39"
import "./scripts/iip-upgrade"
import "./scripts/utilities"
import "./scripts/test-idle-token"
Expand Down Expand Up @@ -75,8 +76,10 @@ const config: HardhatUserConfig = {
hardhat: {
forking: {
// Ethereum
// url: `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`,
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`,
blockNumber: 17634301 // iip-37
blockNumber: 20117100, // iip-39
// blockNumber: 17634301 // iip-37
// blockNumber: 17465062 // iip-36
// blockNumber: 17216816 // iip-35
// blockNumber: 17088152 // iip-34
Expand Down
22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,25 @@
"author": "Idle Dev League",
"license": "Apache 2.0",
"dependencies": {
"@ethersproject/hardware-wallets": "^5.4.0",
"@gnosis.pm/safe-ethers-adapters": "^0.1.0-alpha.3",
"@idle-finance/hardhat-proposals-plugin": "^0.2.5",
"@ledgerhq/hw-app-eth": "^6.4.0",
"@ledgerhq/hw-transport-node-hid": "^6.3.0",
"@ethersproject/hardware-wallets": "5.4.0",
"@gnosis.pm/safe-ethers-adapters": "0.1.0-alpha.3",
"@idle-finance/hardhat-proposals-plugin": "0.2.5",
"@ledgerhq/hw-app-eth": "6.4.0",
"@ledgerhq/hw-transport-node-hid": "6.3.0",
"@openzeppelin/contracts": "^2.5.0",
"@openzeppelin/hardhat-upgrades": "^1.10.0",
"@openzeppelin/hardhat-upgrades": "1.10.0",
"dotenv": "^10.0.0",
"hardhat": "^2.9.0"
"hardhat": "2.21.0"
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@nomiclabs/hardhat-ethers": "2.2.3",
"@nomiclabs/hardhat-waffle": "2.0.1",
"@types/chai": "^4.2.21",
"@types/mocha": "^8.2.3",
"@types/node": "^16.3.3",
"chai": "^4.3.4",
"ethereum-waffle": "^3.4.0",
"ethers": "^5.4.1",
"ethereum-waffle": "3.4.0",
"ethers": "5.7.0",
"ts-node": "^10.1.0",
"tsconfig-paths": "^3.10.1",
"typescript": "^4.3.5"
Expand Down
283 changes: 283 additions & 0 deletions scripts/iip-39.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
import { task } from "hardhat/config"
import { BigNumber } from "ethers";

const DISTRIBUTOR_ABI = require("../abi/Distributor.json");
const addresses = require("../common/addresses")
const GOVERNABLE_FUND = require("../abi/GovernableFund.json");
const ERC20_ABI = require("../abi/ERC20.json");
const IdleTokenABI = require("../abi/IdleTokenGovernance.json")
const IDLE_CONTROLLER_ABI = require("../abi/IdleController.json");
const ILendingProtocolABI = require("../abi/ILendingProtocol.json");
const FeeCollectorABI = require("../abi/FeeCollector.json")
let _hre;
const toBN = function (v: any): BigNumber { return BigNumber.from(v.toString()) };
const ONE = toBN(1e18);
const ONE6 = toBN(1e6);
const check = (condition: boolean, message: string) => {
if (condition) {
console.log(`✅ Correct ${message}`);
} else {
console.log(`🚨 Incorrect ${message}`);
}
};
const checkAlmostEqual = (a: any, b: any, tolerance: any, message: any) => {
const diff = a.sub(b).abs();
const maxDiff = a.mul(tolerance).div(toBN(100));
if (diff.lte(maxDiff)) {
console.log(`✅ Correct ${message}`);
} else {
console.log(`🚨 Incorrect ${message}`);
}
}

const iipDescription = "IIP-39: Add AA_mmSteakUSDC to IdleUSDC. Stop IDLE emissions. Transfer funds for ROX deal and Leagues budget";

export default task("iip-39", iipDescription).setAction(async (_, hre) => {
_hre = hre;
const isLocalNet = hre.network.name == 'hardhat';

const idle = await hre.ethers.getContractAt(ERC20_ABI, addresses.IDLE);
const usdc = await hre.ethers.getContractAt(ERC20_ABI, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48');
// const usdc = await hre.ethers.getContractAt(ERC20_ABI, addresses.USDC.live);
const distributor = await hre.ethers.getContractAt(DISTRIBUTOR_ABI, addresses.gaugeDistributor);
const idleController = await hre.ethers.getContractAt(IDLE_CONTROLLER_ABI, addresses.idleController);
const ecosystemFund = await hre.ethers.getContractAt(GOVERNABLE_FUND, addresses.ecosystemFund)
const longTermFund = await hre.ethers.getContractAt(GOVERNABLE_FUND, addresses.longTermFund)
const feeCollector = await hre.ethers.getContractAt(FeeCollectorABI, addresses.feeCollector);
const feeTreasury = await hre.ethers.getContractAt(GOVERNABLE_FUND, addresses.feeTreasury);

// ############# PARAMS for AA_steakUSDC in idleUSDC #############
const idleToken = await hre.ethers.getContractAt(IdleTokenABI, addresses.idleUSDCV4);
const idleTokenName = await idleToken.name();
console.log(`📄 adding proposal action for ${idleTokenName}`);

const allGovTokens = await idleToken.getGovTokens();
console.log('All gov tokens (USDC)', allGovTokens);

// New wrappers and protocol token for aa tranches
const wrapper = '0x96Dd27112bDd615c3A2D649fe22d8eE27e448152';
const protocolToken = addresses.AA_steakUSDC.live.toLowerCase();
const paramUSDC = await getParamsForSetAll(idleToken, wrapper, protocolToken, addresses.addr0, hre);
// ############# END PARAMS for metamorpho ##########

// ############# PARAMS for ROX deal and Leagues #############
const idleReceiver = addresses.treasuryMultisig;
const idleFrom = addresses.ecosystemFund;
const idleFromEcosystemFund = toBN("66490").mul(ONE);
console.log(`📄 IDLE receiver ${idleReceiver}, amount: ${idleFromEcosystemFund.div(ONE)}`);

const usdcReceiver = addresses.treasuryMultisig;
const usdcFrom = addresses.feeTreasury;
const usdcFromFeeTreasury = toBN("125000").mul(ONE6);
console.log(`📄 USDC receiver ${usdcReceiver}, amount: ${usdcFromFeeTreasury.div(ONE6)}`);

// Get balances for tests
const ecosystemFundIDLEBalanceBefore = await idle.balanceOf(idleFrom);
const feeTreasuryUSDCBalanceBefore = await usdc.balanceOf(usdcFrom);
const idleReceiverBalanceBefore = await idle.balanceOf(idleReceiver);
const tlmultisigUSDCBalanceBefore = await usdc.balanceOf(usdcReceiver);
// ############# END PARAMS for BUDGET ##########

// ############# PARAMS for stopping IDLE emissions ##########
const newControllerRate = toBN(0);
// ############# END PARAMS for topping emissions ############

let proposalBuilder = hre.proposals.builders.alpha();
proposalBuilder = proposalBuilder
.addContractAction(idleController, "_setIdleRate", [newControllerRate])
.addContractAction(idleToken, "setAllAvailableTokensAndWrappers", [
paramUSDC.protocolTokens,
paramUSDC.wrappers,
paramUSDC.govTokens,
paramUSDC.govTokensEqualLength
])
.addContractAction(idleController, "claimIdle", [
[addresses.treasuryMultisig],
[addresses.idleUSDCV4, addresses.idleDAIV4, addresses.idleUSDTV4, addresses.idleWETHV4]
])
.addContractAction(ecosystemFund, "transfer", [addresses.IDLE, idleReceiver, idleFromEcosystemFund])
.addContractAction(feeTreasury, "transfer", [addresses.USDC.live, usdcReceiver, usdcFromFeeTreasury])

// Print and execute proposal
proposalBuilder.setDescription(iipDescription);
const proposal = proposalBuilder.build()
await proposal.printProposalInfo();
await hre.run('execute-proposal-or-simulate', { proposal, isLocalNet });

// Skip tests in mainnet
if (!isLocalNet) {
return;
}
console.log("Checking effects...");

// Check that new protocols are added
console.log('Checking idleUSDC...');
await checkEffects(idleToken, allGovTokens, wrapper, protocolToken, addresses.addr0, paramUSDC.oldProtocolTokens, hre);

// check that controller rate is set to 0
console.log('Checking IdleController...');
const controllerRate = await idleController.idleRate();
check(controllerRate.eq(newControllerRate), `Controller rate set to 0`);

// check idleSpeeds
[addresses.idleUSDCV4, addresses.idleDAIV4, addresses.idleUSDTV4, addresses.idleWETHV4].forEach(async (idleTokenAddress) => {
const idleSpeed = await idleController.idleSpeeds(idleTokenAddress);
check(idleSpeed.eq(toBN(0)), `Idle speed set to 0 for ${idleTokenAddress}`);
});

// check that IDLE funds are sent to longTermFund
console.log('Checking IDLE funds...');
const ecosystemFundIDLEBalanceAfter = await idle.balanceOf(addresses.ecosystemFund);
const roxIDLEBalanceAfter = await idle.balanceOf(idleReceiver);
checkAlmostEqual(ecosystemFundIDLEBalanceAfter, ecosystemFundIDLEBalanceBefore.sub(idleFromEcosystemFund), toBN(0), `IDLE transferred from ecosystemFund`);
checkAlmostEqual(roxIDLEBalanceAfter, idleReceiverBalanceBefore.add(idleFromEcosystemFund), toBN(0), `IDLE transferred to roxReceiver`);

// check that USDC funds are sent to longTermFund
console.log('Checking USDC funds...');
const feeTreasuryUSDCBalanceAfter = await usdc.balanceOf(usdcFrom);
const tlmultisigUSDCBalanceAfter = await usdc.balanceOf(usdcReceiver);
checkAlmostEqual(feeTreasuryUSDCBalanceAfter, feeTreasuryUSDCBalanceBefore.sub(usdcFromFeeTreasury), toBN(100), `USDC transferred from feeTreasury`);
checkAlmostEqual(tlmultisigUSDCBalanceAfter, tlmultisigUSDCBalanceBefore.add(usdcFromFeeTreasury), toBN(0), `USDC transferred to tlmultisig`);
});

const getParamsForSetAll = async (
idleToken: any,
newWrapper: any,
newProtocolToken: any,
oldProtocolToken: any,
hre: any
) => {
let protocolTokens = [...(await idleToken.getAPRs())["0"]].map(x => x.toLowerCase())
let wrappers = []
let govTokensEqualLength = []
let govTokens = [];
let newProtocolTokens = [];

console.log('protocolTokens', protocolTokens);
const isRemoving = newProtocolToken == addresses.addr0.toLowerCase();
const isReplacing = oldProtocolToken != addresses.addr0.toLowerCase();
const newLenDiff = isRemoving ? 1 : 0;

for (var i = 0; i < protocolTokens.length - newLenDiff; i++) {
const token = await hre.ethers.getContractAt(ERC20_ABI, protocolTokens[i]);
const wrapper = await idleToken.protocolWrappers(token.address);
console.log(await token.name(), token.address, " => ", wrapper);

const govToken = await idleToken.getProtocolTokenToGov(token.address)
if (govToken.toLowerCase() != addresses.addr0.toLowerCase()) {
govTokens.push(govToken);
}
if (isReplacing && token.address.toLowerCase() == oldProtocolToken) {
wrappers.push(newWrapper);
govTokensEqualLength.push(addresses.addr0.toLowerCase());
newProtocolTokens.push(newProtocolToken);
continue;
}
wrappers.push(wrapper);
govTokensEqualLength.push(govToken);
newProtocolTokens.push(protocolTokens[i]);
};

if (!isRemoving && !isReplacing) {
// update protocol tokens with new protocol token
newProtocolTokens = [...newProtocolTokens, newProtocolToken];
// update last wrapper (aa senior tranche)
wrappers = [...wrappers, newWrapper];
// update govTokensEqualLength with new gov token set as addr0
govTokensEqualLength = [...govTokensEqualLength, addresses.addr0.toLowerCase()];
}
// add IDLE distribution
govTokens.push(addresses.IDLE);

return {
oldProtocolTokens: protocolTokens,
protocolTokens: newProtocolTokens,
wrappers,
govTokensEqualLength,
govTokens
}
};

const checkEffects = async (
idleToken: any,
allGovTokens: any,
newWrapper: any,
newProtocolToken: any,
oldProtocolToken: any,
oldProtocolTokens: any,
hre: any
) => {
const isRemoving = newProtocolToken == addresses.addr0.toLowerCase();
const isReplacing = oldProtocolToken != addresses.addr0.toLowerCase();
const newGovTokens = await idleToken.getGovTokens();
console.log('newGovTokens', newGovTokens);
check(newGovTokens.length == allGovTokens.length, `Gov tokens length did not change`);

let newProtocolTokens = [...(await idleToken.getAPRs())["0"]].map(x => x.toLowerCase());
if (isRemoving) {
check(newProtocolTokens.length == oldProtocolTokens.length - 1, `Protocol tokens length decreased by 1`);
} else if (isReplacing) {
check(newProtocolTokens.length == oldProtocolTokens.length, `Protocol tokens length did not change: ${newProtocolTokens.length}`);
} else {
check(newProtocolTokens[newProtocolTokens.length - 1].toLowerCase() == newProtocolToken,
`New token added is the correct one`);
}

const newWrappers = [];
const oldTokenIdx = oldProtocolToken ? oldProtocolTokens.indexOf(oldProtocolToken) : null;

for (var i = 0; i < newProtocolTokens.length; i++) {
const token = await hre.ethers.getContractAt(ERC20_ABI, newProtocolTokens[i]);
const wrapper = await idleToken.protocolWrappers(token.address);
console.log(await token.name(), token.address, " => ", wrapper);
if (isReplacing && oldTokenIdx != null && i == oldTokenIdx) {
check(wrapper.toLowerCase() == newWrapper.toLowerCase(), `Old wrapper replaced`);
check(token.address.toLowerCase() == newProtocolToken.toLowerCase(), `Old token replaced`);
}

if (isReplacing && oldTokenIdx == null) {
console.log('ERROR: oldTokenIdx is null');
}

const govToken = await idleToken.getProtocolTokenToGov(token.address)
console.log('-- govToken: ', govToken);
newWrappers.push(wrapper);
};

if (!isRemoving && !isReplacing) {
check(newWrappers[newWrappers.length - 1].toLowerCase() == newWrapper.toLowerCase(), `New wrapper added`);
}

// Test rebalances idleToken all in new protocol
// All funds in the new protocol
let allocations = newProtocolTokens.map(
(_, i) => {
// if is adding, all funds in the new protocol (ie last one)
if (!isRemoving && !isReplacing && i == newProtocolTokens.length - 1) {
return 100000;
} else if (isReplacing && i == oldTokenIdx) {
return 100000;
} else {
return 0;
}
}
);
await hre.run("test-idle-token", { idleToken, allocations })

// All funds in the first protocol
// allocations = newProtocolTokens.map((_, i) => i == 0 ? 100000 : 0);
allocations = newProtocolTokens.map(
(_, i) => {
// if is replacing a protocol (not with idx 0) or is not replacing => all on first protocol
if (((oldTokenIdx != null && oldTokenIdx != 0) || (oldTokenIdx == null)) && i == 0) {
return 100000;
} else if (oldTokenIdx != null && oldTokenIdx == 0 && i == 1) {
// if is replacing a protocol with idx 0 => all on second protocol
return 100000;
} else {
return 0;
}
}
);
await hre.run("test-idle-token", { idleToken, allocations })
}
Loading