Run
foundryup
to ensure using the latest Foundry version
Run
forge install
to ensure dependencies are installed
Run
yarn add --dev @nomicfoundation/hardhat-foundry
to support foundry with hardhat
Add
require("@nomicfoundation/hardhat-foundry");
onhardhat.config.js
Comment all the test foundry contracts below:
- Silo.t.sol
- Sun.t.sol
- Weather.t.sol
- Field.t.sol
- Bean.t.sol
[profile.default]
# Project
# https://book.getfoundry.sh/reference/config/project
src = 'contracts'
test = 'test/foundry'
no_match_test = "testDiff"
out = 'out'
libs = [
'node_modules',
'lib'
]
cache = true
cache_path = 'cache'
# Compiler
# https://book.getfoundry.sh/reference/config/solidity-compiler
libraries = []
auto_detect_solc = true
ignored_error_codes = [1878, 2837]
optimizer = true
optimizer_runs = 200
via_ir = false
bytecode_hash = 'ipfs'
ignored_warnings_from = ["node_modules", "lib", "protocol"]
# Testing
# https://book.getfoundry.sh/reference/config/testing
verbosity = 0
ffi = true
fs_permissions = [{ access = "read", path = "./" }]
sender = '0x00a329c0648769a73afac7f9381e08fb43dbea72' # The value of `msg.sender` in tests.
tx_origin = '0x00a329c0648769a73afac7f9381e08fb43dbea72' # The value of `tx.origin` in tests.
gas_reports = ['*']
# Cache to `$HOME/.foundry/cache/<chain id>/<block number>`.
no_storage_caching = false
evm_version = "shanghai" # fix for https://github.com/foundry-rs/foundry/issues/4988
[rpc_endpoints]
local = "http://127.0.0.1:8545"
[profile.differential]
match_test = "testDiff"
no_match_test = "a^"
# Formatter
# https://book.getfoundry.sh/reference/config/formatter
# ...pass...
[profile.default.rpc_storage_caching]
chains = 'all'
endpoints = 'all'
function sync(address recipient, uint256 minLpAmountOut) public returns (uint256 lpAmountOut) {
IERC20[] memory _tokens = tokens();
uint256 tokensLength = _tokens.length;
uint256[] memory reserves = new uint256[](tokensLength);
for (uint256 i; i < tokensLength; ++i) {
reserves[i] = _tokens[i].balanceOf(address(this));
}
uint256 newTokenSupply = _calcLpTokenSupply(wellFunction(), reserves);
uint256 oldTokenSupply = totalSupply();
if (newTokenSupply > oldTokenSupply) {
lpAmountOut = newTokenSupply - oldTokenSupply;
_mint(recipient, lpAmountOut);
}
if (lpAmountOut < minLpAmountOut) {
revert ("reverted, slippageOut");
}
setReserves(reserves);
}
// Getters for testing
function currentSeason() public view returns (uint32) {
return s.season.current;
}
function weather() public view returns (Storage.Weather memory) {
return s.w;
}
function getIsFarm() external view returns (uint256) {
return s.isFarm;
}
function getUsdTokenPrice() external view returns (uint256) {
return s.usdTokenPrice[C.BEAN_ETH_WELL];
}
function getReserve0() external view returns (uint256) {
return uint256(s.twaReserves[C.BEAN_ETH_WELL].reserve0);
}
function getReserve1() external view returns (uint256) {
return uint256(s.twaReserves[C.BEAN_ETH_WELL].reserve1);
}
function getSoil() external view returns (uint256) {
return uint256(s.f.soil);
}
function getAbovePeg() external view returns (bool) {
return s.season.abovePeg;
}
function getSeasonTimestamp() external view returns (uint256) {
return s.season.timestamp;
}
function wellOracleSnapshot() external view returns (bytes memory) {
return s.wellOracleSnapshots[C.BEAN_ETH_WELL];
}
function mockSopWell() external view returns (address) {
return s.sopWell;
}
/**
* SPDX-License-Identifier: MIT
**/
pragma solidity =0.7.6;
pragma abicoder v2;
import "forge-std/Test.sol";
import "./Strings.sol";
import {Utils} from "test/foundry/utils/Utils.sol";
// Diamond setup
import {Diamond} from "contracts/beanstalk/Diamond.sol";
import {IDiamondCut} from "contracts/interfaces/IDiamondCut.sol";
import {MockInitDiamond} from "contracts/mocks/MockInitDiamond.sol";
/// Modules
// Diamond
import {DiamondCutFacet} from "contracts/beanstalk/diamond/DiamondCutFacet.sol";
import {DiamondLoupeFacet} from "contracts/beanstalk/diamond/DiamondLoupeFacet.sol";
import {PauseFacet} from "contracts/beanstalk/diamond/PauseFacet.sol";
import {OwnershipFacet} from "contracts/beanstalk/diamond/OwnershipFacet.sol";
// Silo
import {MockSiloFacet} from "contracts/mocks/mockFacets/MockSiloFacet.sol";
import {BDVFacet} from "contracts/beanstalk/silo/BDVFacet.sol";
import {ConvertFacet} from "contracts/beanstalk/silo/ConvertFacet.sol";
import {WhitelistFacet} from "contracts/beanstalk/silo/WhitelistFacet/WhitelistFacet.sol";
import {LiquidityWeightFacet} from "contracts/beanstalk/sun/LiquidityWeightFacet.sol";
// Field
import {MockFieldFacet} from "contracts/mocks/mockFacets/MockFieldFacet.sol";
import {MockFundraiserFacet} from "contracts/mocks/mockFacets/MockFundraiserFacet.sol";
// Sun
import {GaugePointFacet} from "contracts/beanstalk/sun/GaugePointFacet.sol";
// Farm
import {FarmFacet} from "contracts/beanstalk/farm/FarmFacet.sol";
import {CurveFacet} from "contracts/beanstalk/farm/CurveFacet.sol";
import {TokenFacet} from "contracts/beanstalk/farm/TokenFacet.sol";
/// Ecosystem
import {BeanstalkPrice} from "contracts/ecosystem/price/BeanstalkPrice.sol";
/// Mocks
import {MockConvertFacet} from "contracts/mocks/mockFacets/MockConvertFacet.sol";
import {MockMarketplaceFacet} from "contracts/mocks/mockFacets/MockMarketplaceFacet.sol";
import {MockSeasonFacet} from "contracts/mocks/mockFacets/MockSeasonFacet.sol";
import {MockFertilizerFacet} from "contracts/mocks/mockFacets/MockFertilizerFacet.sol";
import {MockToken} from "contracts/mocks/MockToken.sol";
import {MockSetComponentsWell} from "contracts/mocks/well/MockSetComponentsWell.sol";
import {MockUnripeFacet} from "contracts/mocks/mockFacets/MockUnripeFacet.sol";
import {Mock3Curve} from "contracts/mocks/curve/Mock3Curve.sol";
import {MockCurveFactory} from "contracts/mocks/curve/MockCurveFactory.sol";
import {MockCurveZap} from "contracts/mocks/curve/MockCurveZap.sol";
import {MockMeta3Curve} from "contracts/mocks/curve/MockMeta3Curve.sol";
import {MockUniswapV3Pool} from "contracts/mocks/uniswap/MockUniswapV3Pool.sol";
import {MockUniswapV3Factory} from "contracts/mocks/uniswap/MockUniswapV3Factory.sol";
import {MockPump} from "contracts/mocks/well/MockPump.sol";
import {MockWETH} from "contracts/mocks/MockWETH.sol";
import {MockChainlinkAggregator} from "contracts/mocks/chainlink/MockChainlinkAggregator.sol";
import "contracts/beanstalk/AppStorage.sol";
import "contracts/libraries/Decimal.sol";
import "contracts/libraries/LibSafeMath32.sol";
import "contracts/libraries/Token/LibTransfer.sol";
import "contracts/C.sol";
import "contracts/interfaces/basin/IWell.sol";
interface IBDVFacet {
function wellBdv(address token, uint256 amount)
external
view
returns (uint256);
}
interface IGaugePointFacet {
function defaultGaugePointFunction(
uint256 currentGaugePoints,
uint256 optimalPercentDepositedBdv,
uint256 percentOfDepositedBdv
) external pure returns (uint256 newGaugePoints);
}
interface ILiquidityWeightFacet {
function maxWeight() external pure returns (uint256);
}
// We deploy every facet, even if a facet is unused
abstract contract TestHelper is Test {
using strings for *;
Utils internal utils;
uint32 constant private BEAN_ETH_SEEDS_PER_BDV = 4.5e6;
uint16 constant private STALK_ISSUED_PER_BDV = 10000;
address payable[] internal users;
// the cool dudes
address internal deployer;
address internal user1;
address internal user2;
address internal user3;
address internal user4;
address internal user5;
address internal diamond;
// season mocks
MockSeasonFacet internal season;
MockSiloFacet internal silo;
MockFieldFacet internal field;
MockConvertFacet internal convert;
MockFundraiserFacet internal fundraiser;
MockMarketplaceFacet internal marketplace;
MockFertilizerFacet internal fertilizer;
MockUnripeFacet internal unripe;
WhitelistFacet internal whitelist;
MockToken internal unripeLP;
MockToken internal unripeBean;
MockWETH internal mockWETH;
TokenFacet internal token;
MockSetComponentsWell internal well;
MockPump internal mockPump;
// oracle mocks
MockChainlinkAggregator internal chainlinkAggregator;
function setupDiamond() public {
diamond = address(deployMocks());
season = MockSeasonFacet(diamond);
silo = MockSiloFacet(diamond);
field = MockFieldFacet(diamond);
convert = MockConvertFacet(diamond);
fundraiser = MockFundraiserFacet(diamond);
marketplace = MockMarketplaceFacet(diamond);
fertilizer = MockFertilizerFacet(diamond);
whitelist = WhitelistFacet(diamond);
unripe = MockUnripeFacet(diamond);
token = TokenFacet(diamond);
_setupWell();
vm.prank(deployer);
_whitelistBeanEthWell();
}
function deployMocks() public returns (Diamond d) {
// create accounts
utils = new Utils();
users = utils.createUsers(6);
deployer = users[0];
user1 = users[1];
user2 = users[2];
user3 = users[3];
user4 = users[4];
user5 = users[5];
vm.label(deployer, "Deployer");
vm.label(user1, "user1");
vm.label(user2, "user2");
vm.label(user3, "user3");
vm.label(user4, "user4");
vm.label(user5, "user5");
// create facet cuts
IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](17);
cut[0] = _cut("BDVFacet", address(new BDVFacet()));
cut[1] = _cut("CurveFacet", address(new CurveFacet()));
cut[2] = _cut("MockConvertFacet", address(new MockConvertFacet()));
cut[3] = _cut("FarmFacet", address(new FarmFacet()));
cut[4] = _cut("MockFieldFacet", address(new MockFieldFacet()));
cut[5] = _cut("MockFundraiserFacet", address(new MockFundraiserFacet()));
cut[6] = _cut("PauseFacet", address(new PauseFacet()));
cut[7] = _cut("MockSeasonFacet", address(new MockSeasonFacet()));
cut[8] = _cut("MockSiloFacet", address(new MockSiloFacet()));
cut[9] = _cut("MockFertilizerFacet", address(new MockFertilizerFacet()));
cut[10] = _cut("OwnershipFacet", address(new OwnershipFacet()));
cut[11] = _cut("TokenFacet", address(new TokenFacet()));
cut[12] = _cut("MockUnripeFacet", address(new MockUnripeFacet()));
cut[13] = _cut("WhitelistFacet", address(new WhitelistFacet()));
cut[14] = _cut("MockMarketplaceFacet", address(new MockMarketplaceFacet()));
cut[15] = _cut("GaugePointFacet", address(new GaugePointFacet()));
cut[16] = _cut("LiquidityWeightFacet", address(new LiquidityWeightFacet()));
console.log("Deployed mock facets.");
deployMockTokens();
// create diamond
d = new Diamond(deployer);
MockInitDiamond i = new MockInitDiamond();
vm.prank(deployer);
IDiamondCut(address(d)).diamondCut(
cut,
address(i), // address of contract with init() function
abi.encodeWithSignature("init()")
);
console.log("Initialized diamond at", address(d));
}
function deployMockTokens() public {
// impersonate tokens and utilities
_mockToken("Bean", address(C.bean()));
MockToken(address(C.bean())).setDecimals(6);
_mockToken("USDC", address(C.usdc()));
_mockPrice();
_mockCurve(); // only if "reset"
_mockUniswap();
_mockChainlinkAggregator();
_mockUnripe();
_mockWeth(); // only if "reset"
}
function _whitelistBeanEthWell() internal {
bytes4 gpSelector = IGaugePointFacet.defaultGaugePointFunction.selector;
bytes4 lwSelector = ILiquidityWeightFacet.maxWeight.selector;
whitelist.whitelistTokenWithEncodeType(
C.BEAN_ETH_WELL,
IBDVFacet.wellBdv.selector,
STALK_ISSUED_PER_BDV,
BEAN_ETH_SEEDS_PER_BDV,
0x01,
gpSelector,
lwSelector,
100e18,
100e6
);
}
function _setupWell() internal {
// deploy well
well = MockSetComponentsWell(_etch("MockSetComponentsWell.sol", C.BEAN_ETH_WELL, abi.encode("")));
vm.label(C.BEAN_ETH_WELL, "BeanEthWell");
well.init();
// deploy pump
mockPump = MockPump(_etch("MockPump.sol", C.BEANSTALK_PUMP, abi.encode(
bytes16(0x3ffe0000000000000000000000000000), // 0.5
bytes16(0x3ffd555555555555553cbcd83d925070), // 0.333333333333333333
12,
bytes16(0x3ffecccccccccccccccccccccccccccc) // 0.9
)));
vm.label(C.BEANSTALK_PUMP, "Pump");
// set pumps
Call[] memory pumps = new Call[](1);
pumps[0] = Call({ target: C.BEANSTALK_PUMP, data: "0x" });
well.setPumps(pumps);
// well function
address beanstalkWellFunctionAddress = deployCode("../node_modules/@beanstalk/wells/out/ConstantProduct2.sol:ConstantProduct2");
Call memory wellFunctionParameters = Call({ target: beanstalkWellFunctionAddress, data: "0x" });
well.setWellFunction(wellFunctionParameters);
// set tokens
IERC20[] memory tokens = new IERC20[](2);
tokens[0] = IERC20(C.BEAN);
tokens[1] = IERC20(C.WETH);
well.setTokens(tokens);
_setReservesForWell(1000000e6, 1000e18);
_setInstantaneousReservesForPump(1e18, 1e18);
well.setSymbol("MOCK");
// _etch("IWell.sol", C.BEAN_ETH_WELL, abi.encode("MOCK", address(well)));
console.log("well function address", beanstalkWellFunctionAddress);
console.log("well address", address(well));
}
function _setReservesForWell(uint256 _beanReserve, uint256 _wethReserve) internal {
uint256[] memory reserves = new uint256[](2);
reserves[0] = _beanReserve;
reserves[1] = _wethReserve;
well.setReserves(reserves);
}
function _setInstantaneousReservesForPump(uint256 balance1, uint256 balance2) internal {
uint256[] memory balances = new uint256[](2);
balances[0] = balance1;
balances[1] = balance2;
mockPump.setInstantaneousReserves(balances);
}
function _mockChainlinkAggregator() internal {
address chainlinkAggregatorAddress = address(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
chainlinkAggregator = MockChainlinkAggregator(_etch("MockChainlinkAggregator.sol", chainlinkAggregatorAddress, abi.encode("")));
chainlinkAggregator.setDecimals(6);
}
///////////////////////// Utilities /////////////////////////
function _abs(int256 v) pure internal returns (uint256) {
return uint256(v < 0 ? 0 : v);
}
function _reset(uint256 _snapId) internal returns (uint256) {
vm.revertTo(_snapId);
return vm.snapshot();
}
function _advanceInTime(uint256 _seconds) internal {
//vm.roll(block.number + 1);
vm.warp(block.timestamp + _seconds);
}
//////////////////////// Deploy /////////////////////////
function _etch(string memory _file, address _address, bytes memory args) internal returns (address) {
address codeaddress = deployCode(_file, args);
vm.etch(_address, at(codeaddress));
return _address;
}
function _mockToken(string memory _tokenName, address _tokenAddress) internal returns (MockToken) {
MockToken mockToken = MockToken(_etch("MockToken.sol", _tokenAddress,abi.encode(_tokenName,"")));
vm.label(address(mockToken), _tokenName);
return mockToken;
}
function _mockWeth() internal returns (MockWETH) {
address payable weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
vm.label(weth, "WETH");
mockWETH = MockWETH(payable(_etch("MockWETH.sol", weth, abi.encode("Wrapped Ether","WETH"))));
return mockWETH;
}
function _mockPrice() internal returns (BeanstalkPrice p) {
address PRICE_DEPLOYER = 0x884B463E078Ff26C4b83792dB9bEF33619a69767;
vm.prank(PRICE_DEPLOYER);
p = new BeanstalkPrice();
}
function _mockCurve() internal {
address THREE_CRV = address(C.threeCrv());
MockToken crv3 = _mockToken("3CRV", THREE_CRV);
MockToken(crv3).setDecimals(18);
//
Mock3Curve pool3 = Mock3Curve(_etch("Mock3Curve.sol", C.curve3PoolAddress(), abi.encode(""))); // 3Curve = 3Pool
Mock3Curve(pool3).set_virtual_price(1);
//
address STABLE_FACTORY = 0xB9fC157394Af804a3578134A6585C0dc9cc990d4;
MockCurveFactory stableFactory = MockCurveFactory(_etch("MockCurveFactory.sol", STABLE_FACTORY, abi.encode("")));
// address CRYPTO_REGISTRY = 0x8F942C20D02bEfc377D41445793068908E2250D0;
address CURVE_REGISTRY = 0x90E00ACe148ca3b23Ac1bC8C240C2a7Dd9c2d7f5;
_etch("MockToken.sol", CURVE_REGISTRY, abi.encode("")); // why this interface?
stableFactory.set_coins(C.CURVE_BEAN_METAPOOL, [
C.BEAN,
THREE_CRV,
address(0),
address(0)
]);
//
MockCurveZap curveZap = MockCurveZap(_etch("MockCurveZap.sol", C.curveZapAddress(), abi.encode("")));
curveZap.approve();
}
function _mockUniswap() internal {
//address UNIV3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
MockUniswapV3Factory uniFactory = MockUniswapV3Factory(new MockUniswapV3Factory());
// USDC
address ethUsdc =
uniFactory.createPool(
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2,//weth
address(C.usdc()),//usdc
3000
);
bytes memory code = at(ethUsdc);
address targetAddr = C.UNIV3_ETH_USDC_POOL;
vm.etch(targetAddr, code);
MockUniswapV3Pool(C.UNIV3_ETH_USDC_POOL).setOraclePrice(1000e6,18);
// USDT
address ethUsdt =
uniFactory.createPool(
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2,//weth
address(C.USDT),//usdt
3000
);
code = at(ethUsdt);
targetAddr = C.UNIV3_ETH_USDT_POOL;
vm.etch(targetAddr, code);
MockUniswapV3Pool(C.UNIV3_ETH_USDT_POOL).setOraclePrice(1000e6,18);
}
function _setEthUsdcPrice(uint256 _price) internal {
MockUniswapV3Pool(C.UNIV3_ETH_USDC_POOL).setOraclePrice(_price,uint8(18));
}
function _setEthUsdtPrice(uint256 _price) internal {
MockUniswapV3Pool(C.UNIV3_ETH_USDT_POOL).setOraclePrice(_price,uint8(18));
}
function _addEthUsdPriceChainlink(int256 _price) internal {
chainlinkAggregator.addRound(_price, block.timestamp, block.timestamp, 1);
}
function _setEthUsdPriceChainlink(uint80 _roundId, int256 _price) internal {
chainlinkAggregator.setRound(_roundId, _price, block.timestamp, block.timestamp, 1);
}
function _mockCurveMetapool() internal {
address THREE_CRV = address(C.threeCrv());
MockMeta3Curve p = MockMeta3Curve(_etch("MockMeta3Curve.sol", C.CURVE_BEAN_METAPOOL, abi.encode("")));
p.init(C.BEAN, THREE_CRV, C.curve3PoolAddress());
p.set_A_precise(1000);
p.set_virtual_price(1 wei);
}
function _mockUnripe() internal {
unripeBean = _mockToken("UNRIPE_BEAN", C.UNRIPE_BEAN);
unripeBean.setDecimals(6);
unripeLP = _mockToken("UNRIPE_BEAN:3CRV", C.UNRIPE_LP);
}
function _printAddresses() internal view {
console.log("C: Bean = %s", address(C.bean()));
}
function _cut(string memory _facetName, address _facetAddress)
internal
returns (IDiamondCut.FacetCut memory cut)
{
bytes4[] memory functionSelectors = generateSelectors(_facetName);
cut = IDiamondCut.FacetCut({
facetAddress: _facetAddress,
action: IDiamondCut.FacetCutAction.Add,
functionSelectors: functionSelectors
});
}
function generateSelectors(string memory _facetName) internal returns (bytes4[] memory selectors) {
//get string of contract methods
string[] memory cmd = new string[](4);
cmd[0] = "forge";
cmd[1] = "inspect";
cmd[2] = _facetName;
cmd[3] = "methods";
bytes memory res = vm.ffi(cmd);
string memory st = string(res);
// extract function signatures and take first 4 bytes of keccak
strings.slice memory s = st.toSlice();
// Skip TRACE lines if any
strings.slice memory nl = "\n".toSlice();
strings.slice memory trace = "TRACE".toSlice();
while (s.contains(trace)) {
s.split(nl);
}
strings.slice memory colon = ":".toSlice();
strings.slice memory comma = ",".toSlice();
strings.slice memory dbquote = '"'.toSlice();
selectors = new bytes4[]((s.count(colon)));
for (uint i = 0; i < selectors.length; i++) {
s.split(dbquote); // advance to next doublequote
// split at colon, extract string up to next doublequote for methodname
strings.slice memory method = s.split(colon).until(dbquote);
selectors[i] = bytes4(method.keccak());
strings.slice memory selectr = s.split(comma).until(dbquote); // advance s to the next comma
}
return selectors;
}
function _generateSelectors(string memory _facetName)
internal
returns (bytes4[] memory selectors)
{
string[] memory cmd = new string[](3);
cmd[0] = "node";
cmd[1] = "scripts/genSelectors.js";
cmd[2] = _facetName;
bytes memory res = vm.ffi(cmd);
selectors = abi.decode(res, (bytes4[]));
}
//gets bytecode at specific address (cant use address.code as we're in 0.7.6)
function at(address _addr) public view returns (bytes memory o_code) {
assembly {
// retrieve the size of the code
let size := extcodesize(_addr)
// allocate output byte array
// by using o_code = new bytes(size)
o_code := mload(0x40)
// new "memory end" including padding
mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
// store length in memory
mstore(o_code, size)
// actually retrieve the code, this needs assembly
extcodecopy(_addr, add(o_code, 0x20), 0, size)
}
}
// function initUser() internal {
// users = new Users();
// address[] memory _user = new address[](2);
// _user = users.createUsers(2);
// user = _user[0];
// user2 = _user[1];
// }
// /// @dev deploy `n` mock ERC20 tokens and sort by address
// function deployMockTokens(uint n) internal {
// IERC20[] memory _tokens = new IERC20[](n);
// for (uint i = 0; i < n; i++) {
// IERC20 temp = IERC20(
// new MockToken(
// string.concat("Token ", i.toString()), // name
// string.concat("TOKEN", i.toString()), // symbol
// 18 // decimals
// )
// );
// // Insertion sort
// uint j;
// if (i > 0) {
// for (j = i; j >= 1 && temp < _tokens[j - 1]; j--)
// _tokens[j] = _tokens[j - 1];
// _tokens[j] = temp;
// } else _tokens[0] = temp;
// }
// for (uint i = 0; i < n; i++) tokens.push(_tokens[i]);
// }
// /// @dev mint mock tokens to each recipient
// function mintTokens(address recipient, uint amount) internal {
// for (uint i = 0; i < tokens.length; i++)
// MockToken(address(tokens[i])).mint(recipient, amount);
// }
// /// @dev approve `spender` to use `owner` tokens
// function approveMaxTokens(address owner, address spender) prank(owner) internal {
// for (uint i = 0; i < tokens.length; i++)
// tokens[i].approve(spender, type(uint).max);
// }
// /// @dev add the same `amount` of liquidity for all underlying tokens
// function addLiquidityEqualAmount(address from, uint amount) prank(from) internal {
// uint[] memory amounts = new uint[](tokens.length);
// for (uint i = 0; i < tokens.length; i++) amounts[i] = amount;
// well.addLiquidity(amounts, 0, from);
// }
// /// @dev gets the first `n` mock tokens
// function getTokens(uint n)
// internal
// view
// returns (IERC20[] memory _tokens)
// {
// _tokens = new IERC20[](n);
// for (uint i; i < n; ++i) {
// _tokens[i] = tokens[i];
// }
// }
// /// @dev get `account` balance of each token, lp token, total lp token supply
// function getBalances(address account) internal view returns (Balances memory balances) {
// uint[] memory tokenBalances = new uint[](tokens.length);
// for (uint i = 0; i < tokenBalances.length; ++i) {
// tokenBalances[i] = tokens[i].balanceOf(account);
// }
// balances = Balances(
// tokenBalances,
// well.balanceOf(account),
// well.totalSupply()
// );
// }
/// @dev impersonate `from`
modifier prank(address from) {
vm.startPrank(from);
_;
vm.stopPrank();
}
modifier resetSnapshot() {
uint256 _snapId = vm.snapshot();
_;
vm.revertTo(_snapId);
}
}
anvil --fork-url https://rpc.ankr.com/eth