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

Encapsulate base vault state #1188

Closed
wants to merge 1 commit into from
Closed
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
235 changes: 152 additions & 83 deletions pkg/solidity-utils/test/foundry/utils/BaseTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,57 +16,76 @@ import { ERC4626TestToken } from "../../../contracts/test/ERC4626TestToken.sol";
import { ERC20TestToken } from "../../../contracts/test/ERC20TestToken.sol";
import { WETHTestToken } from "../../../contracts/test/WETHTestToken.sol";

abstract contract BaseTest is Test, GasSnapshot {
using CastingHelpers for *;

// Reasonable block.timestamp `MAY_1_2023`
uint32 internal constant START_TIMESTAMP = 1_682_899_200;

uint256 internal constant MAX_UINT256 = type(uint256).max;
// Raw token balances are stored in half a slot, so the max is uint128.
uint256 internal constant MAX_UINT128 = type(uint128).max;

struct Accounts {
// Default admin.
address payable internal admin;
uint256 internal adminKey;
address payable admin;
uint256 adminKey;
// Default liquidity provider.
address payable internal lp;
uint256 internal lpKey;
address payable lp;
uint256 lpKey;
// Default user.
address payable internal alice;
uint256 internal aliceKey;
address payable alice;
uint256 aliceKey;
// Default counterparty.
address payable internal bob;
uint256 internal bobKey;
address payable bob;
uint256 bobKey;
// Malicious user.
address payable internal hacker;
uint256 internal hackerKey;
address payable hacker;
uint256 hackerKey;
// Broke user.
address payable internal broke;
uint256 internal brokeUserKey;

address payable broke;
uint256 brokeUserKey;
// List of all users
address payable[] internal users;
uint256[] internal userKeys;
address payable[] users;
uint256[] userKeys;
}

struct TokensInfo {
// ERC20 tokens used for tests.
ERC20TestToken internal dai;
ERC20TestToken internal usdc;
WETHTestToken internal weth;
ERC20TestToken internal wsteth;
ERC20TestToken internal veBAL;
ERC4626TestToken internal waDAI;
ERC4626TestToken internal waWETH;
ERC4626TestToken internal waUSDC;

ERC20TestToken dai;
ERC20TestToken usdc;
WETHTestToken weth;
ERC20TestToken wsteth;
ERC20TestToken veBAL;
ERC4626TestToken waDAI;
ERC4626TestToken waWETH;
ERC4626TestToken waUSDC;
// List of all ERC20 tokens
IERC20[] internal tokens;

IERC20[] tokens;
// List of all ERC4626 tokens
IERC4626[] internal erc4626Tokens;
IERC4626[] erc4626Tokens;
}

struct BaseTestState {
Accounts accounts;
TokensInfo tokensInfo;
}

abstract contract BaseTest is Test, GasSnapshot {
using CastingHelpers for *;

// Reasonable block.timestamp `MAY_1_2023`
uint32 internal constant START_TIMESTAMP = 1_682_899_200;

uint256 internal constant DEFAULT_BALANCE = 1e9 * 1e18;

uint256 internal constant MAX_UINT256 = type(uint256).max;
// Raw token balances are stored in half a slot, so the max is uint128.
uint256 internal constant MAX_UINT128 = type(uint128).max;

// Default balance for accounts
uint256 internal defaultBalance = 1e9 * 1e18;
bool private _initialized;
uint256 private _defaultBalance = DEFAULT_BALANCE;
BaseTestState private _state;

// -------------------- Initializers --------------------
function setDefaultBalance(uint256 defaultBalance) internal {
if (_initialized) {
revert("Default balance can only be set before the test is initialized");
}

_defaultBalance = defaultBalance;
}

function setUp() public virtual {
// Set timestamp only if testing locally
Expand All @@ -76,66 +95,110 @@ abstract contract BaseTest is Test, GasSnapshot {
}

// Deploy the base test contracts.
dai = createERC20("DAI", 18);
ERC20TestToken dai = createERC20("DAI", 18);
// "USDC" is deliberately 18 decimals to test one thing at a time.
usdc = createERC20("USDC", 18);
wsteth = createERC20("WSTETH", 18);
weth = new WETHTestToken();
ERC20TestToken usdc = createERC20("USDC", 18);
ERC20TestToken wsteth = createERC20("WSTETH", 18);
ERC20TestToken weth = new WETHTestToken();
vm.label(address(weth), "WETH");
veBAL = createERC20("veBAL", 18);
ERC20TestToken veBAL = createERC20("veBAL", 18);

_state.tokensInfo.dai = dai;
_state.tokensInfo.usdc = usdc;
_state.tokensInfo.weth = weth;
_state.tokensInfo.wsteth = wsteth;
_state.tokensInfo.veBAL = veBAL;

// Fill the token list.
tokens.push(dai);
tokens.push(usdc);
tokens.push(weth);
tokens.push(wsteth);
_state.tokensInfo.tokens.push(dai);
_state.tokensInfo.tokens.push(usdc);
_state.tokensInfo.tokens.push(weth);
_state.tokensInfo.tokens.push(wsteth);
_state.tokensInfo.tokens.push(veBAL);

// Deploy ERC4626 tokens.
waDAI = createERC4626("Wrapped aDAI", "waDAI", 18, dai);
waWETH = createERC4626("Wrapped aWETH", "waWETH", 18, weth);
ERC4626TestToken waDAI = createERC4626("Wrapped aDAI", "waDAI", 18, dai);
ERC4626TestToken waWETH = createERC4626("Wrapped aWETH", "waWETH", 18, weth);
// "waUSDC" is deliberately 18 decimals to test one thing at a time.
waUSDC = createERC4626("Wrapped aUSDC", "waUSDC", 18, usdc);
ERC4626TestToken waUSDC = createERC4626("Wrapped aUSDC", "waUSDC", 18, usdc);

_state.tokensInfo.waDAI = waDAI;
_state.tokensInfo.waWETH = waWETH;
_state.tokensInfo.waUSDC = waUSDC;

// Fill the ERC4626 token list.
erc4626Tokens.push(waDAI);
erc4626Tokens.push(waWETH);
erc4626Tokens.push(waUSDC);
_state.tokensInfo.erc4626Tokens.push(waDAI);
_state.tokensInfo.erc4626Tokens.push(waWETH);
_state.tokensInfo.erc4626Tokens.push(waUSDC);

// Create users for testing.
(admin, adminKey) = createUser("admin");
(lp, lpKey) = createUser("lp");
(alice, aliceKey) = createUser("alice");
(bob, bobKey) = createUser("bob");
(hacker, hackerKey) = createUser("hacker");
(address admin, uint256 adminKey) = createUser("admin");
(address lp, uint256 lpKey) = createUser("lp");
(address alice, uint256 aliceKey) = createUser("alice");
(address bob, uint256 bobKey) = createUser("bob");
(address hacker, uint256 hackerKey) = createUser("hacker");
address brokeNonPay;
uint256 brokeUserKey;
(brokeNonPay, brokeUserKey) = makeAddrAndKey("broke");
broke = payable(brokeNonPay);
vm.label(broke, "broke");
vm.label(brokeNonPay, "broke");

_state.accounts.admin = admin;
_state.accounts.adminKey = adminKey;
_state.accounts.lp = lp;
_state.accounts.lpKey = lpKey;
_state.accounts.alice = alice;
_state.accounts.aliceKey = aliceKey;
_state.accounts.bob = bob;
_state.accounts.bobKey = bobKey;
_state.accounts.hacker = hacker;
_state.accounts.hackerKey = hackerKey;
_state.accounts.broke = payable(brokeNonPay);
_state.accounts.brokeUserKey = brokeUserKey;

_state.accounts.users.push(admin);
_state.accounts.userKeys.push(adminKey);
_state.accounts.users.push(lp);
_state.accounts.userKeys.push(lpKey);
_state.accounts.users.push(alice);
_state.accounts.userKeys.push(aliceKey);
_state.accounts.users.push(bob);
_state.accounts.userKeys.push(bobKey);
_state.accounts.users.push(hacker);
_state.accounts.userKeys.push(hackerKey);
_state.accounts.users.push(payable(brokeNonPay));
_state.accounts.userKeys.push(brokeUserKey);

// Must mock rates after giving wrapped tokens to users, but before creating pools and initializing buffers.
mockERC4626TokenRates();

// Fill the users list
users.push(admin);
userKeys.push(adminKey);
users.push(lp);
userKeys.push(lpKey);
users.push(alice);
userKeys.push(aliceKey);
users.push(bob);
userKeys.push(bobKey);
users.push(broke);
userKeys.push(brokeUserKey);
_initialized = true;
}

function isBaseTestInitialized() internal view returns (bool) {
return _initialized;
}

function getBaseTestState() internal view returns (BaseTestState memory) {
return _state;
}

function getTokens() internal view returns (TokensInfo memory) {
return _state.tokensInfo;
}

function getAccounts() internal view returns (Accounts memory) {
return _state.accounts;
}

// -------------------- Helpers --------------------

/**
* @notice Manipulate rates of ERC4626 tokens.
* @dev It's important to not have a 1:1 rate when testing ERC4626 tokens, so we can differentiate between
* wrapped and underlying amounts. For certain tests, we may need to override these rates for simplicity.
*/
function mockERC4626TokenRates() internal virtual {
waDAI.inflateUnderlyingOrWrapped(0, 6 * defaultBalance);
waUSDC.inflateUnderlyingOrWrapped(23 * defaultBalance, 0);
_state.tokensInfo.waDAI.inflateUnderlyingOrWrapped(0, 6 * _defaultBalance);
_state.tokensInfo.waUSDC.inflateUnderlyingOrWrapped(23 * _defaultBalance, 0);
}

function getSortedIndexes(
Expand Down Expand Up @@ -186,29 +249,35 @@ abstract contract BaseTest is Test, GasSnapshot {

/// @dev Generates a user, labels its address, and funds it with test assets.
function createUser(string memory name) internal returns (address payable, uint256) {
return createUser(name, _defaultBalance);
}

/// @dev Generates a user, labels its address, and funds it with test assets.
function createUser(string memory name, uint256 balance) internal returns (address payable, uint256) {
(address user, uint256 key) = makeAddrAndKey(name);
vm.label(user, name);
vm.deal(payable(user), defaultBalance);
vm.deal(payable(user), balance);

for (uint256 i = 0; i < tokens.length; ++i) {
deal(address(tokens[i]), user, defaultBalance);
for (uint256 i = 0; i < _state.tokensInfo.tokens.length; ++i) {
deal(address(_state.tokensInfotokens[i]), user, balance);
}

ERC4626TestToken[] memory erc4626Tokens = _state.tokensInfo.erc4626Tokens;
for (uint256 i = 0; i < erc4626Tokens.length; ++i) {
// Give underlying tokens to the user, for depositing in the wrapped token.
if (erc4626Tokens[i].asset() == address(weth)) {
vm.deal(user, user.balance + defaultBalance);
if (erc4626Tokens[i].asset() == address(_state.tokensInfo.weth)) {
vm.deal(user, user.balance + balance);

vm.prank(user);
weth.deposit{ value: defaultBalance }();
_state.tokensInfo.weth.deposit{ value: balance }();
} else {
ERC20TestToken(erc4626Tokens[i].asset()).mint(user, defaultBalance);
ERC20TestToken(erc4626Tokens[i].asset()).mint(user, balance);
}

// Deposit underlying to mint wrapped tokens to the user.
vm.startPrank(user);
IERC20(erc4626Tokens[i].asset()).approve(address(erc4626Tokens[i]), defaultBalance);
erc4626Tokens[i].deposit(defaultBalance, user);
IERC20(erc4626Tokens[i].asset()).approve(address(erc4626Tokens[i]), balance);
erc4626Tokens[i].deposit(balance, user);
vm.stopPrank();
}

Expand Down
8 changes: 5 additions & 3 deletions pkg/vault/test/foundry/AuxiliaryEvent.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@ import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaul

import { PoolMock } from "../../contracts/test/PoolMock.sol";

import { BaseVaultTest } from "./utils/BaseVaultTest.sol";
import { BaseTestState, BaseVaultTest } from "./utils/BaseVaultTest.sol";

contract AuxiliaryEventTest is BaseVaultTest {
function setUp() public virtual override {
BaseVaultTest.setUp();
}

function testWithNonPoolCall() public {
BaseTestState bState = getBaseTestState();

// Only registered pools can emit aux event
vm.expectRevert(abi.encodeWithSelector(IVaultErrors.PoolNotRegistered.selector, admin));
vm.expectRevert(abi.encodeWithSelector(IVaultErrors.PoolNotRegistered.selector, bState.accounts.admin));

vm.prank(admin);
vm.prank(bState.accounts.admin);
vault.emitAuxiliaryEvent("TestEvent", abi.encode(777));
}

Expand Down
6 changes: 4 additions & 2 deletions pkg/vault/test/foundry/BalancerPoolTokenTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/Fixe
import { BalancerPoolToken } from "../../contracts/BalancerPoolToken.sol";
import { PoolMock } from "../../contracts/test/PoolMock.sol";

import { BaseVaultTest } from "./utils/BaseVaultTest.sol";
import { BaseTestState, BaseVaultTest } from "./utils/BaseVaultTest.sol";

contract BalancerPoolTokenTest is BaseVaultTest {
using ArrayHelpers for *;
Expand Down Expand Up @@ -481,8 +481,10 @@ contract BalancerPoolTokenTest is BaseVaultTest {
}

function testGetRate() public {
BaseTestState bState = getBaseTestState();

// Init pool, so it has a BPT supply and rate can be calculated.
vm.startPrank(lp);
vm.startPrank(bState.accounts.lp);
IERC20[] memory tokens = vault.getPoolTokens(address(poolToken));
router.initialize(
address(poolToken),
Expand Down
8 changes: 5 additions & 3 deletions pkg/vault/test/foundry/BatchRouter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IBatchRouter } from "@balancer-labs/v3-interfaces/contracts/vault/IBatc

import { MOCK_BATCH_ROUTER_VERSION } from "../../contracts/test/BatchRouterMock.sol";
import { RouterCommon } from "../../contracts/RouterCommon.sol";
import { BaseVaultTest } from "./utils/BaseVaultTest.sol";
import { BaseTestState, BaseVaultTest } from "./utils/BaseVaultTest.sol";

contract BatchRouterTest is BaseVaultTest {
function setUp() public virtual override {
Expand All @@ -36,9 +36,11 @@ contract BatchRouterTest is BaseVaultTest {
}

function testQuerySingleStepRemove() public {
BaseTestState memory state = getBaseTestState();

// create a swap step and query the batch router, where the first token is the bpt.
IBatchRouter.SwapPathStep[] memory step = new IBatchRouter.SwapPathStep[](1);
step[0] = IBatchRouter.SwapPathStep(address(pool), IERC20(address(dai)), false);
step[0] = IBatchRouter.SwapPathStep(address(pool), IERC20(address(state.tokensInfo.dai)), false);

uint256 totalSupply = IERC20(pool).totalSupply();
uint256 bptAmountIn = 1e18;
Expand All @@ -55,7 +57,7 @@ contract BatchRouterTest is BaseVaultTest {
IBatchRouter.SwapPathExactAmountIn[] memory paths = new IBatchRouter.SwapPathExactAmountIn[](1);
paths[0] = path;

vm.prank(alice, address(0));
vm.prank(state.accounts.alice, address(0));
batchRouter.querySwapExactIn(paths, address(0), bytes(""));
}
}
Loading
Loading