diff --git a/chainio/clients/avsregistry/reader_test.go b/chainio/clients/avsregistry/reader_test.go new file mode 100644 index 00000000..b311174d --- /dev/null +++ b/chainio/clients/avsregistry/reader_test.go @@ -0,0 +1,180 @@ +package avsregistry_test + +import ( + "context" + "math/big" + "os" + "testing" + + "github.com/Layr-Labs/eigensdk-go/chainio/clients" + "github.com/Layr-Labs/eigensdk-go/logging" + "github.com/Layr-Labs/eigensdk-go/testutils" + "github.com/Layr-Labs/eigensdk-go/types" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" +) + +// Starts an anvil container and builds the ChainIO Clients for testing. +func BuildTestClients(t *testing.T) *clients.Clients { + testConfig := testutils.GetDefaultTestConfig() + anvilC, err := testutils.StartAnvilContainer(testConfig.AnvilStateFileName) + require.NoError(t, err) + + anvilHttpEndpoint, err := anvilC.Endpoint(context.Background(), "http") + require.NoError(t, err) + + anvilWsEndpoint, err := anvilC.Endpoint(context.Background(), "ws") + require.NoError(t, err) + logger := logging.NewTextSLogger(os.Stdout, &logging.SLoggerOptions{Level: testConfig.LogLevel}) + + privateKeyHex := "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + ecdsaPrivateKey, err := crypto.HexToECDSA(privateKeyHex) + require.NoError(t, err) + + contractAddrs := testutils.GetContractAddressesFromContractRegistry(anvilHttpEndpoint) + require.NoError(t, err) + + chainioConfig := clients.BuildAllConfig{ + EthHttpUrl: anvilHttpEndpoint, + EthWsUrl: anvilWsEndpoint, + RegistryCoordinatorAddr: contractAddrs.RegistryCoordinator.String(), + OperatorStateRetrieverAddr: contractAddrs.OperatorStateRetriever.String(), + AvsName: "exampleAvs", + PromMetricsIpPortAddress: ":9090", + } + + clients, err := clients.BuildAll( + chainioConfig, + ecdsaPrivateKey, + logger, + ) + require.NoError(t, err) + return clients +} + +func TestReaderMethods(t *testing.T) { + clients := BuildTestClients(t) + chainReader := clients.ReadClients.AvsRegistryChainReader + + quorumNumbers := types.QuorumNums{0} + + t.Run("get quorum state", func(t *testing.T) { + count, err := chainReader.GetQuorumCount(&bind.CallOpts{}) + require.NoError(t, err) + require.NotNil(t, count) + }) + + t.Run("get operator stake in quorums at current block", func(t *testing.T) { + stake, err := chainReader.GetOperatorsStakeInQuorumsAtCurrentBlock(&bind.CallOpts{}, quorumNumbers) + require.NoError(t, err) + require.NotNil(t, stake) + }) + + t.Run("get operator stake in quorums at block", func(t *testing.T) { + stake, err := chainReader.GetOperatorsStakeInQuorumsAtBlock(&bind.CallOpts{}, quorumNumbers, 100) + require.NoError(t, err) + require.NotNil(t, stake) + }) + + t.Run("get operator address in quorums at current block", func(t *testing.T) { + addresses, err := chainReader.GetOperatorAddrsInQuorumsAtCurrentBlock(&bind.CallOpts{}, quorumNumbers) + require.NoError(t, err) + require.NotNil(t, addresses) + }) + + t.Run( + "get operators stake in quorums of operator at block returns error for non-registered operator", + func(t *testing.T) { + operatorAddress := common.Address{0x1} + operatorId, err := chainReader.GetOperatorId(&bind.CallOpts{}, operatorAddress) + require.NoError(t, err) + + _, _, err = chainReader.GetOperatorsStakeInQuorumsOfOperatorAtBlock(&bind.CallOpts{}, operatorId, 100) + require.Error(t, err) + require.Contains(t, err.Error(), "Failed to get operators state") + }) + + t.Run( + "get single operator stake in quorums of operator at current block returns error for non-registered operator", + func(t *testing.T) { + operatorAddress := common.Address{0x1} + operatorId, err := chainReader.GetOperatorId(&bind.CallOpts{}, operatorAddress) + require.NoError(t, err) + + stakes, err := chainReader.GetOperatorStakeInQuorumsOfOperatorAtCurrentBlock(&bind.CallOpts{}, operatorId) + require.NoError(t, err) + require.Equal(t, 0, len(stakes)) + }) + + t.Run("get check signatures indices returns error for non-registered operator", func(t *testing.T) { + operatorAddress := common.Address{0x1} + operatorId, err := chainReader.GetOperatorId(&bind.CallOpts{}, operatorAddress) + require.NoError(t, err) + + _, err = chainReader.GetCheckSignaturesIndices( + &bind.CallOpts{}, + 100, + quorumNumbers, + []types.OperatorId{operatorId}, + ) + require.Contains(t, err.Error(), "Failed to get check signatures indices") + }) + + t.Run("get operator id", func(t *testing.T) { + operatorAddress := common.Address{0x1} + operatorId, err := chainReader.GetOperatorId(&bind.CallOpts{}, operatorAddress) + require.NoError(t, err) + require.NotNil(t, operatorId) + }) + + t.Run("get operator from id returns zero address for non-registered operator", func(t *testing.T) { + operatorAddress := common.HexToAddress("0x1234567890123456789012345678901234567890") + operatorId, err := chainReader.GetOperatorId(&bind.CallOpts{}, operatorAddress) + require.NoError(t, err) + + retrievedAddress, err := chainReader.GetOperatorFromId(&bind.CallOpts{}, operatorId) + require.NoError(t, err) + require.Equal(t, retrievedAddress, common.Address{0x0}) + }) + + t.Run("query registration detail", func(t *testing.T) { + operatorAddress := common.HexToAddress("0x1234567890123456789012345678901234567890") + quorums, err := chainReader.QueryRegistrationDetail(&bind.CallOpts{}, operatorAddress) + require.NoError(t, err) + require.Equal(t, 1, len(quorums)) + }) + + t.Run("is operator registered", func(t *testing.T) { + operatorAddress := common.HexToAddress("0x1234567890123456789012345678901234567890") + isRegistered, err := chainReader.IsOperatorRegistered(&bind.CallOpts{}, operatorAddress) + require.NoError(t, err) + require.False(t, isRegistered) + }) + + t.Run( + "query existing registered operator pub keys", func(t *testing.T) { + addresses, pubKeys, err := chainReader.QueryExistingRegisteredOperatorPubKeys( + context.Background(), + big.NewInt(0), + nil, + nil, + ) + require.NoError(t, err) + require.Equal(t, 0, len(pubKeys)) + require.Equal(t, 0, len(addresses)) + }) + + t.Run( + "query existing registered operator sockets", func(t *testing.T) { + address_to_sockets, err := chainReader.QueryExistingRegisteredOperatorSockets( + context.Background(), + big.NewInt(0), + nil, + nil, + ) + require.NoError(t, err) + require.Equal(t, 0, len(address_to_sockets)) + }) +} diff --git a/chainio/clients/avsregistry/writer_test.go b/chainio/clients/avsregistry/writer_test.go new file mode 100644 index 00000000..1a21ef47 --- /dev/null +++ b/chainio/clients/avsregistry/writer_test.go @@ -0,0 +1,73 @@ +package avsregistry_test + +import ( + "context" + "testing" + + chainioutils "github.com/Layr-Labs/eigensdk-go/chainio/utils" + "github.com/Layr-Labs/eigensdk-go/crypto/bls" + "github.com/Layr-Labs/eigensdk-go/types" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" +) + +func TestWriterMethods(t *testing.T) { + clients := BuildTestClients(t) + chainWriter := clients.AvsRegistryChainWriter + + keypair, err := bls.NewKeyPairFromString("0x01") + require.NoError(t, err) + + addr := gethcommon.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") + ecdsaPrivKeyHex := "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + ecdsaPrivateKey, err := crypto.HexToECDSA(ecdsaPrivKeyHex) + require.NoError(t, err) + + quorumNumbers := types.QuorumNums{0} + + t.Run("register operator", func(t *testing.T) { + receipt, err := chainWriter.RegisterOperator( + context.Background(), + ecdsaPrivateKey, + keypair, + quorumNumbers, + "", + true, + ) + require.NoError(t, err) + require.NotNil(t, receipt) + }) + + t.Run("update stake of operator subset", func(t *testing.T) { + receipt, err := chainWriter.UpdateStakesOfOperatorSubsetForAllQuorums( + context.Background(), + []gethcommon.Address{addr}, + true, + ) + require.NoError(t, err) + require.NotNil(t, receipt) + }) + + t.Run("update stake of entire operator set", func(t *testing.T) { + receipt, err := chainWriter.UpdateStakesOfEntireOperatorSetForQuorums( + context.Background(), + [][]gethcommon.Address{{addr}}, + quorumNumbers, + true, + ) + require.NoError(t, err) + require.NotNil(t, receipt) + }) + + t.Run("deregister operator", func(t *testing.T) { + receipt, err := chainWriter.DeregisterOperator( + context.Background(), + quorumNumbers, + chainioutils.ConvertToBN254G1Point(keypair.PubKey), + true, + ) + require.NoError(t, err) + require.NotNil(t, receipt) + }) +} diff --git a/testutils/anvil.go b/testutils/anvil.go index 3cec5400..ae5f0935 100644 --- a/testutils/anvil.go +++ b/testutils/anvil.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "log/slog" "os/exec" "path/filepath" "runtime" @@ -157,3 +158,15 @@ func AdvanceChainByNBlocksExecInContainer(ctx context.Context, n int, anvilC tes log.Fatalf("Unable to advance anvil chain by n blocks. Expected return code 0, got %v", c) } } + +type TestConfig struct { + AnvilStateFileName string + LogLevel slog.Level +} + +func GetDefaultTestConfig() TestConfig { + return TestConfig{ + AnvilStateFileName: "contracts-deployed-anvil-state.json", + LogLevel: slog.LevelDebug, + } +}