Skip to content

Commit

Permalink
Merge pull request #329 from tranchess/dev-terry-test
Browse files Browse the repository at this point in the history
Add some tests and scripts
  • Loading branch information
bill-clippy authored Jan 28, 2024
2 parents 45aaddc + 333e824 commit 8d3ee5f
Show file tree
Hide file tree
Showing 10 changed files with 1,688 additions and 12 deletions.
49 changes: 49 additions & 0 deletions contracts/test/MockWstETH.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.6.10 <0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import "../interfaces/IWstETH.sol";

import "../utils/SafeDecimalMath.sol";

contract MockWstETH is IWstETH, ERC20 {
using SafeDecimalMath for uint256;

address public immutable override stETH;

uint256 public override stEthPerToken;

constructor(address stETH_) public ERC20("Mock wstETH", "wstETH") {
stETH = stETH_;
_setupDecimals(18);
}

function update(uint256 rate) external {
stEthPerToken = rate;
}

function getWstETHByStETH(uint256 _stETHAmount) public view override returns (uint256) {
return _stETHAmount.divideDecimal(stEthPerToken);
}

function getStETHByWstETH(uint256 _wstETHAmount) public view override returns (uint256) {
return _wstETHAmount.multiplyDecimal(stEthPerToken);
}

function wrap(uint256 _stETHAmount) external override returns (uint256) {
require(_stETHAmount > 0, "wstETH: can't wrap zero stETH");
uint256 wstETHAmount = getWstETHByStETH(_stETHAmount);
_mint(msg.sender, wstETHAmount);
IERC20(stETH).transferFrom(msg.sender, address(this), _stETHAmount);
return wstETHAmount;
}

function unwrap(uint256 _wstETHAmount) external override returns (uint256) {
require(_wstETHAmount > 0, "wstETH: zero amount unwrap not allowed");
uint256 stETHAmount = getStETHByWstETH(_wstETHAmount);
_burn(msg.sender, _wstETHAmount);
IERC20(stETH).transfer(msg.sender, stETHAmount);
return stETHAmount;
}
}
2 changes: 2 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import "./tasks/deploy_eth_staking_strategy";
import "./tasks/deploy_fee_distributor";
import "./tasks/deploy_fund";
import "./tasks/deploy_fund_eth";
import "./tasks/deploy_fund_wsteth";
import "./tasks/deploy_governance";
import "./tasks/deploy_liquidity_gauge_curve";
import "./tasks/deploy_misc";
Expand All @@ -23,6 +24,7 @@ import "./tasks/deploy_mock_twap_oracle";
import "./tasks/deploy_vesting";
import "./tasks/deploy_voting_escrow_impl";
import "./tasks/deploy_stable_swap";
import "./tasks/deploy_stable_swap_wsteth";
import "./tasks/deploy_sub_governance";
import "./tasks/deploy_swap_router";
import "./tasks/deploy_flash_swap_router";
Expand Down
6 changes: 5 additions & 1 deletion tasks/deploy_fee_distributor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Addresses, saveAddressFile, loadAddressFile, newAddresses } from "./add
import type { GovernanceAddresses } from "./deploy_governance";
import { GOVERNANCE_CONFIG } from "../config";
import { updateHreSigner } from "./signers";
import { waitForContract } from "./utils";

export interface FeeDistrubtorAddresses extends Addresses {
underlying: string;
Expand Down Expand Up @@ -34,9 +35,12 @@ task("deploy_fee_distributor", "Deploy fund contracts")
adminFeeRate
);
console.log(`FeeDistributor: ${feeDistributor.address}`);
await waitForContract(hre, feeDistributor.address);

console.log("Transfering ownership to TimelockController");
await feeDistributor.transferOwnership(governanceAddresses.timelockController);
await (
await feeDistributor.transferOwnership(governanceAddresses.timelockController)
).wait();

