-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #329 from tranchess/dev-terry-test
Add some tests and scripts
- Loading branch information
Showing
10 changed files
with
1,688 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.