Skip to content

Commit

Permalink
Merge pull request #22 from Idle-Finance/iip-39
Browse files Browse the repository at this point in the history
IIP-39: Add AA_mmSteakUSDC to IdleUSDC and remove AA_cpPOR_USDC. Stop IDLE emissions. Transfer IDLE for ROX deal
  • Loading branch information
bugduino committed Jun 18, 2024
2 parents 51ee52d + 5374c37 commit bad6050
Show file tree
Hide file tree
Showing 6 changed files with 1,119 additions and 473 deletions.
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

0 comments on commit bad6050

Please sign in to comment.