Skip to content

Commit

Permalink
wip: integration test framework
Browse files Browse the repository at this point in the history
  • Loading branch information
wadealexc committed Nov 16, 2023
1 parent 75e5943 commit 146b09a
Show file tree
Hide file tree
Showing 5 changed files with 556 additions and 0 deletions.
7 changes: 7 additions & 0 deletions src/test/integration/CONCEPTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
- Stakers and Operators are both represented by User/IUser, as they can do pretty much all the same things.
- Should rely on good variable naming to distinguish
- Minimal assertions/cheats used in User contract. These are for carrying out user behaviors, only. Exception:
- User methods snapshot state before performing actions
- Top-level error messages are passed into helper assert methods so that it's always clear where an error came from
- User contract should have an interface as similar as possible to the contract interfaces, so it feels like calling an EigenLayer method rather than some weird abstraction. Exceptions for things like:
- `user.depositIntoEigenLayer(strats, tokenBalances)`
31 changes: 31 additions & 0 deletions src/test/integration/Global.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.12;

contract Global {

Vm cheats = Vm(HEVM_ADDRESS);

bool pastExists = false;
uint lastSnapshot;

function createSnapshot() public returns (uint) {
uint snapshot = cheats.snapshot();
lastSnapshot = snapshot;
pastExists = true;
return snapshot;
}

function warpToLast() public returns (uint curState) {
// Safety check to make sure createSnapshot is called before attempting to warp
// so we don't accidentally prevent our own births
assertTrue(pastExists, "Global.warpToPast: invalid usage, past does not exist");

curState = cheats.snapshot();
cheats.revertTo(lastSnapshot);
return curState;
}

function warpToPresent(uint curState) public {
cheats.revertTo(curState);
}
}
268 changes: 268 additions & 0 deletions src/test/integration/IntegrationBase.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.12;

