From 0812372f2dda82c1e1bcc746429c1af2c1b37d49 Mon Sep 17 00:00:00 2001 From: Brandon Chatham Date: Tue, 17 Dec 2024 13:57:38 -0800 Subject: [PATCH 1/3] Adding output file handling for user admin add-pending. --- pkg/user/admin/add_pending.go | 86 +++++++++++++++++++++++++++++++++-- pkg/user/admin/types.go | 12 +++++ 2 files changed, 93 insertions(+), 5 deletions(-) diff --git a/pkg/user/admin/add_pending.go b/pkg/user/admin/add_pending.go index f32d7d07..e4f23ba2 100644 --- a/pkg/user/admin/add_pending.go +++ b/pkg/user/admin/add_pending.go @@ -2,6 +2,7 @@ package admin import ( "context" + "fmt" "sort" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" @@ -11,6 +12,7 @@ import ( "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + "github.com/ethereum/go-ethereum/accounts/abi/bind" gethcommon "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" @@ -23,6 +25,10 @@ type AddPendingAdminWriter interface { ctx context.Context, request elcontracts.AddPendingAdminRequest, ) (*gethtypes.Receipt, error) + NewAddPendingAdminTx( + txOpts *bind.TransactOpts, + request elcontracts.AddPendingAdminRequest, + ) (*gethtypes.Transaction, error) } func AddPendingCmd(generator func(logging.Logger, *addPendingAdminConfig) (AddPendingAdminWriter, error)) *cli.Command { @@ -56,13 +62,77 @@ func addPendingAdmin( return err } + addPendingRequest := elcontracts.AddPendingAdminRequest{ + AccountAddress: config.AccountAddress, + AdminAddress: config.AdminAddress, + WaitForReceipt: true, + } + + if config.Broadcast { + return broadcastAddPendingAdminTx(ctx, elWriter, config, addPendingRequest) + } + return printAddPendingAdminTx(logger, elWriter, config, addPendingRequest) +} + +func printAddPendingAdminTx( + logger logging.Logger, + elWriter AddPendingAdminWriter, + config *addPendingAdminConfig, + request elcontracts.AddPendingAdminRequest, +) error { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return err + } + noSendTxOpts := common.GetNoSendTxOpts(config.CallerAddress) + if common.IsSmartContractAddress(config.CallerAddress, ethClient) { + // address is a smart contract + noSendTxOpts.GasLimit = 150_000 + } + + unsignedTx, err := elWriter.NewAddPendingAdminTx(noSendTxOpts, request) + if err != nil { + return eigenSdkUtils.WrapError("failed to create unsigned tx", err) + } + + if config.OutputType == string(common.OutputType_Calldata) { + calldataHex := gethcommon.Bytes2Hex(unsignedTx.Data()) + if !common.IsEmptyString(config.OutputFile) { + err = common.WriteToFile([]byte(calldataHex), config.OutputFile) + if err != nil { + return err + } + logger.Infof("Call data written to file: %s", config.OutputFile) + } else { + fmt.Println(calldataHex) + } + } else { + if !common.IsEmptyString(config.OutputType) { + fmt.Println("output file not supported for pretty output type") + fmt.Println() + } + fmt.Printf( + "Admin %s will be added as pending for account %s\n", + config.AdminAddress, + config.AccountAddress, + ) + } + txFeeDetails := common.GetTxFeeDetails(unsignedTx) + fmt.Println() + txFeeDetails.Print() + fmt.Println("To broadcast the transaction, use the --broadcast flag") + return nil +} + +func broadcastAddPendingAdminTx( + ctx context.Context, + elWriter AddPendingAdminWriter, + config *addPendingAdminConfig, + request elcontracts.AddPendingAdminRequest, +) error { receipt, err := elWriter.AddPendingAdmin( ctx, - elcontracts.AddPendingAdminRequest{ - AccountAddress: config.AccountAddress, - AdminAddress: config.AdminAddress, - WaitForReceipt: true, - }, + request, ) if err != nil { return err @@ -81,6 +151,9 @@ func readAndValidateAddPendingAdminConfig( ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) + outputType := cliContext.String(flags.OutputTypeFlag.Name) + outputFile := cliContext.String(flags.OutputFileFlag.Name) + broadcast := cliContext.Bool(flags.BroadcastFlag.Name) if environment == "" { environment = common.GetEnvFromNetwork(network) } @@ -126,6 +199,9 @@ func readAndValidateAddPendingAdminConfig( PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), ChainID: chainID, Environment: environment, + OutputFile: outputFile, + OutputType: outputType, + Broadcast: broadcast, }, nil } diff --git a/pkg/user/admin/types.go b/pkg/user/admin/types.go index 343b6299..8a95d56f 100644 --- a/pkg/user/admin/types.go +++ b/pkg/user/admin/types.go @@ -54,6 +54,9 @@ type acceptAdminConfig struct { SignerConfig types.SignerConfig ChainID *big.Int Environment string + OutputFile string + OutputType string + Broadcast bool } type addPendingAdminConfig struct { @@ -66,6 +69,9 @@ type addPendingAdminConfig struct { SignerConfig types.SignerConfig ChainID *big.Int Environment string + OutputFile string + OutputType string + Broadcast bool } type removeAdminConfig struct { @@ -78,6 +84,9 @@ type removeAdminConfig struct { SignerConfig types.SignerConfig ChainID *big.Int Environment string + OutputFile string + OutputType string + Broadcast bool } type removePendingAdminConfig struct { @@ -90,4 +99,7 @@ type removePendingAdminConfig struct { SignerConfig types.SignerConfig ChainID *big.Int Environment string + OutputFile string + OutputType string + Broadcast bool } From 9df7aa6de32b5bd0ea04c159d6c6612f05ca8a14 Mon Sep 17 00:00:00 2001 From: Brandon Chatham Date: Tue, 17 Dec 2024 14:36:22 -0800 Subject: [PATCH 2/3] Adding output support to file or terminal without broadcast for user admin commands. --- pkg/user/admin/accept.go | 88 +++++++++++++++++++++++-- pkg/user/admin/accept_test.go | 26 +++++++- pkg/user/admin/add_pending_test.go | 24 ++++++- pkg/user/admin/remove.go | 92 ++++++++++++++++++++++++--- pkg/user/admin/remove_pending.go | 90 +++++++++++++++++++++++--- pkg/user/admin/remove_pending_test.go | 25 ++++++-- pkg/user/admin/remove_test.go | 24 ++++++- 7 files changed, 333 insertions(+), 36 deletions(-) diff --git a/pkg/user/admin/accept.go b/pkg/user/admin/accept.go index 531842ae..a77909bf 100644 --- a/pkg/user/admin/accept.go +++ b/pkg/user/admin/accept.go @@ -2,6 +2,8 @@ package admin import ( "context" + "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "sort" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" @@ -23,6 +25,10 @@ type AcceptAdminWriter interface { ctx context.Context, request elcontracts.AcceptAdminRequest, ) (*gethtypes.Receipt, error) + NewAcceptAdminTx( + txOpts *bind.TransactOpts, + request elcontracts.AcceptAdminRequest, + ) (*gethtypes.Transaction, error) } func AcceptCmd(generator func(logging.Logger, *acceptAdminConfig) (AcceptAdminWriter, error)) *cli.Command { @@ -56,17 +62,83 @@ func acceptAdmin( return err } - receipt, err := elWriter.AcceptAdmin( - ctx, - elcontracts.AcceptAdminRequest{AccountAddress: config.AccountAddress, WaitForReceipt: true}, - ) + acceptRequest := elcontracts.AcceptAdminRequest{ + AccountAddress: config.AccountAddress, + WaitForReceipt: true, + } + + if config.Broadcast { + return broadcastAcceptAdminTx(ctx, elWriter, config, acceptRequest) + } + + return printAcceptAdminTx(logger, elWriter, config, acceptRequest) +} + +func broadcastAcceptAdminTx( + ctx context.Context, + elWriter AcceptAdminWriter, + config *acceptAdminConfig, + request elcontracts.AcceptAdminRequest, +) error { + receipt, err := elWriter.AcceptAdmin(ctx, request) if err != nil { - return err + return eigenSdkUtils.WrapError("failed to broadcast AcceptAdmin transaction", err) } common.PrintTransactionInfo(receipt.TxHash.String(), config.ChainID) return nil } +func printAcceptAdminTx( + logger logging.Logger, + elWriter AcceptAdminWriter, + config *acceptAdminConfig, + request elcontracts.AcceptAdminRequest, +) error { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return err + } + + noSendTxOpts := common.GetNoSendTxOpts(config.CallerAddress) + if common.IsSmartContractAddress(config.CallerAddress, ethClient) { + noSendTxOpts.GasLimit = 150_000 + } + + // Generate unsigned transaction + unsignedTx, err := elWriter.NewAcceptAdminTx(noSendTxOpts, request) + if err != nil { + return eigenSdkUtils.WrapError("failed to create unsigned tx", err) + } + + if config.OutputType == string(common.OutputType_Calldata) { + calldataHex := gethcommon.Bytes2Hex(unsignedTx.Data()) + if !common.IsEmptyString(config.OutputFile) { + err = common.WriteToFile([]byte(calldataHex), config.OutputFile) + if err != nil { + return err + } + logger.Infof("Call data written to file: %s", config.OutputFile) + } else { + fmt.Println(calldataHex) + } + } else { + if !common.IsEmptyString(config.OutputType) { + fmt.Println("output file not supported for pretty output type") + fmt.Println() + } + fmt.Printf( + "Pending admin at address %s will accept admin role\n", + config.CallerAddress, + ) + } + + txFeeDetails := common.GetTxFeeDetails(unsignedTx) + fmt.Println() + txFeeDetails.Print() + fmt.Println("To broadcast the transaction, use the --broadcast flag") + return nil +} + func readAndValidateAcceptAdminConfig( cliContext *cli.Context, logger logging.Logger, @@ -76,6 +148,9 @@ func readAndValidateAcceptAdminConfig( ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) + outputType := cliContext.String(flags.OutputTypeFlag.Name) + outputFile := cliContext.String(flags.OutputFileFlag.Name) + broadcast := cliContext.Bool(flags.BroadcastFlag.Name) if environment == "" { environment = common.GetEnvFromNetwork(network) } @@ -120,6 +195,9 @@ func readAndValidateAcceptAdminConfig( SignerConfig: *signerConfig, ChainID: chainID, Environment: environment, + OutputFile: outputFile, + OutputType: outputType, + Broadcast: broadcast, }, nil } diff --git a/pkg/user/admin/accept_test.go b/pkg/user/admin/accept_test.go index 45b8e0cd..8d826f10 100644 --- a/pkg/user/admin/accept_test.go +++ b/pkg/user/admin/accept_test.go @@ -3,6 +3,7 @@ package admin import ( "context" "errors" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "testing" "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" @@ -15,7 +16,8 @@ import ( ) type mockAcceptAdminWriter struct { - acceptAdminFunc func(ctx context.Context, request elcontracts.AcceptAdminRequest) (*gethtypes.Receipt, error) + acceptAdminFunc func(ctx context.Context, request elcontracts.AcceptAdminRequest) (*gethtypes.Receipt, error) + newAcceptAdminTxFunc func(txOpts *bind.TransactOpts, request elcontracts.AcceptAdminRequest) (*gethtypes.Transaction, error) } func (m *mockAcceptAdminWriter) AcceptAdmin( @@ -24,9 +26,19 @@ func (m *mockAcceptAdminWriter) AcceptAdmin( ) (*gethtypes.Receipt, error) { return m.acceptAdminFunc(ctx, request) } +func (m *mockAcceptAdminWriter) NewAcceptAdminTx( + txOpts *bind.TransactOpts, + request elcontracts.AcceptAdminRequest, +) (*gethtypes.Transaction, error) { + if m.newAcceptAdminTxFunc == nil { + return nil, errors.New("newAcceptAdminTxFunc not implemented") + } + return m.newAcceptAdminTxFunc(txOpts, request) +} func generateMockAcceptAdminWriter( receipt *gethtypes.Receipt, + tx *gethtypes.Transaction, err error, ) func(logging.Logger, *acceptAdminConfig) (AcceptAdminWriter, error) { return func(logger logging.Logger, config *acceptAdminConfig) (AcceptAdminWriter, error) { @@ -34,6 +46,9 @@ func generateMockAcceptAdminWriter( acceptAdminFunc: func(ctx context.Context, request elcontracts.AcceptAdminRequest) (*gethtypes.Receipt, error) { return receipt, err }, + newAcceptAdminTxFunc: func(txOpts *bind.TransactOpts, request elcontracts.AcceptAdminRequest) (*gethtypes.Transaction, error) { + return tx, err + }, }, nil } } @@ -42,10 +57,11 @@ func TestAcceptCmd_Success(t *testing.T) { mockReceipt := &gethtypes.Receipt{ TxHash: gethcommon.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), } + mockTx := &gethtypes.Transaction{} app := cli.NewApp() app.Commands = []*cli.Command{ - AcceptCmd(generateMockAcceptAdminWriter(mockReceipt, nil)), + AcceptCmd(generateMockAcceptAdminWriter(mockReceipt, mockTx, nil)), } args := []string{ @@ -55,6 +71,7 @@ func TestAcceptCmd_Success(t *testing.T) { "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--network", "holesky", "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "--broadcast", } err := app.Run(args) @@ -86,9 +103,11 @@ func TestAcceptCmd_GeneratorError(t *testing.T) { func TestAcceptCmd_AcceptAdminError(t *testing.T) { expectedError := "error accepting admin" + mockTx := &gethtypes.Transaction{} + app := cli.NewApp() app.Commands = []*cli.Command{ - AcceptCmd(generateMockAcceptAdminWriter(nil, errors.New(expectedError))), + AcceptCmd(generateMockAcceptAdminWriter(nil, mockTx, errors.New(expectedError))), } args := []string{ @@ -98,6 +117,7 @@ func TestAcceptCmd_AcceptAdminError(t *testing.T) { "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--network", "holesky", "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "--broadcast", } err := app.Run(args) diff --git a/pkg/user/admin/add_pending_test.go b/pkg/user/admin/add_pending_test.go index a9e8dce0..38099083 100644 --- a/pkg/user/admin/add_pending_test.go +++ b/pkg/user/admin/add_pending_test.go @@ -7,6 +7,7 @@ import ( "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" + "github.com/ethereum/go-ethereum/accounts/abi/bind" gethcommon "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" @@ -15,7 +16,8 @@ import ( ) type mockAddPendingAdminWriter struct { - addPendingAdminFunc func(ctx context.Context, request elcontracts.AddPendingAdminRequest) (*gethtypes.Receipt, error) + addPendingAdminFunc func(ctx context.Context, request elcontracts.AddPendingAdminRequest) (*gethtypes.Receipt, error) + newAddPendingAdminTxFunc func(txOpts *bind.TransactOpts, request elcontracts.AddPendingAdminRequest) (*gethtypes.Transaction, error) } func (m *mockAddPendingAdminWriter) AddPendingAdmin( @@ -25,8 +27,16 @@ func (m *mockAddPendingAdminWriter) AddPendingAdmin( return m.addPendingAdminFunc(ctx, request) } +func (m *mockAddPendingAdminWriter) NewAddPendingAdminTx( + txOpts *bind.TransactOpts, + request elcontracts.AddPendingAdminRequest, +) (*gethtypes.Transaction, error) { + return m.newAddPendingAdminTxFunc(txOpts, request) +} + func generateMockAddPendingAdminWriter( receipt *gethtypes.Receipt, + tx *gethtypes.Transaction, err error, ) func(logging.Logger, *addPendingAdminConfig) (AddPendingAdminWriter, error) { return func(logger logging.Logger, config *addPendingAdminConfig) (AddPendingAdminWriter, error) { @@ -34,6 +44,9 @@ func generateMockAddPendingAdminWriter( addPendingAdminFunc: func(ctx context.Context, request elcontracts.AddPendingAdminRequest) (*gethtypes.Receipt, error) { return receipt, err }, + newAddPendingAdminTxFunc: func(txOpts *bind.TransactOpts, request elcontracts.AddPendingAdminRequest) (*gethtypes.Transaction, error) { + return tx, err + }, }, nil } } @@ -42,10 +55,11 @@ func TestAddPendingCmd_Success(t *testing.T) { mockReceipt := &gethtypes.Receipt{ TxHash: gethcommon.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), } + mockTx := &gethtypes.Transaction{} app := cli.NewApp() app.Commands = []*cli.Command{ - AddPendingCmd(generateMockAddPendingAdminWriter(mockReceipt, nil)), + AddPendingCmd(generateMockAddPendingAdminWriter(mockReceipt, mockTx, nil)), } args := []string{ @@ -56,6 +70,7 @@ func TestAddPendingCmd_Success(t *testing.T) { "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--network", "holesky", "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "--broadcast", } err := app.Run(args) @@ -88,9 +103,11 @@ func TestAddPendingCmd_GeneratorError(t *testing.T) { func TestAddPendingCmd_AddPendingError(t *testing.T) { expectedError := "error adding pending admin" + mockTx := &gethtypes.Transaction{} + app := cli.NewApp() app.Commands = []*cli.Command{ - AddPendingCmd(generateMockAddPendingAdminWriter(nil, errors.New(expectedError))), + AddPendingCmd(generateMockAddPendingAdminWriter(nil, mockTx, errors.New(expectedError))), } args := []string{ @@ -101,6 +118,7 @@ func TestAddPendingCmd_AddPendingError(t *testing.T) { "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--network", "holesky", "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "--broadcast", } err := app.Run(args) diff --git a/pkg/user/admin/remove.go b/pkg/user/admin/remove.go index 8e16c91d..01468e27 100644 --- a/pkg/user/admin/remove.go +++ b/pkg/user/admin/remove.go @@ -2,6 +2,8 @@ package admin import ( "context" + "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "sort" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" @@ -23,6 +25,10 @@ type RemoveAdminWriter interface { ctx context.Context, request elcontracts.RemoveAdminRequest, ) (*gethtypes.Receipt, error) + NewRemoveAdminTx( + txOpts *bind.TransactOpts, + request elcontracts.RemoveAdminRequest, + ) (*gethtypes.Transaction, error) } func RemoveCmd(generator func(logging.Logger, *removeAdminConfig) (RemoveAdminWriter, error)) *cli.Command { @@ -56,21 +62,83 @@ func removeAdmin( return err } - receipt, err := elWriter.RemoveAdmin( - ctx, - elcontracts.RemoveAdminRequest{ - AccountAddress: config.AccountAddress, - AdminAddress: config.AdminAddress, - WaitForReceipt: true, - }, - ) + removeRequest := elcontracts.RemoveAdminRequest{ + AccountAddress: config.AccountAddress, + AdminAddress: config.AdminAddress, + WaitForReceipt: true, + } + + if config.Broadcast { + return broadcastRemoveAdminTx(ctx, elWriter, config, removeRequest) + } + + return printRemoveAdminTx(logger, elWriter, config, removeRequest) +} + +func broadcastRemoveAdminTx( + ctx context.Context, + elWriter RemoveAdminWriter, + config *removeAdminConfig, + request elcontracts.RemoveAdminRequest, +) error { + receipt, err := elWriter.RemoveAdmin(ctx, request) if err != nil { - return err + return eigenSdkUtils.WrapError("failed to broadcast RemoveAdmin transaction", err) } common.PrintTransactionInfo(receipt.TxHash.String(), config.ChainID) return nil } +func printRemoveAdminTx( + logger logging.Logger, + elWriter RemoveAdminWriter, + config *removeAdminConfig, + request elcontracts.RemoveAdminRequest, +) error { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return err + } + + noSendTxOpts := common.GetNoSendTxOpts(config.CallerAddress) + if common.IsSmartContractAddress(config.CallerAddress, ethClient) { + noSendTxOpts.GasLimit = 150_000 + } + unsignedTx, err := elWriter.NewRemoveAdminTx(noSendTxOpts, request) + if err != nil { + return eigenSdkUtils.WrapError("failed to create unsigned tx", err) + } + + if config.OutputType == string(common.OutputType_Calldata) { + calldataHex := gethcommon.Bytes2Hex(unsignedTx.Data()) + if !common.IsEmptyString(config.OutputFile) { + err = common.WriteToFile([]byte(calldataHex), config.OutputFile) + if err != nil { + return err + } + logger.Infof("Call data written to file: %s", config.OutputFile) + } else { + fmt.Println(calldataHex) + } + } else { + if !common.IsEmptyString(config.OutputType) { + fmt.Println("output file not supported for pretty output type") + fmt.Println() + } + fmt.Printf( + "Admin %s will be removed for account %s\n", + config.AdminAddress, + config.AccountAddress, + ) + } + + txFeeDetails := common.GetTxFeeDetails(unsignedTx) + fmt.Println() + txFeeDetails.Print() + fmt.Println("To broadcast the transaction, use the --broadcast flag") + return nil +} + func readAndValidateRemoveAdminConfig( cliContext *cli.Context, logger logging.Logger, @@ -81,6 +149,9 @@ func readAndValidateRemoveAdminConfig( ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) + outputType := cliContext.String(flags.OutputTypeFlag.Name) + outputFile := cliContext.String(flags.OutputFileFlag.Name) + broadcast := cliContext.Bool(flags.BroadcastFlag.Name) if environment == "" { environment = common.GetEnvFromNetwork(network) } @@ -126,6 +197,9 @@ func readAndValidateRemoveAdminConfig( SignerConfig: *signerConfig, ChainID: chainID, Environment: environment, + OutputFile: outputFile, + OutputType: outputType, + Broadcast: broadcast, }, nil } diff --git a/pkg/user/admin/remove_pending.go b/pkg/user/admin/remove_pending.go index 3b80ee22..adc74b3f 100644 --- a/pkg/user/admin/remove_pending.go +++ b/pkg/user/admin/remove_pending.go @@ -2,6 +2,8 @@ package admin import ( "context" + "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "sort" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" @@ -23,6 +25,10 @@ type RemovePendingAdminWriter interface { ctx context.Context, request elcontracts.RemovePendingAdminRequest, ) (*gethtypes.Receipt, error) + NewRemovePendingAdminTx( + txOpts *bind.TransactOpts, + request elcontracts.RemovePendingAdminRequest, + ) (*gethtypes.Transaction, error) } func RemovePendingCmd( @@ -57,22 +63,82 @@ func removePendingAdmin( if err != nil { return err } + removeRequest := elcontracts.RemovePendingAdminRequest{ + AccountAddress: config.AccountAddress, + AdminAddress: config.AdminAddress, + WaitForReceipt: true, + } - receipt, err := elWriter.RemovePendingAdmin( - ctx, - elcontracts.RemovePendingAdminRequest{ - AccountAddress: config.AccountAddress, - AdminAddress: config.AdminAddress, - WaitForReceipt: true, - }, - ) + if config.Broadcast { + return broadcastRemovePendingAdminTx(ctx, elWriter, config, removeRequest) + } + return printRemovePendingAdminTx(logger, elWriter, config, removeRequest) +} + +func broadcastRemovePendingAdminTx( + ctx context.Context, + elWriter RemovePendingAdminWriter, + config *removePendingAdminConfig, + request elcontracts.RemovePendingAdminRequest, +) error { + receipt, err := elWriter.RemovePendingAdmin(ctx, request) if err != nil { - return err + return eigenSdkUtils.WrapError("failed to broadcast RemovePendingAdmin transaction", err) } common.PrintTransactionInfo(receipt.TxHash.String(), config.ChainID) return nil } +func printRemovePendingAdminTx( + logger logging.Logger, + elWriter RemovePendingAdminWriter, + config *removePendingAdminConfig, + request elcontracts.RemovePendingAdminRequest, +) error { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return err + } + + noSendTxOpts := common.GetNoSendTxOpts(config.CallerAddress) + if common.IsSmartContractAddress(config.CallerAddress, ethClient) { + noSendTxOpts.GasLimit = 150_000 + } + unsignedTx, err := elWriter.NewRemovePendingAdminTx(noSendTxOpts, request) + if err != nil { + return eigenSdkUtils.WrapError("failed to create unsigned transaction", err) + } + + if config.OutputType == string(common.OutputType_Calldata) { + calldataHex := gethcommon.Bytes2Hex(unsignedTx.Data()) + if !common.IsEmptyString(config.OutputFile) { + err = common.WriteToFile([]byte(calldataHex), config.OutputFile) + if err != nil { + return err + } + logger.Infof("Call data written to file: %s", config.OutputFile) + } else { + fmt.Println(calldataHex) + } + } else { + if !common.IsEmptyString(config.OutputType) { + fmt.Println("Output file not supported for pretty output type") + fmt.Println() + } + fmt.Printf( + "Pending admin %s will be removed for account %s\n", + config.AdminAddress, + config.AccountAddress, + ) + } + + txFeeDetails := common.GetTxFeeDetails(unsignedTx) + fmt.Println() + txFeeDetails.Print() + fmt.Println("To broadcast the transaction, use the --broadcast flag") + return nil +} + func readAndValidateRemovePendingAdminConfig( cliContext *cli.Context, logger logging.Logger, @@ -83,6 +149,9 @@ func readAndValidateRemovePendingAdminConfig( ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) + outputType := cliContext.String(flags.OutputTypeFlag.Name) + outputFile := cliContext.String(flags.OutputFileFlag.Name) + broadcast := cliContext.Bool(flags.BroadcastFlag.Name) if environment == "" { environment = common.GetEnvFromNetwork(network) } @@ -128,6 +197,9 @@ func readAndValidateRemovePendingAdminConfig( SignerConfig: *signerConfig, ChainID: chainID, Environment: environment, + OutputFile: outputFile, + OutputType: outputType, + Broadcast: broadcast, }, nil } diff --git a/pkg/user/admin/remove_pending_test.go b/pkg/user/admin/remove_pending_test.go index ee292a7e..12ff0d62 100644 --- a/pkg/user/admin/remove_pending_test.go +++ b/pkg/user/admin/remove_pending_test.go @@ -7,6 +7,7 @@ import ( "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" + "github.com/ethereum/go-ethereum/accounts/abi/bind" gethcommon "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" @@ -15,7 +16,8 @@ import ( ) type mockRemovePendingAdminWriter struct { - removePendingAdminFunc func(ctx context.Context, request elcontracts.RemovePendingAdminRequest) (*gethtypes.Receipt, error) + removePendingAdminFunc func(ctx context.Context, request elcontracts.RemovePendingAdminRequest) (*gethtypes.Receipt, error) + newRemovePendingAdminTxFunc func(txOpts *bind.TransactOpts, request elcontracts.RemovePendingAdminRequest) (*gethtypes.Transaction, error) } func (m *mockRemovePendingAdminWriter) RemovePendingAdmin( @@ -25,8 +27,16 @@ func (m *mockRemovePendingAdminWriter) RemovePendingAdmin( return m.removePendingAdminFunc(ctx, request) } +func (m *mockRemovePendingAdminWriter) NewRemovePendingAdminTx( + txOpts *bind.TransactOpts, + request elcontracts.RemovePendingAdminRequest, +) (*gethtypes.Transaction, error) { + return m.newRemovePendingAdminTxFunc(txOpts, request) +} + func generateMockRemovePendingAdminWriter( receipt *gethtypes.Receipt, + tx *gethtypes.Transaction, err error, ) func(logging.Logger, *removePendingAdminConfig) (RemovePendingAdminWriter, error) { return func(logger logging.Logger, config *removePendingAdminConfig) (RemovePendingAdminWriter, error) { @@ -34,6 +44,9 @@ func generateMockRemovePendingAdminWriter( removePendingAdminFunc: func(ctx context.Context, request elcontracts.RemovePendingAdminRequest) (*gethtypes.Receipt, error) { return receipt, err }, + newRemovePendingAdminTxFunc: func(txOpts *bind.TransactOpts, request elcontracts.RemovePendingAdminRequest) (*gethtypes.Transaction, error) { + return tx, err + }, }, nil } } @@ -42,10 +55,11 @@ func TestRemovePendingCmd_Success(t *testing.T) { mockReceipt := &gethtypes.Receipt{ TxHash: gethcommon.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), } + mockTx := &gethtypes.Transaction{} app := cli.NewApp() app.Commands = []*cli.Command{ - RemovePendingCmd(generateMockRemovePendingAdminWriter(mockReceipt, nil)), + RemovePendingCmd(generateMockRemovePendingAdminWriter(mockReceipt, mockTx, nil)), } args := []string{ @@ -56,12 +70,12 @@ func TestRemovePendingCmd_Success(t *testing.T) { "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--network", "holesky", "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "--broadcast", } err := app.Run(args) assert.NoError(t, err) } - func TestRemovePendingCmd_GeneratorError(t *testing.T) { expectedError := "failed to create admin writer" app := cli.NewApp() @@ -90,9 +104,11 @@ func TestRemovePendingCmd_GeneratorError(t *testing.T) { func TestRemovePendingCmd_RemovePendingError(t *testing.T) { expectedError := "error removing pending admin" + mockTx := &gethtypes.Transaction{} + app := cli.NewApp() app.Commands = []*cli.Command{ - RemovePendingCmd(generateMockRemovePendingAdminWriter(nil, errors.New(expectedError))), + RemovePendingCmd(generateMockRemovePendingAdminWriter(nil, mockTx, errors.New(expectedError))), } args := []string{ @@ -103,6 +119,7 @@ func TestRemovePendingCmd_RemovePendingError(t *testing.T) { "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--network", "holesky", "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "--broadcast", } err := app.Run(args) diff --git a/pkg/user/admin/remove_test.go b/pkg/user/admin/remove_test.go index 0ba4a735..d4650643 100644 --- a/pkg/user/admin/remove_test.go +++ b/pkg/user/admin/remove_test.go @@ -7,6 +7,7 @@ import ( "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" + "github.com/ethereum/go-ethereum/accounts/abi/bind" gethcommon "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" @@ -15,7 +16,8 @@ import ( ) type mockRemoveAdminWriter struct { - removeAdminFunc func(ctx context.Context, request elcontracts.RemoveAdminRequest) (*gethtypes.Receipt, error) + removeAdminFunc func(ctx context.Context, request elcontracts.RemoveAdminRequest) (*gethtypes.Receipt, error) + newRemoveAdminTxFunc func(txOpts *bind.TransactOpts, request elcontracts.RemoveAdminRequest) (*gethtypes.Transaction, error) } func (m *mockRemoveAdminWriter) RemoveAdmin( @@ -25,8 +27,16 @@ func (m *mockRemoveAdminWriter) RemoveAdmin( return m.removeAdminFunc(ctx, request) } +func (m *mockRemoveAdminWriter) NewRemoveAdminTx( + txOpts *bind.TransactOpts, + request elcontracts.RemoveAdminRequest, +) (*gethtypes.Transaction, error) { + return m.newRemoveAdminTxFunc(txOpts, request) +} + func generateMockRemoveAdminWriter( receipt *gethtypes.Receipt, + tx *gethtypes.Transaction, err error, ) func(logging.Logger, *removeAdminConfig) (RemoveAdminWriter, error) { return func(logger logging.Logger, config *removeAdminConfig) (RemoveAdminWriter, error) { @@ -34,6 +44,9 @@ func generateMockRemoveAdminWriter( removeAdminFunc: func(ctx context.Context, request elcontracts.RemoveAdminRequest) (*gethtypes.Receipt, error) { return receipt, err }, + newRemoveAdminTxFunc: func(txOpts *bind.TransactOpts, request elcontracts.RemoveAdminRequest) (*gethtypes.Transaction, error) { + return tx, err + }, }, nil } } @@ -42,10 +55,11 @@ func TestRemoveCmd_Success(t *testing.T) { mockReceipt := &gethtypes.Receipt{ TxHash: gethcommon.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), } + mockTx := &gethtypes.Transaction{} app := cli.NewApp() app.Commands = []*cli.Command{ - RemoveCmd(generateMockRemoveAdminWriter(mockReceipt, nil)), + RemoveCmd(generateMockRemoveAdminWriter(mockReceipt, mockTx, nil)), } args := []string{ @@ -56,6 +70,7 @@ func TestRemoveCmd_Success(t *testing.T) { "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--network", "holesky", "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "--broadcast", } err := app.Run(args) @@ -88,9 +103,11 @@ func TestRemoveCmd_GeneratorError(t *testing.T) { func TestRemoveCmd_RemoveAdminError(t *testing.T) { expectedError := "error removing admin" + mockTx := &gethtypes.Transaction{} + app := cli.NewApp() app.Commands = []*cli.Command{ - RemoveCmd(generateMockRemoveAdminWriter(nil, errors.New(expectedError))), + RemoveCmd(generateMockRemoveAdminWriter(nil, mockTx, errors.New(expectedError))), } args := []string{ @@ -101,6 +118,7 @@ func TestRemoveCmd_RemoveAdminError(t *testing.T) { "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--network", "holesky", "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "--broadcast", } err := app.Run(args) From 3ba4c0c9ecc02d5c813b04651f8c8e7e16d8c9fd Mon Sep 17 00:00:00 2001 From: Brandon Chatham Date: Tue, 17 Dec 2024 15:13:18 -0800 Subject: [PATCH 3/3] Fixing import ordering. --- pkg/user/admin/accept.go | 2 +- pkg/user/admin/accept_test.go | 2 +- pkg/user/admin/remove.go | 2 +- pkg/user/admin/remove_pending.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/user/admin/accept.go b/pkg/user/admin/accept.go index a77909bf..d9551997 100644 --- a/pkg/user/admin/accept.go +++ b/pkg/user/admin/accept.go @@ -3,7 +3,6 @@ package admin import ( "context" "fmt" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "sort" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" @@ -13,6 +12,7 @@ import ( "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + "github.com/ethereum/go-ethereum/accounts/abi/bind" gethcommon "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" diff --git a/pkg/user/admin/accept_test.go b/pkg/user/admin/accept_test.go index 8d826f10..1347d8c8 100644 --- a/pkg/user/admin/accept_test.go +++ b/pkg/user/admin/accept_test.go @@ -3,11 +3,11 @@ package admin import ( "context" "errors" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "testing" "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" + "github.com/ethereum/go-ethereum/accounts/abi/bind" gethcommon "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/assert" diff --git a/pkg/user/admin/remove.go b/pkg/user/admin/remove.go index 01468e27..a27c6d3a 100644 --- a/pkg/user/admin/remove.go +++ b/pkg/user/admin/remove.go @@ -3,7 +3,6 @@ package admin import ( "context" "fmt" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "sort" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" @@ -13,6 +12,7 @@ import ( "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + "github.com/ethereum/go-ethereum/accounts/abi/bind" gethcommon "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" diff --git a/pkg/user/admin/remove_pending.go b/pkg/user/admin/remove_pending.go index adc74b3f..256ba389 100644 --- a/pkg/user/admin/remove_pending.go +++ b/pkg/user/admin/remove_pending.go @@ -3,7 +3,6 @@ package admin import ( "context" "fmt" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "sort" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" @@ -13,6 +12,7 @@ import ( "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + "github.com/ethereum/go-ethereum/accounts/abi/bind" gethcommon "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient"