Skip to content

Commit

Permalink
AVS-507: Integration test scaffold: add anvil chain with deployed con…
Browse files Browse the repository at this point in the history
…tracts to be able to test sdk features like bindings, txmgr, etc (#176)

* wip: deploying contracts to anvil state
this requires the changes in Layr-Labs/eigenlayer-contracts#495 to be merged first

make generate-bindings exit on error

update eigenlayer-middleware to head of dev branch

make bindings

* fix compilation

* fix tests

* fix start-anvil-chain command and add to makefile

* chore: forge init

* clean-up contracts forge folder

* fix anvil scripts

* added avs deployment contracts and scripts

* add ContractsRegistry to generate-bindings + make bindings

* update StartAnvilContainer + make egnaddrs test use new deployed anvil db json file

* Refactored DeployMockAvs into 2 separate contracts (one for serviceManager, one for registries)

* small refactor + cleanup of DeployMockAvs
  • Loading branch information
samlaf authored Jun 14, 2024
1 parent 60b4426 commit 1623b01
Show file tree
Hide file tree
Showing 45 changed files with 2,104 additions and 364 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "contracts/lib/eigenlayer-middleware"]
path = contracts/lib/eigenlayer-middleware
url = [email protected]:Layr-Labs/eigenlayer-middleware.git
[submodule "contracts/lib/forge-std"]
path = contracts/lib/forge-std
url = [email protected]/foundry-rs/forge-std
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,12 @@ format-lines: ## formats all go files with golines

lint: ## runs all linters
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
golangci-lint run ./...
golangci-lint run ./...

___CONTRACTS___: ##

deploy-contracts-to-anvil-and-save-state: ##
./contracts/anvil/deploy-contracts-save-anvil-state.sh