abstract contract IntegrationBase {

Vm cheats = Vm(HEVM_ADDRESS);

IDelegationManager delegationManager;
IEigenPodManager eigenPodManager;
IStrategyManager strategyManager;

Global global;

function setUp() virtual public {
// Deploy contracts

// Deploy strategies and assign to strategymgr

// initialize contracts
}

/**
* Gen/Init methods:
*/

/// Creates a blank user object and sanity checks setup
function _newRegisteredOperator() internal returns (IUser) {
IUser operator = IUser(new User());

operator.registerAsOperator();

assertTrue(delegationManager.isOperator(operator), "_gen_Operator_Registered: operator should be registered");
assert_HasNoDelegatableShares(operator, "new operator should not have delegatable shares");

return IUser(operator);
}

/// This method is very unfinished, but this should combine with _configRand in Test.t.sol
/// to generate a User object with the specific assets allowed in the test.
function _newRandomStaker() internal returns (IUser) {
UserType uType = UserType(_randUint({ min: type(UserType).min, max: type(UserType).max }));

User user;
if (uType == UserType.MIXED_ASSETS) {
user = new
}

IStrategy[] memory strategies = _selectRandomStrategies();
uint[] memory tokenBalances = _awardRandomUnderlyingBalances(staker, strategies);

assertEq(strategies.length, tokenBalances.length, "todo");
assertTrue(strategies.length != 0, "todo");

// Validate that staker has correct balance in each selected strategy's underlying token
for (uint i = 0; i < strategies.length; i++) {
IStrategy strat = strategies[i];
uint balance = tokenBalances[i];

if (strat == BEACONCHAIN_ETH_STRAT) {
assertEq(tokenBalance, address(staker).balance, "staker should hold correct amount of native ETH");
} else {
assertEq(
tokenBalance, strat.underlyingToken.balanceOf(address(staker)),
"staker should hold correct amount of underlying token"
);
}
}

return staker;
}

/**
* Common assertions:
*/

function assert_HasNoDelegatableShares(IUser user, string memory err) internal {
(IStrategy[] memory strategies, uint[] memory shares) =
delegationManager.getDelegatableShares(user);

assertEq(strategies.length, 0, err);
assertEq(strategies.length, shares.length, "assert_HasNoDelegatableShares: return length mismatch");
}

function assert_HasNoUnderlyingTokenBalance(IUser user, IStrategy[] memory strategies, string memory err) internal {
for (uint i = 0; i < strategies.length; i++) {
IStrategy strat = strategies[i];

uint tokenBalance;
if (strat == BEACONCHAIN_ETH_STRAT) {
tokenBalance = address(user).balance;
} else {
tokenBalance = strat.underlyingToken.balanceOf(user);
}

assertEq(0, tokenBalance, err);
}
}

function assert_HasExpectedShares(IUser user, IStrategy[] memory strategies, uint[] memory expectedShares) internal {
for (uint i = 0; i < strategies.length; i++) {
IStrategy strat = strategies[i];

uint actualShares;
if (strat == BEACONCHAIN_ETH_STRAT) {
actualShares = eigenPodManager.podOwnerShares(user);
} else {
actualShares = strategyManager.stakerStrategyShares(user);
}

assertEq(expectedShares[i], actualShares, err);
}
}

function assert_HasOperatorShares(IUser user, IStrategy[] memory strategies, uint[] memory expectedShares) internal {
for (uint i = 0; i < strategies.length; i++) {
IStrategy strat = strategies[i];

uint actualShares = delegationManager.operatorShares(user, strat);

assertEq(expectedShares[i], actualShares, err);
}
}

/**
* Snapshot assertions combine Global's snapshots with assertions
* that allow easy comparisons between prev/cur values
*/

/// @dev Check that the operator has `addedShares` additional shares for each
/// strategy since the last snapshot
function assert_Snap_AddedOperatorShares(
IUser operator,
IStrategy[] memory strategies,
uint[] memory addedShares,
string memory err
) internal {
// Put current state on stack and revert to prior state to fetch
// previous operator shares
uint curState = global.warpToLast();
uint[] memory prevShares = _getOperatorShares(operator, strategies);
global.warpToPresent(curState); // Snap back to reality

uint[] memory curShares = _getOperatorShares(operator, strategies);

// For each strategy, check (prev + added == cur)
for (uint i = 0; i < strategies.length; i++) {
assertEq(prevShares[i] + addedShares[i], curShares[i], err);
}
}

/// @dev Check that the operator has `removedShares` prior shares for each
/// strategy since the last snapshot
function assert_Snap_RemovedOperatorShares(
IUser operator,
IStrategy[] memory strategies,
uint[] memory removedShares,
string memory err
) internal {
// Put current state on stack and revert to prior state to fetch
// previous operator shares
uint curState = global.warpToLast();
uint[] memory prevShares = _getOperatorShares(operator, strategies);
global.warpToPresent(curState); // Snap back to reality

uint[] memory curShares = _getOperatorShares(operator, strategies);

// For each strategy, check (prev - removed == cur)
for (uint i = 0; i < strategies.length; i++) {
assertEq(prevShares[i] - removedShares[i], curShares[i], err);
}
}

/// @dev Check that the staker has `addedShares` additional shares for each
/// strategy since the last snapshot
function assert_Snap_AddedStakerShares(
IUser staker,
IStrategy[] memory strategies,
uint[] memory addedShares,
string memory err
) internal {
// Put current state on stack and revert to prior state to fetch
// previous staker shares
uint curState = global.warpToLast();
uint[] memory prevShares = _getStakerShares(staker, strategies);
global.warpToPresent(curState); // Snap back to reality

uint[] memory curShares = _getStakerShares(staker, strategies);

// For each strategy, check (prev + added == cur)
for (uint i = 0; i < strategies.length; i++) {
assertEq(prevShares[i] + addedShares[i], curShares[i], err);
}
}

/// @dev Check that the staker has `removedShares` prior shares for each
/// strategy since the last snapshot
function assert_Snap_RemovedStakerShares(
IUser staker,
IStrategy[] memory strategies,
uint[] memory removedShares,
string memory err
) internal {
// Put current state on stack and revert to prior state to fetch
// previous staker shares
uint curState = global.warpToLast();
uint[] memory prevShares = _getStakerShares(staker, strategies);
global.warpToPresent(curState); // Snap back to reality

uint[] memory curShares = _getStakerShares(staker, strategies);

// For each strategy, check (prev - removed == cur)
for (uint i = 0; i < strategies.length; i++) {
assertEq(prevShares[i] - removedShares[i], curShares[i], err);
}
}

/**
* Helpful getters:
*/

/// @dev For some strategies/underlying token balances, calculate the expected shares received
/// from depositing all tokens
function _calculateExpectedShares(IStrategy[] memory strategies, uint[] memory tokenBalances) internal view returns (uint[] memory) {
uint[] memory expectedShares = new uint[](strategies.length);

for (uint i = 0; i < strategies.length; i++) {
IStrategy strat = strategies[i];

uint tokenBalance = tokenBalances[i];
if (strat == BEACONCHAIN_ETH_STRAT) {
// TODO - need to calculate this
// expectedShares[i] = eigenPodManager.underlyingToShares(tokenBalance);
} else {
expectedShares[i] = strat.underlyingToShares(tokenBalance);
}
}

return expectedShares;
}

/// @dev Looks up each strategy and returns a list of the operator's shares
function _getOperatorShares(IUser operator, IStrategy[] memory strategies) internal view returns (uint[] memory) {
uint[] memory curShares = new uint[](strategies.length);

for (uint i = 0; i < strategies.length; i++) {
curShares[i] = delegationManager.operatorShares(operator, strategies[i]);
}

return curShares;
}

/// @dev Looks up each strategy and returns a list of the staker's shares
function _getStakerShares(IUser staker, IStrategy[] memory strategies) internal view returns (uint[] memory) {
uint[] memory curShares = new uint[](strategies.length);

for (uint i = 0; i < strategies.length; i++) {
IStrategy strat = strategies[i];

if (strat == BEACONCHAIN_ETH_STRAT) {
curShares[i] = eigenPodManager.podOwnerShares(staker);
} else {
curShares[i] = strategyManager.stakerStrategyShares(staker, strat);
}
}

return curShares;
}
}
Loading

0 comments on commit 146b09a

Please sign in to comment.