-
Notifications
You must be signed in to change notification settings - Fork 428
release(refactor): integration overhaul #1337
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
base: main
Are you sure you want to change the base?
release(refactor): integration overhaul #1337
Conversation
b942b32
to
69927a1
Compare
|
||
# NOTE: Use address(0) for any null/undeployed addresses. | ||
|
||
[governance] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm.. can we get Zeus to generate these for us somehow? That way it's super easy to update after a deploy/upgrade.
3432230
to
3130926
Compare
refactor: move time travel getters to separate file chore: wand vacuum src/test/integration --delete refactor: reorganize + rename `IntegrationCheckUtils` -> `IntegrationChecks` chore: make fmt refactor: fixed time machine + beacon chain addresses fix: ci refactor: remove old random getters refactor: randomness refactor: reorganize chore: make fmt fix: ci perf: remove some vm.assumes perf: remove unused user setup return params chore: move `TimeMachine.t.sol` -> `src/test/utils` chore: wand vacuum src/test/integration --delete perf: optimize bitmap conversion + remove shuffle function refactor: update randomization functions + add parsing methods chore: remove unused imports feat: add `Constants.t.sol` refactor: use cheats alias refactor: remove logger from `BeaconChainMock` and `TimeMachine` refactor: renaming refactor: integration deployer + logger fix: ci refactor: rename deprecatedInterfaces -> deprecated refactor: move `src/test/integration/mocks` -> `src/test/mocks` refactor: sort strategies once chore: reduce fuzz runs for time intensive tests chore: forge snapshot refactor: condense storage refactor: cleanup refactor: check_Withdrawal_AsTokens_State chore: weekly fuzz seed reduce rpc usage fix: ci chore: comment out failing tests refactor: use new assertEq array fns chore: make fmt perf: _init is called in setUp (once per run) chore: make fmt chore: only use fuzz seed for fork testing refactor: cleanup `IntegrationDeployer` chore: remove gas snapshot chore: make fmt chore: remove vm.assumes perf: alm multi single run (too expensive) perf: test_multiple_deposits single run (too expensive) perf(refactor): ALM_RegisterAndModify.t.sol chore: make fmt refactor: remove redundant checks chore: make fmt perf: single run for `test_completeCP_withNoAddedShares` (too expensive) refactor: cleanup empty space chore: mv `UpgradeTest` to `integration/tests/upgrade/` chore: mv `TypeImporter` to `interfaces/ICore.sol` chore: remove snapshots fix: tests test: compiles fix: compile errors
**Motivation:** We want to support Zeus config parsing to enable testing of queued deployments. Our original deployment parser lacked the flexibility needed to support Zeus without requiring changes to Zeus itself. Additionally, the `User` and `AVS` contracts deployed by the `IntegrationDeployer` aren't able to properly read config from `IntegrationDeployer` leading to bandaid solutions. **Modifications:** - Removed `InitialDeploymentParser`: About 1k lines -> 100 for parsing. - Added `ConfigParser`: A parser that supports both fast TOML parsing and Zeus-style parsing. - Added `ConfigGetters`: A abstraction that allows contracts deployed by the `IntegrationDeployer` to easily access the current configuration. **Result:** Contracts deployed through the `IntegrationDeployer` can now reliably access their configuration, and Zeus integration is possible without needing to modify Zeus.
**Motivation:** *Explain here the context, and why you're making that change. What is the problem you're trying to solve.* **Modifications:** *Describe the modifications you've done.* **Result:** *After your change, what will change.*
9b9e243
to
ce780f8
Compare
"strategyFactoryImplementation": "0x8b1F09f8292fd658Da35b9b3b1d4F7d1C0F3F592", | ||
"strategyManager": "0x70f8bC2Da145b434de66114ac539c9756eF64fb3", | ||
"strategyManagerImplementation": "0x1562BfE7Cb4644ff030C1dE4aA5A9aBb88a61aeC", | ||
"TestToken": "0xcae746B46013Bf4D43B8E2B9e98F9DBE2461532A", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we nuke this file? It keeps popping up in places...
What's creating it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah pretty sure I removed the logic that generates this, think this file just needs to be removed now.
import "../Env.sol"; | ||
import {Deploy} from "./1-deployContracts.s.sol"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should not have any diffs to prior release scripts.
@@ -1,3 +1,3 @@ | |||
{ | |||
"gasUsed": "47579" | |||
"gasUsed": "137087" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this file? Can we gitignore snapshots, or figure out what's generating this?
(is it useful/used somewhere?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be fixed as well now, just need to delete this file.
|
||
contract ConfigGettersTest is ConfigGetters, Test { | ||
function setUp() public { | ||
vm.createSelectFork(vm.rpcUrl("mainnet"), 22_181_590); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's pull this block value out into a constant and leave a comment explaining what it is.
(i assume this is post-slashing upgrade?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also I think this just gets run with our standard local test suite, right?
That might be annoying if someone's not trying to run fork tests, but this test fails?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, will move/refactor these tests a bit.
return proxy; | ||
} | ||
|
||
function emptyContract() external returns (address deployed) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be removed, doesn't work.
using ConfigParser for *; | ||
|
||
function test_ParseTOML() public { | ||
vm.createSelectFork(vm.rpcUrl("mainnet"), 22_181_590); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See previous comments about this block value and running this by default
TODO: Bump |
import "src/test/integration/deprecatedInterfaces/mainnet/IEigenPod.sol"; | ||
import "src/test/integration/deprecatedInterfaces/mainnet/IEigenPodManager.sol"; | ||
import "src/test/integration/deprecatedInterfaces/mainnet/IStrategyManager.sol"; | ||
import "src/test/integration/deprecated/mainnet/IEigenPod.sol"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's delete User_M1 entirely. It's not used anywhere.
import "src/test/integration/deprecatedInterfaces/mainnet/IStrategyManager.sol"; | ||
import "src/test/integration/deprecatedInterfaces/mainnet/IDelegationManager.sol"; | ||
|
||
import "src/test/integration/deprecated/mainnet/IDelegationManager.sol"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can/should delete User_M2
, but it might require just a little bit more work because it's still used (though we don't need to be using it).
Let's mark this work for a followup PR.
AVS avs; | ||
SlashingParams slashParams; | ||
SlashingParams slashingParams; | ||
|
||
User operator; | ||
OperatorSet operatorSet; | ||
AllocateParams allocateParams; | ||
|
||
User staker; | ||
IERC20[] tokens; | ||
IStrategy[] strategies; | ||
uint[] initTokenBalances; | ||
uint[] initDepositShares; | ||
uint[] withdrawableShares; | ||
// Withdrawal[] withdrawals; // cannot copy from memory to storage easily | ||
bytes32[] withdrawalRoots; | ||
uint[] numTokensRemaining; | ||
|
||
uint64 beaconBalanceGwei; | ||
uint40[] validators; | ||
uint40[] slashedValidators; | ||
uint64 slashedGwei; | ||
uint64 slashedAmountGwei; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are all these state variables?
They're shadowed throughout the contract -- can we get rid of them? Tests that need persistent state across setups can just declare it themselves.
(I know we have a bunch of tests that declare similar storage, but people are gonna declare storage variables no matter what - I think it's good to have that at the surface level)
function _completeWithdrawal( | ||
User staker, | ||
User operator, | ||
Withdrawal[] memory withdrawals, | ||
IStrategy[] memory strategies, | ||
uint[] memory withdrawalShares, | ||
uint[] memory expectedTokens | ||
) internal { | ||
for (uint i = 0; i < withdrawals.length; i++) { | ||
if (_randBool()) { | ||
staker.completeWithdrawalAsTokens(withdrawals[i]); | ||
check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], withdrawalShares, expectedTokens); | ||
} else { | ||
staker.completeWithdrawalAsShares(withdrawals[i]); | ||
check_Withdrawal_AsShares_State(staker, operator, withdrawals[i], withdrawals[i].strategies, withdrawalShares); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this supposed to be in this contract? It feels out of place
/** | ||
* | ||
* EIGENPOD CHECKS | ||
* | ||
*/ | ||
function check_VerifyWC_State(User staker, uint40[] memory validators, uint64 beaconBalanceGwei) internal { | ||
function check_VerifyWC_State(User staker, uint40[] memory validators, uint64 beaconBalanceGwei) public { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Putting a pin in this, why are these public now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok i figured this meant tests are calling them like checker.check_VerifyWC_State()
, but they're not.
could we just keep these internal to keep the diff down, then? maybe im missing where this is needed?
@@ -245,8 +283,8 @@ contract IntegrationCheckUtils is IntegrationBase { | |||
staker, strategies, shares, "staker should expected shares in each strategy after depositing" | |||
); | |||
|
|||
if (delegationManager.isDelegated(address(staker))) { | |||
User operator = User(payable(delegationManager.delegatedTo(address(staker)))); | |||
if (delegationManager().isDelegated(address(staker))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noting, I feel like making these functions (instead of variables) is a slight downgrade for brevity. But I'm fine with it atm...
/// Complete withdrawal(s): | ||
// The staker will complete the withdrawal as shares | ||
// | ||
// ... check that the withdrawal is not pending, that the token balances of the staker and operator are unchanged, | ||
// that the withdrawer received the expected shares, and that that the total shares of each o | ||
// strategy withdrawn remains unchanged | ||
assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending"); | ||
assert_WithdrawalNotPending( | ||
delegationManager().calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Followup work - we should be able to only pass in withdrawal
and have the underlying method get the root.
function isForktest() public view returns (bool) { | ||
return _hash("forktest") == _hash(cheats.envOr(string("FOUNDRY_PROFILE"), string("default"))); | ||
/// @dev Configures the possible asset and user types for the random users. | ||
function _configRand(uint _assetTypes, uint _userTypes) internal virtual { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this method isn't used anymore - so let's just get rid of it?
// Whitelist the strategies | ||
cheats.prank(strategyManager().strategyWhitelister()); | ||
strategyManager().addStrategiesToDepositWhitelist(allStrats); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're adding the beacon chain strat to the deposit whitelist here
@@ -7,9 +7,10 @@ import "src/contracts/libraries/BeaconChainProofs.sol"; | |||
import "src/contracts/libraries/Merkle.sol"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Request - let's please move all the beacon chain / EIP mock contracts to a subfolder, for organization's sake.
e.g:
mocks/beacon/
- BeaconChainMock.t.sol
- BeaconChainMock_Deneb.t.sol
- EIP_4788_Oracle_Mock.t.sol
I'm adding 2 more mocks for Pectra predeploys in MOOCOW, and having all of them in the same place is super helpful. (they're closely related anyway, since the beacon chain deploys and "drives" the predeploys)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function _init() internal virtual override { | ||
if (!forkConfig.supportEigenPodTests) cheats.skip(true); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would we not support these?
assert_HasExpectedShares(staker, strategies, shares, "shares should decrease by slashed amount"); | ||
} | ||
} | ||
// // SPDX-License-Identifier: BUSL-1.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotta fix this before we merge
return expectedTokenDeltas; | ||
} | ||
} | ||
// // SPDX-License-Identifier: BUSL-1.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotta fix this before we merge
|
||
cheats.createSelectFork(cheats.rpcUrl("mainnet"), mainnetForkBlock); | ||
/// @dev Sets up the integration tests for mainnet. | ||
function _setUpFork(bool upgrade) public virtual { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is pretty messy, can we clean it up?
- Confusion between
bool upgrade
vsforkConfig.upgradeBeforeTesting
vsisUpgraded
forkConfig.supportEigenPodTests
evaluated in multiple places; let's please keep that to 1 (im not sure what this is for in the first place though)- First part of the method does either
_executeTimelockUpgrade
or_deployProxies + _upgradeProxies
. Later in the method, we might also do_upgradeMainnetContracts
. Is that mutually exclusive, or can that happen in addition to the earlier upgrade stuff? Logic should be more clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I realized _upgradeMainnetContracts
is just _upgradeProxies
, but disguised... let's collapse these definitions and condense upgrade logic pls
// Deploy ProxyAdmin, PauserRegistry, and executorMultisig. | ||
config.governance.proxyAdmin = new ProxyAdmin(); | ||
config.governance.pauserRegistry = new PauserRegistry(PAUSER.toArray(), UNPAUSER); | ||
config.governance.executorMultisig = address(proxyAdmin().owner()); | ||
|
||
_deployProxies(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see we got rid of _deployImplementations
here, which is fine - I think moving this to _upgradeProxies
is clean.
That said, it feels little inconsistent that the Config
contract has fields for "impls" - but I don't think we set them anywhere. IIUC, these are only set if we're doing a fork test and they get read from config?
We should do ONE of the following:
- Remove impls from config/parsing entirely. Maybe they're just not needed!
- Make sure config is updated with impls no matter what env you're running in
// if (eq(profile, "default") || eq(profile, "mainnet") || (eq(profile, "forktest") && isUpgraded)) { | ||
// user = userType == DEFAULT ? new User(name) : User(new User_AltMethods(name)); | ||
// } else if (eq(profile, "forktest") && !isUpgraded) { | ||
// user = User(new User_M2(name)); | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If these are fully disabled lets just completely get rid of em.
// Ensure the staker has at least 64 ETH to deposit. | ||
if (initTokenBalances[0] < 64 ether) { | ||
initTokenBalances[0] = 64 ether; | ||
cheats.deal(address(staker), 64 ether); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we just need better "create user" syntax tbh. can leave that for a followup PR though.
|
||
_createPod(); | ||
constructor(string memory name) { | ||
config = ConfigGetters(address(msg.sender)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just inherit ConfigGetters?
I'm actually a little lost on what's going on here - are we calling methods on User when we access config.delegationManager()
?
@@ -176,7 +176,7 @@ contract BeaconChainMock is Logger { | |||
/// - Setting their current/effective balance | |||
/// - Assigning them a new, unique index | |||
function newValidator(bytes memory withdrawalCreds) public payable returns (uint40) { | |||
print.method("newValidator"); | |||
console.log("BeaconChain.newValidator()"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why get rid of Logger?
Feels like we print inconsistently in a lot of places,,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See questions/comments above.
Overall nice work, there's a lot of good cleanup here.
Motivation:
This PR refactors the integration test framework to improve performance, maintainability, and organization. The existing integration test structure had grown complex with multiple responsibilities mixed in different files, making it harder to understand and maintain. The goal is to create a more modular, better organized test framework with clear separation of concerns to make writing and maintaining integration tests easier.
Modifications:
Created two new utility files to better organize the codebase:
IntegrationGetters.t.sol
: Centralizes all state-fetching functionality, providing methods to get both current and previous state snapshots.IntegrationUtils.t.sol
: Contains helper functions for creating users, generating allocation parameters, and other common tasks.Restructured folder organization:
deprecatedInterfaces/
todeprecated/
./test/utils/
folder.integration/mocks/
totest/mocks/
.Renamed test files to follow a more consistent naming convention:
ALM_RegisterAndModify.t.sol
→Deposit_Delegate_Modify.t.sol
.Refactored the
TimeMachine.t.sol
contract with improved functionality and moved it toutils/
folder to make it accessible beyond integration tests.Added a new
Constants.t.sol
file to centralize commonly used constants.Added a
FOUNDRY_FUZZ_SEED
to fork tests that updates weekly (this significantly reduces our RPC usage).Moved
_init
intosetUp()
(this means setup happens once per fuzz run, rather than once per test).Sort
strategies
array once, rather than JIT (sorting an array in place is time intensive).Refactored randomness sourcing to use Vm.
Previously, we were using a
uint24 _random
variable combined with hashing to generate subsequent random values.While this pattern simplified test reruns (allowing the reuse of the same _random value for consistency), it has notable drawbacks. Randomness isn't unevenly distributed, leading to modulo bias, and the process is computationally expensive.
Additionally, the same functionality can now be easily achieved through Foundry flags.
Result:
After these changes, the integration test framework will be more modular with clear separation of concerns, making it easier to understand and maintain.
Integration CI times has decreased from 15-25mins -> 5-6mins.
NOTE: There's still quite a bit that could be done, don't want this PR getting too big.