start-anvil-with-contracts-deployed: ##
./contracts/anvil/start-anvil-chain-with-el-and-avs-deployed.sh
2 changes: 1 addition & 1 deletion chainio/clients/elcontracts/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func (r *ELChainReader) GetOperatorDetails(opts *bind.CallOpts, operator types.O

return types.Operator{
Address: operator.Address,
EarningsReceiverAddress: operatorDetails.EarningsReceiver.Hex(),
EarningsReceiverAddress: operatorDetails.DeprecatedEarningsReceiver.Hex(),
StakerOptOutWindowBlocks: operatorDetails.StakerOptOutWindowBlocks,
DelegationApproverAddress: operatorDetails.DelegationApprover.Hex(),
}, nil
Expand Down
12 changes: 6 additions & 6 deletions chainio/clients/elcontracts/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ func BuildELChainWriter(
func (w *ELChainWriter) RegisterAsOperator(ctx context.Context, operator types.Operator) (*gethtypes.Receipt, error) {
w.logger.Infof("registering operator %s to EigenLayer", operator.Address)
opDetails := delegationmanager.IDelegationManagerOperatorDetails{
EarningsReceiver: gethcommon.HexToAddress(operator.EarningsReceiverAddress),
StakerOptOutWindowBlocks: operator.StakerOptOutWindowBlocks,
DelegationApprover: gethcommon.HexToAddress(operator.DelegationApproverAddress),
DeprecatedEarningsReceiver: gethcommon.HexToAddress(operator.EarningsReceiverAddress),
StakerOptOutWindowBlocks: operator.StakerOptOutWindowBlocks,
DelegationApprover: gethcommon.HexToAddress(operator.DelegationApproverAddress),
}

noSendTxOpts, err := w.txMgr.GetNoSendTxOpts()
Expand All @@ -143,9 +143,9 @@ func (w *ELChainWriter) UpdateOperatorDetails(

w.logger.Infof("updating operator details of operator %s to EigenLayer", operator.Address)
opDetails := delegationmanager.IDelegationManagerOperatorDetails{
EarningsReceiver: gethcommon.HexToAddress(operator.EarningsReceiverAddress),
DelegationApprover: gethcommon.HexToAddress(operator.DelegationApproverAddress),
StakerOptOutWindowBlocks: operator.StakerOptOutWindowBlocks,
DeprecatedEarningsReceiver: gethcommon.HexToAddress(operator.EarningsReceiverAddress),
DelegationApprover: gethcommon.HexToAddress(operator.DelegationApproverAddress),
StakerOptOutWindowBlocks: operator.StakerOptOutWindowBlocks,
}

noSendTxOpts, err := w.txMgr.GetNoSendTxOpts()
Expand Down
20 changes: 9 additions & 11 deletions cmd/egnaddrs/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ import (
)

const (
// just copied this file from an eigencert deployment and hardcoded addrs below
// TODO(samlaf): eventually we should make updating this file automated
anvilStateFileName = "eigenlayer-and-registries-deployed-anvil-state.json"
serviceManagerAddr = "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F"
registryCoordinatorAddr = "0x09635F643e140090A9A8Dcd712eD6285858ceBef"
anvilStateFileName = "contracts-deployed-anvil-state.json" // in contracts/anvil/
)

func TestEgnAddrsWithServiceManagerFlag(t *testing.T) {
Expand All @@ -21,14 +17,15 @@ func TestEgnAddrsWithServiceManagerFlag(t *testing.T) {
if err != nil {
t.Fatal(err)
}
anvilEndpoint, err := anvilC.Endpoint(context.Background(), "")
anvilEndpoint, err := anvilC.Endpoint(context.Background(), "http")
if err != nil {
t.Error(err)
}
contractAddrs := testutils.GetContractAddressesFromContractRegistry(anvilEndpoint)

args := []string{"egnaddrs"}
args = append(args, "--service-manager", serviceManagerAddr)
args = append(args, "--rpc-url", "http://"+anvilEndpoint)
args = append(args, "--service-manager", contractAddrs.ServiceManager.Hex())
args = append(args, "--rpc-url", anvilEndpoint)
// we just make sure it doesn't crash
run(args)
}
Expand All @@ -39,14 +36,15 @@ func TestEgnAddrsWithRegistryCoordinatorFlag(t *testing.T) {
if err != nil {
t.Fatal(err)
}
anvilEndpoint, err := anvilC.Endpoint(context.Background(), "")
anvilEndpoint, err := anvilC.Endpoint(context.Background(), "http")
if err != nil {
t.Error(err)
}
contractAddrs := testutils.GetContractAddressesFromContractRegistry(anvilEndpoint)

args := []string{"egnaddrs"}
args = append(args, "--registry-coordinator", registryCoordinatorAddr)
args = append(args, "--rpc-url", "http://"+anvilEndpoint)
args = append(args, "--registry-coordinator", contractAddrs.RegistryCoordinator.Hex())
args = append(args, "--rpc-url", anvilEndpoint)
// we just make sure it doesn't crash
run(args)
}

This file was deleted.

14 changes: 14 additions & 0 deletions contracts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
21 changes: 21 additions & 0 deletions contracts/anvil/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Integration Tests

We store an anvil state files in this directory, so that we can start an anvil chain with the correct state for integration tests.
```
anvil --load-state STATE_FILE.json
```

## Eigenlayer deployment state file
`eigenlayer-deployed-anvil-state.json` contains the eigenlayer deployment.

It was created by running this [deploy script](https://github.com/Layr-Labs/eigenlayer-contracts/blob/2cb9ed107c6c918b9dfbac94cd71b4ab7c94e8c2/script/testing/M2_Deploy_From_Scratch.s.sol). If you ever need to redeploy a new version of eigenlayer contracts, first start an anvil chain that dumps its state after exiting
```
anvil --dump-state eigenlayer-deployed-anvil-state.json
```
Then run the deploy script
```
forge script script/testing/M2_Deploy_From_Scratch.s.sol --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast --sig "run(string memory configFile)" -- M2_deploy_from_scratch.anvil.config.json
```
and finally kill the anvil chain with `Ctrl-C`. Make sure to copy the deployment [output file](https://github.com/Layr-Labs/eigenlayer-contracts/blob/master/script/output/M2_from_scratch_deployment_data.json) to [eigenlayer_deployment_output.json](../../contracts/script/output/31337/eigenlayer_deployment_output.json) so that the tests can find the deployed contracts.

See the main [README](../../README.md#dependencies) to understand why we deploy from the `experimental-reduce-strategy-manager-bytecode-size` branch of eigenlayer-contracts.
1 change: 1 addition & 0 deletions contracts/anvil/contracts-deployed-anvil-state.json

Large diffs are not rendered by default.

73 changes: 73 additions & 0 deletions contracts/anvil/deploy-contracts-save-anvil-state.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/bin/bash

# Enable the script to exit immediately if a command exits with a non-zero status
set -o errexit -o nounset -o pipefail

# Define your cleanup function
clean_up() {
echo "Executing cleanup function..."
set +e
pkill -f anvil

# Check if the exit status is non-zero
exit_status=$?
if [ $exit_status -ne 0 ]; then
echo "Script exited due to set -e on line $1 with command '$2'. Exit status: $exit_status"
fi
}
# Use trap to call the clean_up function when the script exits
trap 'clean_up $LINENO "$BASH_COMMAND"' EXIT

# cd to the directory of this script so that this can be run from anywhere
anvil_dir=$(
cd "$(dirname "${BASH_SOURCE[0]}")"
pwd -P
)
root_dir=$(realpath $anvil_dir/../..)

set -a
source $anvil_dir/utils.sh
# we overwrite some variables here because should always deploy to anvil (localhost)
ETH_HTTP_URL=http://localhost:8545
DEPLOYER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
set +a

# start an empty anvil chain in the background and dump its state to a json file upon exit
start_anvil_docker "" $anvil_dir/contracts-deployed-anvil-state.json
sleep 1

CHAIN_ID=$(cast chain-id)

# DEPLOY CONTRACT REGISTRY
cd $root_dir/contracts
forge create src/ContractsRegistry.sol:ContractsRegistry --rpc-url $ETH_HTTP_URL --private-key $DEPLOYER_PRIVATE_KEY

# DEPLOY EIGENLAYER
EIGEN_CONTRACTS_DIR=$root_dir/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts
DEVNET_OUTPUT_DIR=$EIGEN_CONTRACTS_DIR/script/output/devnet
# deployment overwrites this file, so we save it as backup, because we want that output in our local files, and not in the eigenlayer-contracts submodule files
mv $DEVNET_OUTPUT_DIR/M2_from_scratch_deployment_data.json $DEVNET_OUTPUT_DIR/M2_from_scratch_deployment_data.json.bak
cd $EIGEN_CONTRACTS_DIR
forge script script/deploy/devnet/M2_Deploy_From_Scratch.s.sol --rpc-url $ETH_HTTP_URL \
--private-key $DEPLOYER_PRIVATE_KEY --broadcast \
--sig "run(string memory configFileName)" -- M2_deploy_from_scratch.anvil.config.json
mv $DEVNET_OUTPUT_DIR/M2_from_scratch_deployment_data.json $root_dir/contracts/script/output/${CHAIN_ID:?}/eigenlayer_deployment_output.json
mv $DEVNET_OUTPUT_DIR/M2_from_scratch_deployment_data.json.bak $DEVNET_OUTPUT_DIR/M2_from_scratch_deployment_data.json

# DEPLOY MOCKAVS
cd $root_dir/contracts
forge script script/DeployMockAvs.s.sol --rpc-url $ETH_HTTP_URL --private-key $DEPLOYER_PRIVATE_KEY --broadcast

# DEPLOY TOKENS AND STRATEGIES
cd $root_dir/contracts
# DO NOT REMOVE THE SLOW DIRECTIVE FROM THIS SCRIPT INVOCATION
# slow ensures that the transaction reciept is successful and recieved before sending the next transaction
# this should prevent the strategies deploying/registering in a flakey manner,
forge script script/DeployTokensStrategiesCreateQuorums.s.sol --rpc-url $ETH_HTTP_URL --private-key $DEPLOYER_PRIVATE_KEY --broadcast --slow

# REGISTER OPERATORS WITH EIGENLAYER
cd $root_dir/contracts
# DO NOT REMOVE THE SLOW DIRECTIVE FROM THIS SCRIPT INVOCATION
# slow ensures that the transaction receipt is successful and recieved before sending the next transaction
# this should prevent the operators registering in a flakey manner, the operators registered will change from run to run without this
forge script script/RegisterOperatorsWithEigenlayer.s.sol --rpc-url $ETH_HTTP_URL --private-key $DEPLOYER_PRIVATE_KEY --broadcast --slow
29 changes: 29 additions & 0 deletions contracts/anvil/start-anvil-chain-with-el-and-avs-deployed.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash

set -o errexit -o nounset -o pipefail

# cd to the directory of this script so that this can be run from anywhere
anvil_dir=$(
cd "$(dirname "${BASH_SOURCE[0]}")"
pwd -P
)
root_dir=$(realpath $anvil_dir/../..)

set -a
source $anvil_dir/utils.sh
set +a

# start an anvil instance in the background that has eigenlayer contracts deployed
# we start anvil in the background so that we can run the below script
start_anvil_docker $anvil_dir/contracts-deployed-anvil-state.json ""

cd $root_dir/contracts
# we need to restart the anvil chain at the correct block, otherwise the indexRegistry has a quorumUpdate at the block number
# at which it was deployed (aka quorum was created/updated), but when we start anvil by loading state file it starts at block number 0
# so calling getOperatorListAtBlockNumber reverts because it thinks there are no quorums registered at block 0
# advancing chain manually like this is a current hack until https://github.com/foundry-rs/foundry/issues/6679 is merged
cast rpc anvil_mine 200 --rpc-url http://localhost:8545 > /dev/null
echo "Anvil is ready. Advanced chain to block-number:" $(cast block-number)


docker attach anvil
39 changes: 39 additions & 0 deletions contracts/anvil/utils.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash

set -e -o nounset

parent_path=$(
cd "$(dirname "${BASH_SOURCE[0]}")"
pwd -P
)

FOUNDRY_IMAGE=ghcr.io/foundry-rs/foundry:nightly-5b7e4cb3c882b28f3c32ba580de27ce7381f415a

clean_up() {
# Check if the exit status is non-zero
exit_status=$?
if [ $exit_status -ne 0 ]; then
echo "Script exited due to set -e on line $1 with command '$2'. Exit status: $exit_status"
fi
}
# Use trap to call the clean_up function when the script exits
trap 'clean_up $LINENO "$BASH_COMMAND"' ERR

# start_anvil_docker $LOAD_STATE_FILE $DUMP_STATE_FILE
start_anvil_docker() {
LOAD_STATE_FILE=$1
DUMP_STATE_FILE=$2
LOAD_STATE_VOLUME_DOCKER_ARG=$([[ -z $LOAD_STATE_FILE ]] && echo "" || echo "-v $LOAD_STATE_FILE:/load-state.json")
DUMP_STATE_VOLUME_DOCKER_ARG=$([[ -z $DUMP_STATE_FILE ]] && echo "" || echo "-v $DUMP_STATE_FILE:/dump-state.json")
LOAD_STATE_ANVIL_ARG=$([[ -z $LOAD_STATE_FILE ]] && echo "" || echo "--load-state /load-state.json")
DUMP_STATE_ANVIL_ARG=$([[ -z $DUMP_STATE_FILE ]] && echo "" || echo "--dump-state /dump-state.json")

trap 'docker stop anvil 2>/dev/null || true' EXIT
set -o xtrace
docker run --rm -d --name anvil -p 8545:8545 $LOAD_STATE_VOLUME_DOCKER_ARG $DUMP_STATE_VOLUME_DOCKER_ARG \
--entrypoint anvil \
$FOUNDRY_IMAGE \
$LOAD_STATE_ANVIL_ARG $DUMP_STATE_ANVIL_ARG --host 0.0.0.0
set +o xtrace
sleep 2
}
Loading

0 comments on commit 1623b01

Please sign in to comment.