const addresses: FeeDistrubtorAddresses = {
...newAddresses(hre),
Expand Down
194 changes: 194 additions & 0 deletions tasks/deploy_fund_wsteth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { strict as assert } from "assert";
import { task } from "hardhat/config";
import { Addresses, saveAddressFile, loadAddressFile, newAddresses } from "./address_file";
import type { GovernanceAddresses } from "./deploy_governance";
import type { FeeDistrubtorAddresses } from "./deploy_fee_distributor";
import { updateHreSigner } from "./signers";
import { BigNumber } from "ethers";
import { waitForContract } from "./utils";

export interface FundAddresses extends Addresses {
underlyingSymbol: string;
underlying: string;
twapOracle: string;
aprOracle: string;
feeConverter: string;
feeDistributor: string;
fund: string;
shareQ: string;
shareB: string;
shareR: string;
primaryMarket: string;
primaryMarketRouter: string;
}

task("deploy_fund_wsteth", "Deploy fund contracts for wstETH")
.addParam("underlying", "Underlying token (wstETH) address")
.addParam("redemptionFeeRate", "Primary market redemption fee rate")
.addParam("mergeFeeRate", "Primary market merge fee rate")
.addParam("bishopApr", "Initial annual interest rate")
.addParam(
"fundInitializationParams",
"Parameters to call Fund.initialize() in JSON (param names in camelCase)",
""
)
.setAction(async function (args, hre) {
await updateHreSigner(hre);
const { ethers } = hre;
const { parseEther } = ethers.utils;
await hre.run("compile");

const underlyingAddress = args.underlying;
const underlyingToken = await ethers.getContractAt("ERC20", underlyingAddress);
const underlyingDecimals = await underlyingToken.decimals();
const underlyingSymbol: string = await underlyingToken.symbol();
assert.match(underlyingSymbol, /^[a-zA-Z]+$/, "Invalid symbol");
console.log(`Underlying: ${underlyingToken.address}`);

const bishopApr = parseEther(args.bishopApr);
assert.ok(bishopApr.lt(parseEther("1")) && bishopApr.gt(0), "Invalid bishop APR");

const governanceAddresses = loadAddressFile<GovernanceAddresses>(hre, "governance");
const feeDistributorAddresses = loadAddressFile<FeeDistrubtorAddresses>(
hre,
`fee_distributor_${underlyingSymbol.toLowerCase()}`
);

const fundInitializationParams = JSON.parse(args.fundInitializationParams);
const fundNewSplitRatio = parseEther(fundInitializationParams.newSplitRatio);
const fundLastNavB = parseEther(fundInitializationParams.lastNavB || "1");
const fundLastNavR = parseEther(fundInitializationParams.lastNavR || "1");

const [deployer] = await ethers.getSigners();

const WstETHPriceOracle = await ethers.getContractFactory("WstETHPriceOracle");
const twapOracle = await WstETHPriceOracle.deploy(underlyingToken.address);
console.log(`TwapOracle: ${twapOracle.address}`);
await waitForContract(hre, twapOracle.address);

const ConstAprOracle = await ethers.getContractFactory("ConstAprOracle");
const aprOracle = await ConstAprOracle.deploy(bishopApr.div(365));
console.log(`AprOracle: ${aprOracle.address}`);
await waitForContract(hre, aprOracle.address);

// +0 ShareQ
// +1 ShareB
// +2 ShareR
// +3 Fund
// +4 PrimaryMarket
// +5 FeeConverter
const fundAddress = ethers.utils.getContractAddress({
from: deployer.address,
nonce: (await deployer.getTransactionCount("pending")) + 3,
});
const primaryMarketAddress = ethers.utils.getContractAddress({
from: deployer.address,
nonce: (await deployer.getTransactionCount("pending")) + 4,
});
const feeConverterAddress = ethers.utils.getContractAddress({
from: deployer.address,
nonce: (await deployer.getTransactionCount("pending")) + 5,
});

const Share = await ethers.getContractFactory("ShareV2");
const shareQ = await Share.deploy("Tranchess wstETH QUEEN", "wstQUEEN", fundAddress, 0);
console.log(`ShareQ: ${shareQ.address}`);
await waitForContract(hre, shareQ.address);

const shareB = await Share.deploy(
"Tranchess wstETH stable YETH",
"staYETH",
fundAddress,
1
);
console.log(`ShareB: ${shareB.address}`);
await waitForContract(hre, shareB.address);

const shareR = await Share.deploy("Tranchess wstETH turbo YETH", "turYETH", fundAddress, 2);
console.log(`ShareR: ${shareR.address}`);
await waitForContract(hre, shareR.address);

const Fund = await ethers.getContractFactory("FundV5");
const fund = await Fund.deploy([
9,
86400 * 365, // 1 year
underlyingToken.address,
underlyingDecimals,
shareQ.address,
shareB.address,
shareR.address,
primaryMarketAddress,
ethers.constants.AddressZero,
twapOracle.address,
aprOracle.address,
feeConverterAddress,
{ gasLimit: 5e6 }, // Gas estimation may fail
]);
assert.strictEqual(fund.address, fundAddress);
console.log(`Fund: ${fund.address}`);
await waitForContract(hre, fund.address);

const redemptionFeeRate = parseEther(args.redemptionFeeRate);
const mergeFeeRate = parseEther(args.mergeFeeRate);
const PrimaryMarket = await ethers.getContractFactory("PrimaryMarketV5");
const primaryMarket = await PrimaryMarket.deploy(
fund.address,
redemptionFeeRate,
mergeFeeRate,
BigNumber.from(1).shl(256).sub(1), // fund cap
true, // redemption flag
{ gasLimit: 8e6 } // Gas estimation may fail
);
assert.strictEqual(primaryMarket.address, primaryMarketAddress);
console.log(`PrimaryMarket: ${primaryMarket.address}`);
await waitForContract(hre, primaryMarket.address);

const FeeConverter = await ethers.getContractFactory("FeeConverter");
const feeConverter = await FeeConverter.deploy(
primaryMarketAddress,
feeDistributorAddresses.feeDistributor
);
assert.strictEqual(feeConverter.address, feeConverterAddress);
console.log(`FeeConverter: ${feeConverter.address}`);
await waitForContract(hre, feeConverter.address);

const WstETHPrimaryMarketRouter = await ethers.getContractFactory(
"WstETHPrimaryMarketRouter"
);
const primaryMarketRouter = await WstETHPrimaryMarketRouter.deploy(primaryMarket.address);
console.log(`PrimaryMarketRouter: ${primaryMarketRouter.address}`);
await waitForContract(hre, primaryMarketRouter.address);

console.log(
`Initializing fund with ${fundNewSplitRatio}, ${fundLastNavB}, ${fundLastNavR}`
);
await (await fund.initialize(fundNewSplitRatio, fundLastNavB, fundLastNavR, 0)).wait();

console.log("Transfering PrimaryMarket's ownership to TimelockController");
await (
await primaryMarket.transferOwnership(governanceAddresses.timelockController)
).wait();

console.log("Transfering Fund's ownership to TimelockController");
await (await fund.transferOwnership(governanceAddresses.timelockController)).wait();

console.log("Transfering ConstAprOracle's ownership to TimelockController");
await (await aprOracle.transferOwnership(governanceAddresses.timelockController)).wait();

const addresses: FundAddresses = {
...newAddresses(hre),
underlyingSymbol,
underlying: underlyingToken.address,
twapOracle: twapOracle.address,
aprOracle: aprOracle.address,
feeConverter: feeConverter.address,
feeDistributor: feeDistributorAddresses.feeDistributor,
fund: fund.address,
shareQ: shareQ.address,
shareB: shareB.address,
shareR: shareR.address,
primaryMarket: primaryMarket.address,
primaryMarketRouter: primaryMarketRouter.address,
};
saveAddressFile(hre, `fund_${underlyingSymbol.toLowerCase()}`, addresses);
});
11 changes: 7 additions & 4 deletions tasks/deploy_mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { task } from "hardhat/config";
import { keyInYNStrict } from "readline-sync";
import { Addresses, saveAddressFile, newAddresses } from "./address_file";
import { updateHreSigner } from "./signers";
import { waitForContract } from "./utils";

export interface MockAddresses extends Addresses {
mockAprOracle: string;
Expand Down Expand Up @@ -116,16 +117,18 @@ task("deploy_mock", "Deploy mock contracts")
if (args.silent || keyInYNStrict("Deploy MockStEth and MockWstEth?", { guide: true })) {
const MockToken = await ethers.getContractFactory("MockToken");
const mockStEth = await MockToken.deploy("Mock stETH", "stETH", 18);
await waitForContract(hre, mockStEth.address);
console.log(`MockStEth: ${mockStEth.address}`);
const MockWstETH = await ethers.getContractFactory("MockWstETH");
const mockWstEth = await MockWstETH.deploy(mockStEth.address);
await mockWstEth.update(parseEther("1"));
await waitForContract(hre, mockWstEth.address);
await (await mockWstEth.update(parseEther("1"))).wait();
console.log(`MockWstEth: ${mockWstEth.address}`);
mockStEthAddress = mockStEth.address;
mockWstEthAddress = mockWstEth.address;
await mockStEth.mint(deployer.address, parseEther("2000000"));
await mockStEth.approve(mockWstEth.address, parseEther("1000000"));
await mockWstEth.wrap(parseEther("1000000"));
await (await mockStEth.mint(deployer.address, parseEther("2000000"))).wait();
await (await mockStEth.approve(mockWstEth.address, parseEther("1000000"))).wait();
await (await mockWstEth.wrap(parseEther("1000000"))).wait();
}

let mockUniswapV2PairAddress = "";
Expand Down
Loading

0 comments on commit 8d3ee5f

Please sign in to comment.