From 5ba8748f0d8857dc7364bbba7ed2964fcb088984 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Mon, 26 Aug 2024 15:44:50 -0700 Subject: [PATCH 01/34] feat: slashing commands feat: add working cmds remove unrelated changes remove unrelated changes unsigned tx add some tests comment add readme more allocations basic show command update with latest contracts add more code add more code working slashing %age and comments update with latest contracts Update show.go (#226) updated to latest AM updated to latest AM updated to latest AM update eigensdk update with latest contracts update with latest contracts update allocation delay cmd update with latest contracts added registered/deregistered operator sets only show deregister if there's any allocation left UAM Command Schema with CanCall Implementation (#244) Co-authored-by: Brandon Chatham fix: tests and fmt (#249) Implementing `list` and `list_permissions` Commands. (#248) Co-authored-by: Brandon Chatham --- .gitignore | 1 + cmd/eigenlayer/main.go | 1 + go.mod | 6 +- go.sum | 6 +- pkg/internal/common/contracts.go | 12 + pkg/internal/common/eth.go | 46 ++ pkg/internal/common/flags/avs.go | 47 ++ pkg/internal/common/flags/general.go | 21 + pkg/internal/common/helper.go | 68 ++- pkg/operator.go | 3 + pkg/operator/allocations.go | 21 + pkg/operator/allocations/README.md | 75 +++ pkg/operator/allocations/flags.go | 19 + .../allocations/set_allocation_delay.go | 208 ++++++++ pkg/operator/allocations/show.go | 318 +++++++++++ .../allocations/testdata/allocations1.csv | 5 + .../testdata/allocations_duplicate.csv | 6 + pkg/operator/allocations/types.go | 294 +++++++++++ pkg/operator/allocations/update.go | 492 ++++++++++++++++++ pkg/operator/allocations/update_test.go | 275 ++++++++++ pkg/operator/config/create.go | 34 ++ pkg/operator/deregister_operator_sets.go | 237 +++++++++ pkg/operator/register.go | 1 + pkg/operator/register_operator_sets.go | 223 ++++++++ pkg/operator/types.go | 40 ++ pkg/operator/update_metadata_uri.go | 7 +- pkg/rewards/claim.go | 26 +- pkg/rewards/claim_test.go | 16 +- pkg/types/chain_metadata.go | 1 + pkg/user/admin/accept.go | 33 ++ pkg/user/admin/add_pending.go | 34 ++ pkg/user/admin/admin.go | 34 ++ pkg/user/admin/flags.go | 36 ++ pkg/user/admin/is_admin.go | 26 + pkg/user/admin/is_pending.go | 26 + pkg/user/admin/list.go | 25 + pkg/user/admin/list_pending.go | 25 + pkg/user/admin/remove.go | 34 ++ pkg/user/admin/remove_pending.go | 34 ++ pkg/user/appointee/appointee.go | 32 ++ pkg/user/appointee/batch_set.go | 33 ++ pkg/user/appointee/can_call.go | 147 ++++++ pkg/user/appointee/can_call_test.go | 137 +++++ pkg/user/appointee/flags.go | 48 ++ pkg/user/appointee/list.go | 161 ++++++ pkg/user/appointee/list_permissions.go | 160 ++++++ pkg/user/appointee/list_permissions_test.go | 116 +++++ pkg/user/appointee/list_test.go | 134 +++++ pkg/user/appointee/remove.go | 36 ++ pkg/user/appointee/set.go | 36 ++ pkg/user/appointee/types.go | 41 ++ pkg/users.go | 20 + 52 files changed, 3886 insertions(+), 31 deletions(-) create mode 100644 pkg/internal/common/flags/avs.go create mode 100644 pkg/operator/allocations.go create mode 100644 pkg/operator/allocations/README.md create mode 100644 pkg/operator/allocations/flags.go create mode 100644 pkg/operator/allocations/set_allocation_delay.go create mode 100644 pkg/operator/allocations/show.go create mode 100644 pkg/operator/allocations/testdata/allocations1.csv create mode 100644 pkg/operator/allocations/testdata/allocations_duplicate.csv create mode 100644 pkg/operator/allocations/types.go create mode 100644 pkg/operator/allocations/update.go create mode 100644 pkg/operator/allocations/update_test.go create mode 100644 pkg/operator/deregister_operator_sets.go create mode 100644 pkg/operator/register_operator_sets.go create mode 100644 pkg/operator/types.go create mode 100644 pkg/user/admin/accept.go create mode 100644 pkg/user/admin/add_pending.go create mode 100644 pkg/user/admin/admin.go create mode 100644 pkg/user/admin/flags.go create mode 100644 pkg/user/admin/is_admin.go create mode 100644 pkg/user/admin/is_pending.go create mode 100644 pkg/user/admin/list.go create mode 100644 pkg/user/admin/list_pending.go create mode 100644 pkg/user/admin/remove.go create mode 100644 pkg/user/admin/remove_pending.go create mode 100644 pkg/user/appointee/appointee.go create mode 100644 pkg/user/appointee/batch_set.go create mode 100644 pkg/user/appointee/can_call.go create mode 100644 pkg/user/appointee/can_call_test.go create mode 100644 pkg/user/appointee/flags.go create mode 100644 pkg/user/appointee/list.go create mode 100644 pkg/user/appointee/list_permissions.go create mode 100644 pkg/user/appointee/list_permissions_test.go create mode 100644 pkg/user/appointee/list_test.go create mode 100644 pkg/user/appointee/remove.go create mode 100644 pkg/user/appointee/set.go create mode 100644 pkg/user/appointee/types.go create mode 100644 pkg/users.go diff --git a/.gitignore b/.gitignore index ef990a37..9f1cd9e5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ operator-config.yaml.old operator.yaml operator.yaml.old config/* +updates.csv # build dist/ \ No newline at end of file diff --git a/cmd/eigenlayer/main.go b/cmd/eigenlayer/main.go index 57367989..a7f23519 100644 --- a/cmd/eigenlayer/main.go +++ b/cmd/eigenlayer/main.go @@ -43,6 +43,7 @@ func main() { app.Commands = append(app.Commands, pkg.RewardsCmd(prompter)) app.Commands = append(app.Commands, pkg.KeysCmd(prompter)) app.Commands = append(app.Commands, pkg.EigenPodCmd(prompter)) + app.Commands = append(app.Commands, pkg.UserCmd()) if err := app.Run(os.Args); err != nil { _, err := fmt.Fprintln(os.Stderr, err) diff --git a/go.mod b/go.mod index d96aada1..bb732316 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,11 @@ require ( github.com/Layr-Labs/eigenlayer-contracts v0.3.2-mainnet-rewards github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e - github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212190947-9985122d81fe + //<<<<<<< HEAD + // github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212190947-9985122d81fe + //======= + github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210000422-beb1a3c4502f + //>>>>>>> 070a30e (feat: slashing commands) github.com/blang/semver/v4 v4.0.0 github.com/consensys/gnark-crypto v0.12.1 github.com/ethereum/go-ethereum v1.14.5 diff --git a/go.sum b/go.sum index 3ec027e3..7def4b86 100644 --- a/go.sum +++ b/go.sum @@ -12,10 +12,8 @@ github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 h1:G5Q1SnLmFbEjhOkky3vIHk github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12/go.mod h1:OlJd1QjqEW53wfWG/lJyPCGvrXwWVEjPQsP4TV+gttQ= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e h1:DvW0/kWHV9mZsbH2KOjEHKTSIONNPUj6X05FJvUohy4= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e/go.mod h1:T7tYN8bTdca2pkMnz9G2+ZwXYWw5gWqQUIu4KLgC/vM= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241211225219-79336bf6e886 h1:+7AijqdfRXdDc3zvj02Alqsk6Qd3owvlqPYQN1Hc1ME= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241211225219-79336bf6e886/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212190947-9985122d81fe h1:FeXxapvtEbbTbEWsrcBTTzQ2u2quGJ9HNYQVSk5JZ8g= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212190947-9985122d81fe/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210000422-beb1a3c4502f h1:D94Vf+dALr9W0Ie18lZ8QDPvOAFX8FBbIpyVAtCUL1A= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210000422-beb1a3c4502f/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= diff --git a/pkg/internal/common/contracts.go b/pkg/internal/common/contracts.go index b6adf130..ff7fe70a 100644 --- a/pkg/internal/common/contracts.go +++ b/pkg/internal/common/contracts.go @@ -1,6 +1,7 @@ package common import ( + "context" "errors" "math/big" @@ -57,3 +58,14 @@ func GetELWriter( return eLWriter, nil } + +func IsSmartContractAddress(address gethcommon.Address, ethClient *ethclient.Client) bool { + code, err := ethClient.CodeAt(context.Background(), address, nil) + if err != nil { + // We return true here because we want to treat the address as a smart contract + // This is only used to gas estimation and creating unsigned transactions + // So it's fine if eth client return an error + return true + } + return len(code) > 0 +} diff --git a/pkg/internal/common/eth.go b/pkg/internal/common/eth.go index 59123dbc..5cd9162e 100644 --- a/pkg/internal/common/eth.go +++ b/pkg/internal/common/eth.go @@ -3,8 +3,10 @@ package common import ( "fmt" "math/big" + "strconv" "strings" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -46,3 +48,47 @@ func GetTxFeeDetails(tx *types.Transaction) *TxFeeDetails { GasFeeCapGwei: gasFeeCapGwei, } } + +func ConvertStringSliceToGethAddressSlice(addresses []string) []common.Address { + gethAddresses := make([]common.Address, 0, len(addresses)) + for _, address := range addresses { + parsed := common.HexToAddress(address) + gethAddresses = append(gethAddresses, parsed) + } + return gethAddresses +} + +func ShortEthAddress(address common.Address) string { + return fmt.Sprintf("%s...%s", address.Hex()[:6], address.Hex()[len(address.Hex())-4:]) +} + +func Uint64ToString(num uint64) string { + return strconv.FormatUint(num, 10) +} + +func FormatNumberWithUnderscores(numStr string) string { + + // If the number is less than 1000, no formatting is needed + if len(numStr) <= 3 { + return numStr + } + + // Calculate the number of groups of 3 digits + groups := (len(numStr) - 1) / 3 + + // Create a slice to hold the result + result := make([]byte, len(numStr)+groups) + + // Fill the result slice from right to left + resultIndex := len(result) - 1 + for i := len(numStr) - 1; i >= 0; i-- { + if (len(numStr)-i-1)%3 == 0 && i != len(numStr)-1 { + result[resultIndex] = '_' + resultIndex-- + } + result[resultIndex] = numStr[i] + resultIndex-- + } + + return string(result) +} diff --git a/pkg/internal/common/flags/avs.go b/pkg/internal/common/flags/avs.go new file mode 100644 index 00000000..702a6d3c --- /dev/null +++ b/pkg/internal/common/flags/avs.go @@ -0,0 +1,47 @@ +package flags + +import "github.com/urfave/cli/v2" + +var ( + AVSAddressesFlag = cli.StringSliceFlag{ + Name: "avs-addresses", + Usage: "AVS addresses", + Aliases: []string{"aa"}, + EnvVars: []string{"AVS_ADDRESSES"}, + } + + AVSAddressFlag = cli.StringFlag{ + Name: "avs-address", + Usage: "AVS addresses", + Aliases: []string{"aa"}, + EnvVars: []string{"AVS_ADDRESS"}, + } + + StrategyAddressesFlag = cli.StringSliceFlag{ + Name: "strategy-addresses", + Usage: "Strategy addresses", + Aliases: []string{"sa"}, + EnvVars: []string{"STRATEGY_ADDRESSES"}, + } + + StrategyAddressFlag = cli.StringFlag{ + Name: "strategy-address", + Usage: "Strategy addresses", + Aliases: []string{"sa"}, + EnvVars: []string{"STRATEGY_ADDRESS"}, + } + + OperatorSetIdFlag = cli.Uint64Flag{ + Name: "operator-set-id", + Usage: "Operator set ID", + Aliases: []string{"osid"}, + EnvVars: []string{"OPERATOR_SET_ID"}, + } + + OperatorSetIdsFlag = cli.Uint64SliceFlag{ + Name: "operator-set-ids", + Usage: "Operator set IDs. Comma separated list of operator set IDs", + Aliases: []string{"osids"}, + EnvVars: []string{"OPERATOR_SET_IDS"}, + } +) diff --git a/pkg/internal/common/flags/general.go b/pkg/internal/common/flags/general.go index 4077c1a5..517bffc8 100644 --- a/pkg/internal/common/flags/general.go +++ b/pkg/internal/common/flags/general.go @@ -104,4 +104,25 @@ var ( Usage: "Input file for batch rewards claim", EnvVars: []string{"BATCH_CLAIM_FILE"}, } + + CSVFileFlag = cli.StringFlag{ + Name: "csv-file", + Aliases: []string{"csv"}, + Usage: "CSV file to read data from", + EnvVars: []string{"CSV_FILE"}, + } + + EnvironmentFlag = cli.StringFlag{ + Name: "environment", + Aliases: []string{"env"}, + Usage: "environment to use. Currently supports 'preprod' ,'testnet' and 'prod'. If not provided, it will be inferred based on network", + EnvVars: []string{"ENVIRONMENT"}, + } + + DelegationManagerAddressFlag = cli.StringFlag{ + Name: "delegation-manager-address", + Aliases: []string{"dma"}, + Usage: "Optional delegation manager address. This can be used if you are testing against your own deployment of eigenlayer contracts", + EnvVars: []string{"DELEGATION_MANAGER_ADDRESS"}, + } ) diff --git a/pkg/internal/common/helper.go b/pkg/internal/common/helper.go index 6cfe066a..bbd282ad 100644 --- a/pkg/internal/common/helper.go +++ b/pkg/internal/common/helper.go @@ -3,6 +3,7 @@ package common import ( "context" "crypto/ecdsa" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -14,8 +15,6 @@ import ( "strings" "time" - "github.com/urfave/cli/v2" - "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/types" "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" @@ -36,6 +35,15 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/fatih/color" + "github.com/urfave/cli/v2" +) + +const ( + mainnet = "mainnet" + testnet = "testnet" + local = "local" + selectorHexIdLength = 10 + addressPrefix = "0x" ) var ChainMetadataMap = map[int64]types.ChainMetadata{ @@ -44,6 +52,7 @@ var ChainMetadataMap = map[int64]types.ChainMetadata{ ELDelegationManagerAddress: "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", ELAVSDirectoryAddress: "0x135dda560e946695d6f155dacafc6f1f25c1f5af", ELRewardsCoordinatorAddress: "0x7750d328b314EfFa365A0402CcfD489B80B0adda", + ELPermissionManagerAddress: "", WebAppUrl: "https://app.eigenlayer.xyz/operator", ProofStoreBaseURL: "https://eigenlabs-rewards-mainnet-ethereum.s3.amazonaws.com", }, @@ -52,6 +61,7 @@ var ChainMetadataMap = map[int64]types.ChainMetadata{ ELDelegationManagerAddress: "0xA44151489861Fe9e3055d95adC98FbD462B948e7", ELAVSDirectoryAddress: "0x055733000064333CaDDbC92763c58BF0192fFeBf", ELRewardsCoordinatorAddress: "0xAcc1fb458a1317E886dB376Fc8141540537E68fE", + ELPermissionManagerAddress: "", WebAppUrl: "https://holesky.eigenlayer.xyz/operator", ProofStoreBaseURL: "https://eigenlabs-rewards-testnet-holesky.s3.amazonaws.com", }, @@ -60,6 +70,7 @@ var ChainMetadataMap = map[int64]types.ChainMetadata{ ELDelegationManagerAddress: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", ELAVSDirectoryAddress: "0x0165878A594ca255338adfa4d48449f69242Eb8F", ELRewardsCoordinatorAddress: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", + ELPermissionManagerAddress: "", WebAppUrl: "", ProofStoreBaseURL: "", }, @@ -321,6 +332,26 @@ func GetAVSDirectoryAddress(chainID *big.Int) (string, error) { } } +func GetDelegationManagerAddress(chainID *big.Int) (string, error) { + chainIDInt := chainID.Int64() + chainMetadata, ok := ChainMetadataMap[chainIDInt] + if !ok { + return "", fmt.Errorf("chain ID %d not supported", chainIDInt) + } else { + return chainMetadata.ELDelegationManagerAddress, nil + } +} + +func GetPermissionManagerAddress(chainID *big.Int) (string, error) { + chainIDInt := chainID.Int64() + chainMetadata, ok := ChainMetadataMap[chainIDInt] + if !ok { + return "", fmt.Errorf("chain ID %d not supported", chainIDInt) + } else { + return chainMetadata.ELDelegationManagerAddress, nil + } +} + func GetTransactionLink(txHash string, chainId *big.Int) string { chainIDInt := chainId.Int64() chainMetadata, ok := ChainMetadataMap[chainIDInt] @@ -480,7 +511,7 @@ func GetNoSendTxOpts(from common.Address) *bind.TransactOpts { } func Trim0x(s string) string { - return strings.TrimPrefix(s, "0x") + return strings.TrimPrefix(s, addressPrefix) } func Sign(digest []byte, cfg types.SignerConfig, p utils.Prompter) ([]byte, error) { @@ -533,3 +564,34 @@ func Sign(digest []byte, cfg types.SignerConfig, p utils.Prompter) ([]byte, erro return signed, nil } + +func ValidateAndConvertSelectorString(selector string) ([4]byte, error) { + if len(selector) != selectorHexIdLength || selector[:2] != addressPrefix { + return [4]byte{}, errors.New("selector must be a 4-byte hex string prefixed with '0x'") + } + + decoded, err := hex.DecodeString(selector[2:]) + if err != nil { + return [4]byte{}, eigenSdkUtils.WrapError("invalid hex encoding: %v", err) + } + + if len(decoded) != 4 { + return [4]byte{}, fmt.Errorf("decoded selector must be 4 bytes, got %d bytes", len(decoded)) + } + + var selectorBytes [4]byte + copy(selectorBytes[:], decoded) + + return selectorBytes, nil +} + +func GetEnvFromNetwork(network string) string { + switch network { + case utils.HoleskyNetworkName: + return testnet + case utils.MainnetNetworkName: + return mainnet + default: + return local + } +} diff --git a/pkg/operator.go b/pkg/operator.go index d34d1742..5a9c6bf3 100644 --- a/pkg/operator.go +++ b/pkg/operator.go @@ -22,6 +22,9 @@ func OperatorCmd(p utils.Prompter) *cli.Command { operator.GetOperatorSplitCmd(p), operator.GetOperatorPISplitCmd(p), operator.SetOperatorPISplitCmd(p), + operator.AllocationsCmd(p), + operator.DeregisterCommand(p), + operator.RegisterOperatorSetsCommand(p), }, } diff --git a/pkg/operator/allocations.go b/pkg/operator/allocations.go new file mode 100644 index 00000000..67d5a304 --- /dev/null +++ b/pkg/operator/allocations.go @@ -0,0 +1,21 @@ +package operator + +import ( + "github.com/Layr-Labs/eigenlayer-cli/pkg/operator/allocations" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/urfave/cli/v2" +) + +func AllocationsCmd(p utils.Prompter) *cli.Command { + var allocationsCmd = &cli.Command{ + Name: "allocations", + Usage: "Stake allocation commands for operators", + Subcommands: []*cli.Command{ + allocations.ShowCmd(p), + allocations.UpdateCmd(p), + allocations.SetDelayCmd(p), + }, + } + + return allocationsCmd +} diff --git a/pkg/operator/allocations/README.md b/pkg/operator/allocations/README.md new file mode 100644 index 00000000..3f8cf16c --- /dev/null +++ b/pkg/operator/allocations/README.md @@ -0,0 +1,75 @@ +## Allocations Command +### Initialize Delay +```bash +eigenlayer operator allocations initialize-delay --help +NAME: + eigenlayer operator allocations initialize-delay - Initialize the allocation delay for operator + +USAGE: + initialize-delay [flags] + +DESCRIPTION: + Initializes the allocation delay for operator. This is a one time command. You can not change the allocation delay once + +OPTIONS: + --broadcast, -b Use this flag to broadcast the transaction (default: false) [$BROADCAST] + --ecdsa-private-key value, -e value ECDSA private key hex to send transaction [$ECDSA_PRIVATE_KEY] + --environment value, --env value environment to use. Currently supports 'preprod' ,'testnet' and 'prod'. If not provided, it will be inferred based on network [$ENVIRONMENT] + --eth-rpc-url value, -r value URL of the Ethereum RPC [$ETH_RPC_URL] + --fireblocks-api-key value, --ff value Fireblocks API key [$FIREBLOCKS_API_KEY] + --fireblocks-aws-region value, --fa value AWS region if secret is stored in AWS KMS (default: "us-east-1") [$FIREBLOCKS_AWS_REGION] + --fireblocks-base-url value, --fb value Fireblocks base URL [$FIREBLOCKS_BASE_URL] + --fireblocks-secret-key value, --fs value Fireblocks secret key. If you are using AWS Secret Manager, this should be the secret name. [$FIREBLOCKS_SECRET_KEY] + --fireblocks-secret-storage-type value, --fst value Fireblocks secret storage type. Supported values are 'plaintext' and 'aws_secret_manager' [$FIREBLOCKS_SECRET_STORAGE_TYPE] + --fireblocks-timeout value, --ft value Fireblocks timeout (default: 30) [$FIREBLOCKS_TIMEOUT] + --fireblocks-vault-account-name value, --fv value Fireblocks vault account name [$FIREBLOCKS_VAULT_ACCOUNT_NAME] + --network value, -n value Network to use. Currently supports 'holesky' and 'mainnet' (default: "holesky") [$NETWORK] + --operator-address value, --oa value, --operator value Operator address [$OPERATOR_ADDRESS] + --output-file value, -o value Output file to write the data [$OUTPUT_FILE] + --output-type value, --ot value Output format of the command. One of 'pretty', 'json' or 'calldata' (default: "pretty") [$OUTPUT_TYPE] + --path-to-key-store value, -k value Path to the key store used to send transactions [$PATH_TO_KEY_STORE] + --verbose, -v Enable verbose logging (default: false) [$VERBOSE] + --web3signer-url value, -w value URL of the Web3Signer [$WEB3SIGNER_URL] + --help, -h show help +``` + +### Update allocations +```bash +eigenlayer operator allocations update --help +NAME: + eigenlayer operator allocations update - Update allocations + +USAGE: + update + +DESCRIPTION: + + Command to update allocations + + +OPTIONS: + --avs-address value, --aa value AVS addresses [$AVS_ADDRESS] + --bips-to-allocate value, --bta value, --bips value, --bps value Bips to allocate to the strategy (default: 0) [$BIPS_TO_ALLOCATE] + --broadcast, -b Use this flag to broadcast the transaction (default: false) [$BROADCAST] + --csv-file value, --csv value CSV file to read data from [$CSV_FILE] + --ecdsa-private-key value, -e value ECDSA private key hex to send transaction [$ECDSA_PRIVATE_KEY] + --environment value, --env value environment to use. Currently supports 'preprod' ,'testnet' and 'prod'. If not provided, it will be inferred based on network [$ENVIRONMENT] + --eth-rpc-url value, -r value URL of the Ethereum RPC [$ETH_RPC_URL] + --fireblocks-api-key value, --ff value Fireblocks API key [$FIREBLOCKS_API_KEY] + --fireblocks-aws-region value, --fa value AWS region if secret is stored in AWS KMS (default: "us-east-1") [$FIREBLOCKS_AWS_REGION] + --fireblocks-base-url value, --fb value Fireblocks base URL [$FIREBLOCKS_BASE_URL] + --fireblocks-secret-key value, --fs value Fireblocks secret key. If you are using AWS Secret Manager, this should be the secret name. [$FIREBLOCKS_SECRET_KEY] + --fireblocks-secret-storage-type value, --fst value Fireblocks secret storage type. Supported values are 'plaintext' and 'aws_secret_manager' [$FIREBLOCKS_SECRET_STORAGE_TYPE] + --fireblocks-timeout value, --ft value Fireblocks timeout (default: 30) [$FIREBLOCKS_TIMEOUT] + --fireblocks-vault-account-name value, --fv value Fireblocks vault account name [$FIREBLOCKS_VAULT_ACCOUNT_NAME] + --network value, -n value Network to use. Currently supports 'holesky' and 'mainnet' (default: "holesky") [$NETWORK] + --operator-address value, --oa value, --operator value Operator address [$OPERATOR_ADDRESS] + --operator-set-id value, --osid value Operator set ID (default: 0) [$OPERATOR_SET_ID] + --output-file value, -o value Output file to write the data [$OUTPUT_FILE] + --output-type value, --ot value Output format of the command. One of 'pretty', 'json' or 'calldata' (default: "pretty") [$OUTPUT_TYPE] + --path-to-key-store value, -k value Path to the key store used to send transactions [$PATH_TO_KEY_STORE] + --strategy-address value, --sa value Strategy addresses [$STRATEGY_ADDRESS] + --verbose, -v Enable verbose logging (default: false) [$VERBOSE] + --web3signer-url value, -w value URL of the Web3Signer [$WEB3SIGNER_URL] + --help, -h show help +``` \ No newline at end of file diff --git a/pkg/operator/allocations/flags.go b/pkg/operator/allocations/flags.go new file mode 100644 index 00000000..cb096919 --- /dev/null +++ b/pkg/operator/allocations/flags.go @@ -0,0 +1,19 @@ +package allocations + +import "github.com/urfave/cli/v2" + +var ( + BipsToAllocateFlag = cli.Uint64Flag{ + Name: "bips-to-allocate", + Aliases: []string{"bta", "bips", "bps"}, + Usage: "Bips to allocate to the strategy", + EnvVars: []string{"BIPS_TO_ALLOCATE"}, + } + + EnvironmentFlag = cli.StringFlag{ + Name: "environment", + Aliases: []string{"env"}, + Usage: "environment to use. Currently supports 'preprod' ,'testnet' and 'prod'. If not provided, it will be inferred based on network", + EnvVars: []string{"ENVIRONMENT"}, + } +) diff --git a/pkg/operator/allocations/set_allocation_delay.go b/pkg/operator/allocations/set_allocation_delay.go new file mode 100644 index 00000000..82af2b8c --- /dev/null +++ b/pkg/operator/allocations/set_allocation_delay.go @@ -0,0 +1,208 @@ +package allocations + +import ( + "fmt" + "sort" + "strconv" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + + "github.com/urfave/cli/v2" +) + +func SetDelayCmd(p utils.Prompter) *cli.Command { + setDelayCmd := &cli.Command{ + Name: "set-delay", + UsageText: "set-delay [flags] ", + Usage: "Set the allocation delay for operator in blocks", + Description: "Set the allocation delay for operator. It will take effect after the delay period", + Flags: getSetAllocationDelayFlags(), + After: telemetry.AfterRunAction(), + Action: func(c *cli.Context) error { + return setDelayAction(c, p) + }, + } + + return setDelayCmd +} + +func setDelayAction(cCtx *cli.Context, p utils.Prompter) error { + ctx := cCtx.Context + logger := common.GetLogger(cCtx) + + config, err := readAndValidateAllocationDelayConfig(cCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate claim config", err) + } + cCtx.App.Metadata["network"] = config.chainID.String() + ethClient, err := ethclient.Dial(config.rpcUrl) + if err != nil { + return eigenSdkUtils.WrapError("failed to create new eth client", err) + } + + if config.broadcast { + confirm, err := p.Confirm( + "This will set the allocation delay for operator. Do you want to continue?", + ) + if err != nil { + return err + } + if !confirm { + logger.Info("Operation cancelled") + return nil + } + eLWriter, err := common.GetELWriter( + config.operatorAddress, + config.signerConfig, + ethClient, + elcontracts.Config{ + DelegationManagerAddress: config.delegationManagerAddress, + }, + p, + config.chainID, + logger, + ) + + if err != nil { + return eigenSdkUtils.WrapError("failed to get EL writer", err) + } + + receipt, err := eLWriter.SetAllocationDelay(ctx, config.operatorAddress, config.allocationDelay, true) + if err != nil { + return err + } + common.PrintTransactionInfo(receipt.TxHash.String(), config.chainID) + } else { + noSendTxOpts := common.GetNoSendTxOpts(config.operatorAddress) + _, _, contractBindings, err := elcontracts.BuildClients(elcontracts.Config{ + DelegationManagerAddress: config.delegationManagerAddress, + }, ethClient, nil, logger, nil) + if err != nil { + return err + } + // If operator is a smart contract, we can't estimate gas using geth + // since balance of contract can be 0, as it can be called by an EOA + // to claim. So we hardcode the gas limit to 150_000 so that we can + // create unsigned tx without gas limit estimation from contract bindings + if common.IsSmartContractAddress(config.operatorAddress, ethClient) { + // address is a smart contract + noSendTxOpts.GasLimit = 150_000 + } + + unsignedTx, err := contractBindings.AllocationManager.SetAllocationDelay(noSendTxOpts, config.operatorAddress, config.allocationDelay) + 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.output) { + err = common.WriteToFile([]byte(calldataHex), config.output) + if err != nil { + return err + } + logger.Infof("Call data written to file: %s", config.output) + } else { + fmt.Println(calldataHex) + } + } else { + if !common.IsEmptyString(config.output) { + fmt.Println("output file not supported for pretty output type") + fmt.Println() + } + fmt.Printf("Allocation delay %d will be set for operator %s\n", config.allocationDelay, config.operatorAddress.String()) + } + txFeeDetails := common.GetTxFeeDetails(unsignedTx) + fmt.Println() + txFeeDetails.Print() + fmt.Println("To broadcast the transaction, use the --broadcast flag") + } + + return nil +} + +func getSetAllocationDelayFlags() []cli.Flag { + baseFlags := []cli.Flag{ + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + &flags.OutputFileFlag, + &flags.OutputTypeFlag, + &flags.BroadcastFlag, + &flags.VerboseFlag, + &flags.OperatorAddressFlag, + &flags.DelegationManagerAddressFlag, + } + allFlags := append(baseFlags, flags.GetSignerFlags()...) + sort.Sort(cli.FlagsByName(allFlags)) + return allFlags +} + +func readAndValidateAllocationDelayConfig(c *cli.Context, logger logging.Logger) (*allocationDelayConfig, error) { + args := c.Args() + if args.Len() != 1 { + return nil, fmt.Errorf("accepts 1 arg, received %d", args.Len()) + } + + allocationDelayString := c.Args().First() + allocationDelayInt, err := strconv.Atoi(allocationDelayString) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to convert allocation delay to int", err) + } + + network := c.String(flags.NetworkFlag.Name) + environment := c.String(EnvironmentFlag.Name) + rpcUrl := c.String(flags.ETHRpcUrlFlag.Name) + output := c.String(flags.OutputFileFlag.Name) + outputType := c.String(flags.OutputTypeFlag.Name) + broadcast := c.Bool(flags.BroadcastFlag.Name) + operatorAddress := c.String(flags.OperatorAddressFlag.Name) + + chainID := utils.NetworkNameToChainId(network) + logger.Debugf("Using chain ID: %s", chainID.String()) + + if common.IsEmptyString(environment) { + environment = common.GetEnvFromNetwork(network) + } + logger.Debugf("Using network %s and environment: %s", network, environment) + + // Get signerConfig + signerConfig, err := common.GetSignerConfig(c, logger) + if err != nil { + // We don't want to throw error since people can still use it to generate the claim + // without broadcasting it + logger.Debugf("Failed to get signer config: %s", err) + } + + delegationManagerAddress := c.String(flags.DelegationManagerAddressFlag.Name) + if delegationManagerAddress == "" { + delegationManagerAddress, err = common.GetDelegationManagerAddress(chainID) + if err != nil { + return nil, err + } + } + + return &allocationDelayConfig{ + network: network, + rpcUrl: rpcUrl, + environment: environment, + chainID: chainID, + output: output, + outputType: outputType, + broadcast: broadcast, + operatorAddress: gethcommon.HexToAddress(operatorAddress), + signerConfig: signerConfig, + delegationManagerAddress: gethcommon.HexToAddress(delegationManagerAddress), + allocationDelay: uint32(allocationDelayInt), + }, nil +} diff --git a/pkg/operator/allocations/show.go b/pkg/operator/allocations/show.go new file mode 100644 index 00000000..27208dbc --- /dev/null +++ b/pkg/operator/allocations/show.go @@ -0,0 +1,318 @@ +package allocations + +import ( + "fmt" + "math/big" + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + allocationmanager "github.com/Layr-Labs/eigensdk-go/contracts/bindings/AllocationManager" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + + "github.com/urfave/cli/v2" +) + +var ( + // PrecisionFactor comes from the allocation manager contract + PrecisionFactor = big.NewInt(1e18) +) + +func ShowCmd(p utils.Prompter) *cli.Command { + showCmd := &cli.Command{ + Name: "show", + Usage: "Show allocations", + After: telemetry.AfterRunAction(), + Description: ` +Command to show allocations +`, + Flags: getShowFlags(), + Action: func(cCtx *cli.Context) error { + return showAction(cCtx, p) + }, + } + return showCmd +} + +func showAction(cCtx *cli.Context, p utils.Prompter) error { + ctx := cCtx.Context + logger := common.GetLogger(cCtx) + + config, err := readAndValidateShowConfig(cCtx, &logger) + if err != nil { + return err + } + cCtx.App.Metadata["network"] = config.chainID.String() + + ethClient, err := ethclient.Dial(config.rpcUrl) + if err != nil { + return eigenSdkUtils.WrapError("failed to create new eth client", err) + } + + elReader, err := elcontracts.NewReaderFromConfig( + elcontracts.Config{ + DelegationManagerAddress: config.delegationManagerAddress, + }, + ethClient, + logger, + ) + if err != nil { + return eigenSdkUtils.WrapError("failed to create new reader from config", err) + } + + /* + 1. Get the allocatable magnitude for all strategies + */ + for _, strategyAddress := range config.strategyAddresses { + allocatableMagnitude, err := elReader.GetAllocatableMagnitude( + ctx, + config.operatorAddress, + strategyAddress, + ) + if err != nil { + return eigenSdkUtils.WrapError("failed to get allocatable magnitude", err) + } + logger.Debugf( + "Allocatable magnitude for strategy %v: %s", + strategyAddress, + common.FormatNumberWithUnderscores(common.Uint64ToString(allocatableMagnitude)), + ) + } + + /* + 2. Get the total magnitude for all strategies + */ + totalMagnitudeMap := make(map[string]uint64) + totalMagnitudes, err := elReader.GetMaxMagnitudes( + ctx, + config.operatorAddress, + config.strategyAddresses, + ) + if err != nil { + return eigenSdkUtils.WrapError("failed to get allocatable magnitude", err) + } + for i, strategyAddress := range config.strategyAddresses { + totalMagnitudeMap[strategyAddress.String()] = totalMagnitudes[i] + } + + /* + 3. Get allocation info for the operator + */ + allAllocations := make(map[string][]elcontracts.AllocationInfo, len(config.strategyAddresses)) + for _, strategyAddress := range config.strategyAddresses { + allocations, err := elReader.GetAllocationInfo( + ctx, + config.operatorAddress, + strategyAddress, + ) + if err != nil { + return eigenSdkUtils.WrapError("failed to get allocations", err) + } + allAllocations[strategyAddress.String()] = allocations + } + + /* + 4. Get the operator's registered operator sets + */ + registeredOperatorSets, err := elReader.GetRegisteredSets(ctx, config.operatorAddress) + if err != nil { + return eigenSdkUtils.WrapError("failed to get registered operator sets", err) + } + registeredOperatorSetsMap := make(map[string]allocationmanager.OperatorSet) + for _, opSet := range registeredOperatorSets { + registeredOperatorSetsMap[getUniqueKey(opSet.Avs, opSet.Id)] = opSet + } + + /* + 5. Get the operator scaled shares for all strategies + */ + operatorDelegatedSharesMap := make(map[string]*big.Int) + shares, err := elReader.GetOperatorShares(ctx, config.operatorAddress, config.strategyAddresses) + if err != nil { + return eigenSdkUtils.WrapError("failed to get operator shares", err) + } + for i, strategyAddress := range config.strategyAddresses { + operatorDelegatedSharesMap[strategyAddress.String()] = shares[i] + } + + /* + 6. Using all of the above, calculate SlashableMagnitudeHolders object + for displaying the allocation state of the operator + */ + slashableMagnitudeHolders := make(SlashableMagnitudeHolders, 0) + dergisteredOpsets := make(DeregsiteredOperatorSets, 0) + for strategy, allocations := range allAllocations { + strategyShares := operatorDelegatedSharesMap[strategy] + for _, alloc := range allocations { + currentShares, currentSharesPercentage := getSharesFromMagnitude( + strategyShares, + alloc.CurrentMagnitude.Uint64(), + ) + newMagnitudeBigInt := big.NewInt(0) + if alloc.PendingDiff.Cmp(big.NewInt(0)) != 0 { + newMagnitudeBigInt = big.NewInt(0).Add(alloc.CurrentMagnitude, alloc.PendingDiff) + } + newShares, newSharesPercentage := getSharesFromMagnitude(strategyShares, newMagnitudeBigInt.Uint64()) + + // Check if the operator set is not registered and add it to the unregistered list + // Then skip the rest of the loop + if _, ok := registeredOperatorSetsMap[getUniqueKey(alloc.AvsAddress, alloc.OperatorSetId)]; !ok { + dergisteredOpsets = append(dergisteredOpsets, DeregisteredOperatorSet{ + StrategyAddress: gethcommon.HexToAddress(strategy), + AVSAddress: alloc.AvsAddress, + OperatorSetId: alloc.OperatorSetId, + SlashableMagnitude: alloc.CurrentMagnitude.Uint64(), + Shares: currentShares, + SharesPercentage: currentSharesPercentage.String(), + }) + continue + } + + // Add the operator set to the registered list + slashableMagnitudeHolders = append(slashableMagnitudeHolders, SlashableMagnitudesHolder{ + StrategyAddress: gethcommon.HexToAddress(strategy), + AVSAddress: alloc.AvsAddress, + OperatorSetId: alloc.OperatorSetId, + SlashableMagnitude: alloc.CurrentMagnitude.Uint64(), + Shares: currentShares, + SharesPercentage: currentSharesPercentage.String(), + NewMagnitude: newMagnitudeBigInt.Uint64(), + UpdateBlock: alloc.EffectBlock, + NewAllocationShares: newShares, + UpcomingSharesPercentage: newSharesPercentage.String(), + }) + } + } + + for key, val := range operatorDelegatedSharesMap { + fmt.Printf("Strategy Address: %s, Shares %s\n", key, val.String()) + } + + currBlockNumber, err := ethClient.BlockNumber(ctx) + if err != nil { + return eigenSdkUtils.WrapError("failed to get current block number", err) + } + delay, err := elReader.GetAllocationDelay(ctx, config.operatorAddress) + if err != nil { + return err + } + fmt.Println() + fmt.Printf("Current allocation delay: %d blocks\n", delay) + fmt.Println() + fmt.Printf( + "------------------ Allocation State for %s (Block: %d) ---------------------\n", + config.operatorAddress.String(), + currBlockNumber, + ) + if config.outputType == string(common.OutputType_Json) { + slashableMagnitudeHolders.PrintJSON() + } else { + slashableMagnitudeHolders.PrintPretty() + } + + if len(dergisteredOpsets) > 0 { + fmt.Println() + fmt.Printf( + "NOTE: You have %d deregistered operator sets which have nonzero allocations as listed below. Please deallocate to use those funds.\n", + len(dergisteredOpsets), + ) + if config.outputType == string(common.OutputType_Json) { + dergisteredOpsets.PrintJSON() + } else { + dergisteredOpsets.PrintPretty() + } + } + + return nil +} + +func getSharesFromMagnitude(totalScaledShare *big.Int, magnitude uint64) (*big.Int, *big.Float) { + + /* + * shares = totalScaledShare * magnitude / PrecisionFactor + * percentageShares = (shares / totalScaledShare) * 100 + */ + // Check for zero magnitude or totalScaledShare to avoid divide-by-zero errors + if magnitude == 0 || totalScaledShare.Cmp(big.NewInt(0)) == 0 { + return big.NewInt(0), big.NewFloat(0) + } + + slashableMagBigInt := big.NewInt(1) + slashableMagBigInt = slashableMagBigInt.SetUint64(magnitude) + + scaledOpShares := big.NewInt(1) + scaledOpShares = scaledOpShares.Set(totalScaledShare) + scaledOpShares = scaledOpShares.Div(scaledOpShares, PrecisionFactor) + shares := scaledOpShares.Mul(scaledOpShares, slashableMagBigInt) + + percentageShares := big.NewInt(1) + percentageShares = percentageShares.Mul(scaledOpShares, big.NewInt(100)) + percentageSharesFloat := new( + big.Float, + ).Quo(new(big.Float).SetInt(percentageShares), new(big.Float).SetInt(totalScaledShare)) + + return shares, percentageSharesFloat +} + +func getUniqueKey(strategyAddress gethcommon.Address, opSetId uint32) string { + return fmt.Sprintf("%s-%d", strategyAddress.String(), opSetId) +} + +func readAndValidateShowConfig(cCtx *cli.Context, logger *logging.Logger) (*showConfig, error) { + network := cCtx.String(flags.NetworkFlag.Name) + rpcUrl := cCtx.String(flags.ETHRpcUrlFlag.Name) + environment := cCtx.String(flags.EnvironmentFlag.Name) + operatorAddress := gethcommon.HexToAddress(cCtx.String(flags.OperatorAddressFlag.Name)) + avsAddresses := common.ConvertStringSliceToGethAddressSlice(cCtx.StringSlice(flags.AVSAddressesFlag.Name)) + strategyAddresses := common.ConvertStringSliceToGethAddressSlice(cCtx.StringSlice(flags.StrategyAddressesFlag.Name)) + outputFile := cCtx.String(flags.OutputFileFlag.Name) + outputType := cCtx.String(flags.OutputTypeFlag.Name) + + chainId := utils.NetworkNameToChainId(network) + delegationManagerAddress := cCtx.String(flags.DelegationManagerAddressFlag.Name) + var err error + if delegationManagerAddress == "" { + delegationManagerAddress, err = common.GetDelegationManagerAddress(chainId) + if err != nil { + return nil, err + } + } + + return &showConfig{ + network: network, + rpcUrl: rpcUrl, + environment: environment, + operatorAddress: operatorAddress, + avsAddresses: avsAddresses, + strategyAddresses: strategyAddresses, + output: outputFile, + outputType: outputType, + delegationManagerAddress: gethcommon.HexToAddress(delegationManagerAddress), + }, nil +} + +func getShowFlags() []cli.Flag { + baseFlags := []cli.Flag{ + &flags.OperatorAddressFlag, + &flags.AVSAddressesFlag, + &flags.StrategyAddressesFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + &flags.VerboseFlag, + &flags.OutputFileFlag, + &flags.OutputTypeFlag, + &flags.DelegationManagerAddressFlag, + } + + sort.Sort(cli.FlagsByName(baseFlags)) + return baseFlags +} diff --git a/pkg/operator/allocations/testdata/allocations1.csv b/pkg/operator/allocations/testdata/allocations1.csv new file mode 100644 index 00000000..f6d13110 --- /dev/null +++ b/pkg/operator/allocations/testdata/allocations1.csv @@ -0,0 +1,5 @@ +avs_address,operator_set_id,strategy_address,bips +0x2222AAC0C980Cc029624b7ff55B88Bc6F63C538f,1,0x49989b32351Eb9b8ab2d5623cF22E7F7C23e5630,2000 +0x2222AAC0C980Cc029624b7ff55B88Bc6F63C538f,3,0x49989b32351Eb9b8ab2d5623cF22E7F7C23e5630,1000 +0x111116fE4F8C2f83E3eB2318F090557b7CD0BF76,4,0x232326fE4F8C2f83E3eB2318F090557b7CD02222,3000 +0x111116fE4F8C2f83E3eB2318F090557b7CD0BF76,5,0x545456fE4F8C2f83E3eB2318F090557b7CD04567,4000 \ No newline at end of file diff --git a/pkg/operator/allocations/testdata/allocations_duplicate.csv b/pkg/operator/allocations/testdata/allocations_duplicate.csv new file mode 100644 index 00000000..0da774fb --- /dev/null +++ b/pkg/operator/allocations/testdata/allocations_duplicate.csv @@ -0,0 +1,6 @@ +avs_address,operator_set_id,strategy_address,bips +0x2222AAC0C980Cc029624b7ff55B88Bc6F63C538f,1,0x49989b32351Eb9b8ab2d5623cF22E7F7C23e5630,2000 +0x2222AAC0C980Cc029624b7ff55B88Bc6F63C538f,3,0x49989b32351Eb9b8ab2d5623cF22E7F7C23e5630,1000 +0x111116fE4F8C2f83E3eB2318F090557b7CD0BF76,4,0x232326fE4F8C2f83E3eB2318F090557b7CD02222,3000 +0x111116fE4F8C2f83E3eB2318F090557b7CD0BF76,5,0x545456fE4F8C2f83E3eB2318F090557b7CD04567,4000 +0x111116fE4F8C2f83E3eB2318F090557b7CD0BF76,5,0x545456fE4F8C2f83E3eB2318F090557b7CD04567,5000 \ No newline at end of file diff --git a/pkg/operator/allocations/types.go b/pkg/operator/allocations/types.go new file mode 100644 index 00000000..2ceb1ac0 --- /dev/null +++ b/pkg/operator/allocations/types.go @@ -0,0 +1,294 @@ +package allocations + +import ( + "encoding/json" + "fmt" + "math/big" + "strings" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" + "github.com/Layr-Labs/eigenlayer-cli/pkg/types" + + allocationmanager "github.com/Layr-Labs/eigensdk-go/contracts/bindings/AllocationManager" + + gethcommon "github.com/ethereum/go-ethereum/common" +) + +type BulkModifyAllocations struct { + Allocations []allocationmanager.IAllocationManagerTypesAllocateParams + AllocatableMagnitudes map[gethcommon.Address]uint64 +} + +func (b *BulkModifyAllocations) PrintPretty() { + + fmt.Println() + fmt.Println("Allocations to be Updated") + allocations := b.Allocations + headers := []string{ + "Strategy", + "Allocatable Magnitude", + "Operator Set ID", + "AVS", + "Magnitude", + } + widths := []int{20, 25, 20, 20, 25} + + // print dashes + for _, width := range widths { + fmt.Print("+" + strings.Repeat("-", width+1)) + } + + fmt.Println("+") + + // Print header + for i, header := range headers { + fmt.Printf("| %-*s", widths[i], header) + } + + fmt.Println("|") + + // Print separator + for _, width := range widths { + fmt.Print("|", strings.Repeat("-", width+1)) + } + + fmt.Println("|") + + // Print data rows + for _, a := range allocations { + for i, strategy := range a.Strategies { + fmt.Printf( + "| %-*s| %-*s| %-*d| %-*s| %-*s|\n", + widths[0], + common.ShortEthAddress(strategy), + widths[1], + common.FormatNumberWithUnderscores(common.Uint64ToString(b.AllocatableMagnitudes[strategy])), + widths[2], + a.OperatorSet.Id, + widths[3], + common.ShortEthAddress(a.OperatorSet.Avs), + widths[4], + common.FormatNumberWithUnderscores(common.Uint64ToString(a.NewMagnitudes[i])), + ) + } + } + + // print dashes + for _, width := range widths { + fmt.Print("+" + strings.Repeat("-", width+1)) + } + + fmt.Println("+") +} + +type updateConfig struct { + network string + rpcUrl string + environment string + chainID *big.Int + output string + outputType string + broadcast bool + operatorAddress gethcommon.Address + avsAddress gethcommon.Address + strategyAddress gethcommon.Address + delegationManagerAddress gethcommon.Address + operatorSetId uint32 + bipsToAllocate uint64 + signerConfig *types.SignerConfig + csvFilePath string + isSilent bool +} + +type allocation struct { + AvsAddress gethcommon.Address `csv:"avs_address"` + OperatorSetId uint32 `csv:"operator_set_id"` + StrategyAddress gethcommon.Address `csv:"strategy_address"` + Bips uint64 `csv:"bips"` +} + +type allocationDelayConfig struct { + network string + rpcUrl string + environment string + chainID *big.Int + output string + outputType string + broadcast bool + operatorAddress gethcommon.Address + signerConfig *types.SignerConfig + allocationDelay uint32 + delegationManagerAddress gethcommon.Address +} + +type showConfig struct { + network string + rpcUrl string + environment string + chainID *big.Int + output string + outputType string + operatorAddress gethcommon.Address + delegationManagerAddress gethcommon.Address + avsAddresses []gethcommon.Address + strategyAddresses []gethcommon.Address +} + +type SlashableMagnitudeHolders []SlashableMagnitudesHolder + +type SlashableMagnitudesHolder struct { + StrategyAddress gethcommon.Address + AVSAddress gethcommon.Address + OperatorSetId uint32 + SlashableMagnitude uint64 + NewMagnitude uint64 + NewAllocationShares *big.Int + UpdateBlock uint32 + Shares *big.Int + SharesPercentage string + UpcomingSharesPercentage string +} + +func (s SlashableMagnitudeHolders) PrintPretty() { + // Define column headers and widths + headers := []string{ + "Strategy Address", + "AVS Address", + "OperatorSet ID", + "Slashable Shares (Wei)", + "Shares %", + "Upcoming Shares (Wei)", + "Upcoming Shares %", + "Update Block", + } + widths := []int{len(headers[0]) + 1, len(headers[1]) + 3, 15, 30, 25, 30, 25, 25} + + // print dashes + for _, width := range widths { + fmt.Print("+" + strings.Repeat("-", width+1)) + } + fmt.Println("+") + + // Print header + for i, header := range headers { + fmt.Printf("| %-*s", widths[i], header) + } + fmt.Println("|") + + // Print separator + for _, width := range widths { + fmt.Print("|", strings.Repeat("-", width+1)) + } + fmt.Println("|") + + // Print data rows + for _, holder := range s { + + upcomingSharesDisplay := common.FormatNumberWithUnderscores(holder.NewAllocationShares.String()) + + fmt.Printf("| %-*s| %-*s| %-*d| %-*s| %-*s| %-*s| %-*s| %-*d|\n", + widths[0], common.ShortEthAddress(holder.StrategyAddress), + widths[1], common.ShortEthAddress(holder.AVSAddress), + widths[2], holder.OperatorSetId, + widths[3], common.FormatNumberWithUnderscores(holder.Shares.String()), + widths[4], holder.SharesPercentage+" %", + widths[5], upcomingSharesDisplay, + widths[6], holder.UpcomingSharesPercentage+" %", + widths[7], holder.UpdateBlock, + ) + } + + // print dashes + for _, width := range widths { + fmt.Print("+" + strings.Repeat("-", width+1)) + } + fmt.Println("+") +} + +func (s SlashableMagnitudeHolders) PrintJSON() { + obj, err := json.MarshalIndent(s, "", " ") + if err != nil { + fmt.Println("Error marshalling to JSON:", err) + return + } + fmt.Println(string(obj)) +} + +type DeregsiteredOperatorSets []DeregisteredOperatorSet +type DeregisteredOperatorSet struct { + StrategyAddress gethcommon.Address + AVSAddress gethcommon.Address + OperatorSetId uint32 + SlashableMagnitude uint64 + Shares *big.Int + SharesPercentage string +} + +func (s DeregsiteredOperatorSets) PrintPretty() { + // Define column headers and widths + headers := []string{ + "Strategy Address", + "AVS Address", + "OperatorSet ID", + "Slashable Shares (Wei)", + "Shares %", + } + widths := []int{len(headers[0]) + 1, len(headers[1]) + 3, 15, 30, 25} + + // print dashes + for _, width := range widths { + fmt.Print("+" + strings.Repeat("-", width+1)) + } + fmt.Println("+") + + // Print header + for i, header := range headers { + fmt.Printf("| %-*s", widths[i], header) + } + fmt.Println("|") + + // Print separator + for _, width := range widths { + fmt.Print("|", strings.Repeat("-", width+1)) + } + fmt.Println("|") + + // Print data rows + for _, holder := range s { + fmt.Printf("| %-*s| %-*s| %-*d| %-*s| %-*s|\n", + widths[0], common.ShortEthAddress(holder.StrategyAddress), + widths[1], common.ShortEthAddress(holder.AVSAddress), + widths[2], holder.OperatorSetId, + widths[3], common.FormatNumberWithUnderscores(holder.Shares.String()), + widths[4], holder.SharesPercentage+" %", + ) + } + + // print dashes + for _, width := range widths { + fmt.Print("+" + strings.Repeat("-", width+1)) + } + fmt.Println("+") +} + +func (s DeregsiteredOperatorSets) PrintJSON() { + obj, err := json.MarshalIndent(s, "", " ") + if err != nil { + fmt.Println("Error marshalling to JSON:", err) + return + } + fmt.Println(string(obj)) +} + +type AllocationDetails struct { + StrategyAddress gethcommon.Address + AVSAddress gethcommon.Address + OperatorSetId uint32 + Allocation uint64 + Timestamp uint32 +} + +type AllocDetails struct { + Magnitude uint64 + Timestamp uint32 +} diff --git a/pkg/operator/allocations/update.go b/pkg/operator/allocations/update.go new file mode 100644 index 00000000..2f196c24 --- /dev/null +++ b/pkg/operator/allocations/update.go @@ -0,0 +1,492 @@ +package allocations + +import ( + "context" + "errors" + "fmt" + "math/big" + "os" + "sort" + "sync" + + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + allocationmanager "github.com/Layr-Labs/eigensdk-go/contracts/bindings/AllocationManager" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + + "github.com/gocarina/gocsv" + "github.com/urfave/cli/v2" +) + +type elChainReader interface { + GetMaxMagnitudes( + ctx context.Context, + operatorAddress gethcommon.Address, + strategyAddresses []gethcommon.Address, + ) ([]uint64, error) + GetAllocatableMagnitude( + ctx context.Context, + operator gethcommon.Address, + strategy gethcommon.Address, + ) (uint64, error) +} + +func UpdateCmd(p utils.Prompter) *cli.Command { + updateCmd := &cli.Command{ + Name: "update", + Usage: "Update allocations", + UsageText: "update", + Description: ` +Command to update allocations of slashable stake + `, + Flags: getUpdateFlags(), + After: telemetry.AfterRunAction(), + Action: func(context *cli.Context) error { + return updateAllocations(context, p) + }, + } + + return updateCmd +} + +func updateAllocations(cCtx *cli.Context, p utils.Prompter) error { + ctx := cCtx.Context + logger := common.GetLogger(cCtx) + + config, err := readAndValidateUpdateFlags(cCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate update flags", err) + } + cCtx.App.Metadata["network"] = config.chainID.String() + + ethClient, err := ethclient.Dial(config.rpcUrl) + if err != nil { + return eigenSdkUtils.WrapError("failed to create new eth client", err) + } + + elReader, err := elcontracts.NewReaderFromConfig( + elcontracts.Config{ + DelegationManagerAddress: config.delegationManagerAddress, + }, + ethClient, + logger, + ) + if err != nil { + return eigenSdkUtils.WrapError("failed to create new reader from config", err) + } + + allocationsToUpdate, err := generateAllocationsParams(ctx, elReader, config, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to generate Allocations params", err) + } + + if config.broadcast { + if config.signerConfig == nil { + return errors.New("signer is required for broadcasting") + } + logger.Info("Broadcasting magnitude allocation update...") + eLWriter, err := common.GetELWriter( + config.operatorAddress, + config.signerConfig, + ethClient, + elcontracts.Config{ + DelegationManagerAddress: config.delegationManagerAddress, + }, + p, + config.chainID, + logger, + ) + if err != nil { + return eigenSdkUtils.WrapError("failed to get EL writer", err) + } + + receipt, err := eLWriter.ModifyAllocations( + ctx, + config.operatorAddress, + allocationsToUpdate.Allocations, + true, + ) + if err != nil { + return err + } + common.PrintTransactionInfo(receipt.TxHash.String(), config.chainID) + } else { + noSendTxOpts := common.GetNoSendTxOpts(config.operatorAddress) + _, _, contractBindings, err := elcontracts.BuildClients(elcontracts.Config{ + DelegationManagerAddress: config.delegationManagerAddress, + }, ethClient, nil, logger, nil) + if err != nil { + return err + } + // If operator is a smart contract, we can't estimate gas using geth + // since balance of contract can be 0, as it can be called by an EOA + // to claim. So we hardcode the gas limit to 150_000 so that we can + // create unsigned tx without gas limit estimation from contract bindings + if common.IsSmartContractAddress(config.operatorAddress, ethClient) { + // address is a smart contract + noSendTxOpts.GasLimit = 150_000 + } + + unsignedTx, err := contractBindings.AllocationManager.ModifyAllocations( + noSendTxOpts, + config.operatorAddress, + allocationsToUpdate.Allocations, + ) + 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.output) { + err = common.WriteToFile([]byte(calldataHex), config.output) + if err != nil { + return err + } + logger.Infof("Call data written to file: %s", config.output) + } else { + fmt.Println(calldataHex) + } + } else { + if !common.IsEmptyString(config.output) { + fmt.Println("output file not supported for pretty output type") + fmt.Println() + } + allocationsToUpdate.PrintPretty() + } + if !config.isSilent { + txFeeDetails := common.GetTxFeeDetails(unsignedTx) + fmt.Println() + txFeeDetails.Print() + fmt.Println("To broadcast the transaction, use the --broadcast flag") + } + } + + return nil +} + +func getUpdateFlags() []cli.Flag { + baseFlags := []cli.Flag{ + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + &flags.OutputFileFlag, + &flags.OutputTypeFlag, + &flags.BroadcastFlag, + &flags.VerboseFlag, + &flags.AVSAddressFlag, + &flags.StrategyAddressFlag, + &flags.OperatorAddressFlag, + &flags.OperatorSetIdFlag, + &flags.CSVFileFlag, + &flags.DelegationManagerAddressFlag, + &flags.SilentFlag, + &BipsToAllocateFlag, + } + allFlags := append(baseFlags, flags.GetSignerFlags()...) + sort.Sort(cli.FlagsByName(allFlags)) + return allFlags +} + +func generateAllocationsParams( + ctx context.Context, + elReader elChainReader, + config *updateConfig, + logger logging.Logger, +) (*BulkModifyAllocations, error) { + allocations := make([]allocationmanager.IAllocationManagerTypesAllocateParams, 0) + var allocatableMagnitudes map[gethcommon.Address]uint64 + + var err error + if len(config.csvFilePath) == 0 { + magnitude, err := elReader.GetMaxMagnitudes( + ctx, + config.operatorAddress, + []gethcommon.Address{config.strategyAddress}, + ) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to get latest total magnitude", err) + } + allocatableMagnitude, err := elReader.GetAllocatableMagnitude( + ctx, + config.operatorAddress, + config.strategyAddress, + ) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to get allocatable magnitude", err) + } + logger.Debugf("Total Magnitude: %d", magnitude) + logger.Debugf("Allocatable Magnitude: %d", allocatableMagnitude) + logger.Debugf("Bips to allocate: %d", config.bipsToAllocate) + magnitudeToUpdate := calculateMagnitudeToUpdate(magnitude[0], config.bipsToAllocate) + logger.Debugf("Magnitude to update: %d", magnitudeToUpdate) + malloc := allocationmanager.IAllocationManagerTypesAllocateParams{ + Strategies: []gethcommon.Address{config.strategyAddress}, + OperatorSet: allocationmanager.OperatorSet{ + Avs: config.avsAddress, + Id: config.operatorSetId, + }, + NewMagnitudes: []uint64{magnitudeToUpdate}, + } + allocations = append(allocations, malloc) + } else { + allocations, allocatableMagnitudes, err = computeAllocations(config.csvFilePath, config.operatorAddress, elReader) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to compute allocations", err) + } + } + + return &BulkModifyAllocations{ + Allocations: allocations, + AllocatableMagnitudes: allocatableMagnitudes, + }, nil +} + +func computeAllocations( + filePath string, + operatorAddress gethcommon.Address, + elReader elChainReader, +) ([]allocationmanager.IAllocationManagerTypesAllocateParams, map[gethcommon.Address]uint64, error) { + allocations, err := parseAllocationsCSV(filePath) + if err != nil { + return nil, nil, eigenSdkUtils.WrapError("failed to parse allocations csv", err) + } + + err = validateDataFromCSV(allocations) + if err != nil { + return nil, nil, eigenSdkUtils.WrapError("failed to validate data from csv", err) + } + + strategies := getUniqueStrategies(allocations) + strategyTotalMagnitudes, err := getMagnitudes(strategies, operatorAddress, elReader) + if err != nil { + return nil, nil, eigenSdkUtils.WrapError("failed to get total magnitudes", err) + } + + allocatableMagnitudePerStrategy, err := parallelGetAllocatableMagnitudes(strategies, operatorAddress, elReader) + if err != nil { + return nil, nil, eigenSdkUtils.WrapError("failed to get allocatable magnitudes", err) + } + + magnitudeAllocations := convertAllocationsToMagnitudeAllocations(allocations, strategyTotalMagnitudes) + return magnitudeAllocations, allocatableMagnitudePerStrategy, nil +} + +func validateDataFromCSV(allocations []allocation) error { + // check for duplicated (avs_address,operator_set_id,strategy_address) + tuples := make(map[string]struct{}) + + for _, alloc := range allocations { + tuple := fmt.Sprintf("%s_%d_%s", alloc.AvsAddress.Hex(), alloc.OperatorSetId, alloc.StrategyAddress.Hex()) + if _, exists := tuples[tuple]; exists { + return fmt.Errorf( + "duplicate combination found: avs_address=%s, operator_set_id=%d, strategy_address=%s", + alloc.AvsAddress.Hex(), + alloc.OperatorSetId, + alloc.StrategyAddress.Hex(), + ) + } + tuples[tuple] = struct{}{} + } + + return nil +} + +func parallelGetAllocatableMagnitudes( + strategies []gethcommon.Address, + operatorAddress gethcommon.Address, + elReader elChainReader, +) (map[gethcommon.Address]uint64, error) { + strategyAllocatableMagnitudes := make(map[gethcommon.Address]uint64, len(strategies)) + var wg sync.WaitGroup + errChan := make(chan error, len(strategies)) + + for _, s := range strategies { + wg.Add(1) + go func(strategy gethcommon.Address) { + defer wg.Done() + magnitude, err := elReader.GetAllocatableMagnitude(context.Background(), operatorAddress, strategy) + if err != nil { + errChan <- err + return + } + strategyAllocatableMagnitudes[strategy] = magnitude + }(s) + } + + wg.Wait() + close(errChan) + + if len(errChan) > 0 { + return nil, <-errChan // Return the first error encountered + } + + return strategyAllocatableMagnitudes, nil +} + +func getMagnitudes( + strategies []gethcommon.Address, + operatorAddress gethcommon.Address, + reader elChainReader, +) (map[gethcommon.Address]uint64, error) { + strategyTotalMagnitudes := make(map[gethcommon.Address]uint64, len(strategies)) + totalMagnitudes, err := reader.GetMaxMagnitudes( + context.Background(), + operatorAddress, + strategies, + ) + if err != nil { + return nil, err + } + i := 0 + for _, strategy := range strategies { + strategyTotalMagnitudes[strategy] = totalMagnitudes[i] + i++ + } + + return strategyTotalMagnitudes, nil +} + +func getUniqueStrategies(allocations []allocation) []gethcommon.Address { + uniqueStrategies := make(map[gethcommon.Address]struct{}) + for _, a := range allocations { + uniqueStrategies[a.StrategyAddress] = struct{}{} + } + strategies := make([]gethcommon.Address, 0, len(uniqueStrategies)) + for s := range uniqueStrategies { + strategies = append(strategies, s) + } + return strategies +} + +func parseAllocationsCSV(filePath string) ([]allocation, error) { + var allocations []allocation + file, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer file.Close() + if err := gocsv.UnmarshalFile(file, &allocations); err != nil { + return nil, err + } + + return allocations, nil +} + +func convertAllocationsToMagnitudeAllocations( + allocations []allocation, + strategyTotalMagnitudes map[gethcommon.Address]uint64, +) []allocationmanager.IAllocationManagerTypesAllocateParams { + magnitudeAllocations := make([]allocationmanager.IAllocationManagerTypesAllocateParams, 0) + strategiesPerOperatorSetMap := make(map[allocationmanager.OperatorSet][]gethcommon.Address) + magnitudeAllocationsPerOperatorSetMap := make(map[allocationmanager.OperatorSet][]uint64) + for _, a := range allocations { + totalMag := strategyTotalMagnitudes[a.StrategyAddress] + magnitudeToUpdate := calculateMagnitudeToUpdate(totalMag, a.Bips) + + opSet := allocationmanager.OperatorSet{Avs: a.AvsAddress, Id: a.OperatorSetId} + strategies, ok := strategiesPerOperatorSetMap[opSet] + if !ok { + strategies = make([]gethcommon.Address, 0) + } + + strategies = append(strategies, a.StrategyAddress) + strategiesPerOperatorSetMap[opSet] = strategies + + magnitudes := magnitudeAllocationsPerOperatorSetMap[opSet] + magnitudes = append(magnitudes, magnitudeToUpdate) + magnitudeAllocationsPerOperatorSetMap[opSet] = magnitudes + } + + for opSet, strategies := range strategiesPerOperatorSetMap { + magnitudeAllocations = append( + magnitudeAllocations, + allocationmanager.IAllocationManagerTypesAllocateParams{ + OperatorSet: opSet, + Strategies: strategies, + NewMagnitudes: magnitudeAllocationsPerOperatorSetMap[opSet], + }, + ) + } + + return magnitudeAllocations +} + +func calculateMagnitudeToUpdate(totalMagnitude uint64, bipsToAllocate uint64) uint64 { + bigMagnitude := big.NewInt(int64(totalMagnitude)) + bigBipsToAllocate := big.NewInt(int64(bipsToAllocate)) + bigBipsMultiplier := big.NewInt(10_000) + bigMagnitudeToUpdate := bigMagnitude.Mul(bigMagnitude, bigBipsToAllocate).Div(bigMagnitude, bigBipsMultiplier) + return bigMagnitudeToUpdate.Uint64() +} + +func readAndValidateUpdateFlags(cCtx *cli.Context, logger logging.Logger) (*updateConfig, error) { + network := cCtx.String(flags.NetworkFlag.Name) + environment := cCtx.String(flags.EnvironmentFlag.Name) + logger.Debugf("Using network %s and environment: %s", network, environment) + + rpcUrl := cCtx.String(flags.ETHRpcUrlFlag.Name) + output := cCtx.String(flags.OutputFileFlag.Name) + outputType := cCtx.String(flags.OutputTypeFlag.Name) + broadcast := cCtx.Bool(flags.BroadcastFlag.Name) + isSilent := cCtx.Bool(flags.SilentFlag.Name) + + operatorAddress := gethcommon.HexToAddress(cCtx.String(flags.OperatorAddressFlag.Name)) + avsAddress := gethcommon.HexToAddress(cCtx.String(flags.AVSAddressFlag.Name)) + strategyAddress := gethcommon.HexToAddress(cCtx.String(flags.StrategyAddressFlag.Name)) + operatorSetId := uint32(cCtx.Uint64(flags.OperatorSetIdFlag.Name)) + bipsToAllocate := cCtx.Uint64(BipsToAllocateFlag.Name) + logger.Debugf( + "Operator address: %s, AVS address: %s, Strategy address: %s, Bips to allocate: %d", + operatorAddress.Hex(), + avsAddress.Hex(), + strategyAddress.Hex(), + bipsToAllocate, + ) + + // Get signerConfig + signerConfig, err := common.GetSignerConfig(cCtx, logger) + if err != nil { + // We don't want to throw error since people can still use it to generate the claim + // without broadcasting it + logger.Debugf("Failed to get signer config: %s", err) + } + + csvFilePath := cCtx.String(flags.CSVFileFlag.Name) + chainId := utils.NetworkNameToChainId(network) + + delegationManagerAddress := cCtx.String(flags.DelegationManagerAddressFlag.Name) + if delegationManagerAddress == "" { + delegationManagerAddress, err = common.GetDelegationManagerAddress(chainId) + if err != nil { + return nil, err + } + } + + return &updateConfig{ + network: network, + rpcUrl: rpcUrl, + environment: environment, + output: output, + outputType: outputType, + broadcast: broadcast, + operatorAddress: operatorAddress, + avsAddress: avsAddress, + strategyAddress: strategyAddress, + bipsToAllocate: bipsToAllocate, + signerConfig: signerConfig, + csvFilePath: csvFilePath, + operatorSetId: operatorSetId, + chainID: chainId, + delegationManagerAddress: gethcommon.HexToAddress(delegationManagerAddress), + isSilent: isSilent, + }, nil +} diff --git a/pkg/operator/allocations/update_test.go b/pkg/operator/allocations/update_test.go new file mode 100644 index 00000000..a97dd217 --- /dev/null +++ b/pkg/operator/allocations/update_test.go @@ -0,0 +1,275 @@ +package allocations + +import ( + "context" + "errors" + "math" + "os" + "testing" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/testutils" + + allocationmanager "github.com/Layr-Labs/eigensdk-go/contracts/bindings/AllocationManager" + "github.com/Layr-Labs/eigensdk-go/logging" + + gethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/stretchr/testify/assert" +) + +const ( + initialMagnitude = 1e18 +) + +type fakeElChainReader struct { + // operator --> strategy --> magnitude + allocatableMagnitudeMap map[gethcommon.Address]map[gethcommon.Address]uint64 + totalMagnitudeMap map[gethcommon.Address]map[gethcommon.Address]uint64 +} + +func newFakeElChainReader( + allocatableMagnitudeMap map[gethcommon.Address]map[gethcommon.Address]uint64, + totalMagnitudeMap map[gethcommon.Address]map[gethcommon.Address]uint64, +) *fakeElChainReader { + return &fakeElChainReader{ + allocatableMagnitudeMap: allocatableMagnitudeMap, + totalMagnitudeMap: totalMagnitudeMap, + } +} + +func (f *fakeElChainReader) GetMaxMagnitudes( + ctx context.Context, + operator gethcommon.Address, + strategyAddresses []gethcommon.Address, +) ([]uint64, error) { + stratMap, ok := f.totalMagnitudeMap[operator] + if !ok { + return []uint64{}, errors.New("operator not found") + } + + // iterate over strategyAddresses and return the corresponding magnitudes + magnitudes := make([]uint64, 0, len(strategyAddresses)) + for _, strategy := range strategyAddresses { + magnitude, ok := stratMap[strategy] + if !ok { + magnitude = 0 + } + magnitudes = append(magnitudes, magnitude) + } + return magnitudes, nil +} + +func (f *fakeElChainReader) GetAllocatableMagnitude( + ctx context.Context, + operator gethcommon.Address, + strategy gethcommon.Address, +) (uint64, error) { + stratMap, ok := f.allocatableMagnitudeMap[operator] + if !ok { + return initialMagnitude, nil + } + + magnitude, ok := stratMap[strategy] + if !ok { + return initialMagnitude, nil + } + return magnitude, nil +} + +func TestGenerateAllocationsParams(t *testing.T) { + avsAddress := testutils.GenerateRandomEthereumAddressString() + strategyAddress := testutils.GenerateRandomEthereumAddressString() + operatorAddress := testutils.GenerateRandomEthereumAddressString() + tests := []struct { + name string + config *updateConfig + expectError bool + expectedAllocations *BulkModifyAllocations + }{ + { + name: "simple single allocation without csv", + config: &updateConfig{ + operatorAddress: gethcommon.HexToAddress("0x2222AAC0C980Cc029624b7ff55B88Bc6F63C538f"), + avsAddress: gethcommon.HexToAddress(avsAddress), + strategyAddress: gethcommon.HexToAddress(strategyAddress), + bipsToAllocate: 1000, + operatorSetId: 1, + }, + expectError: false, + expectedAllocations: &BulkModifyAllocations{ + Allocations: []allocationmanager.IAllocationManagerTypesAllocateParams{ + { + Strategies: []gethcommon.Address{gethcommon.HexToAddress(strategyAddress)}, + OperatorSet: allocationmanager.OperatorSet{ + Avs: gethcommon.HexToAddress(avsAddress), + Id: 1, + }, + NewMagnitudes: []uint64{1e17}, + }, + }, + }, + }, + { + name: "csv file allocations1.csv", + config: &updateConfig{ + csvFilePath: "testdata/allocations1.csv", + operatorAddress: gethcommon.HexToAddress(operatorAddress), + }, + expectError: false, + expectedAllocations: &BulkModifyAllocations{ + Allocations: []allocationmanager.IAllocationManagerTypesAllocateParams{ + { + Strategies: []gethcommon.Address{ + gethcommon.HexToAddress("0x49989b32351Eb9b8ab2d5623cF22E7F7C23e5630"), + }, + OperatorSet: allocationmanager.OperatorSet{ + Avs: gethcommon.HexToAddress("0x2222AAC0C980Cc029624b7ff55B88Bc6F63C538f"), + Id: 1, + }, + NewMagnitudes: []uint64{2e17}, + }, + { + Strategies: []gethcommon.Address{ + gethcommon.HexToAddress("0x49989b32351Eb9b8ab2d5623cF22E7F7C23e5630"), + }, + OperatorSet: allocationmanager.OperatorSet{ + Avs: gethcommon.HexToAddress("0x2222AAC0C980Cc029624b7ff55B88Bc6F63C538f"), + Id: 3, + }, + NewMagnitudes: []uint64{1e17}, + }, + { + Strategies: []gethcommon.Address{ + gethcommon.HexToAddress("0x232326fE4F8C2f83E3eB2318F090557b7CD02222"), + }, + OperatorSet: allocationmanager.OperatorSet{ + Avs: gethcommon.HexToAddress("0x111116fE4F8C2f83E3eB2318F090557b7CD0BF76"), + Id: 4, + }, + NewMagnitudes: []uint64{3e17}, + }, + { + Strategies: []gethcommon.Address{ + gethcommon.HexToAddress("0x545456fE4F8C2f83E3eB2318F090557b7CD04567"), + }, + OperatorSet: allocationmanager.OperatorSet{ + Avs: gethcommon.HexToAddress("0x111116fE4F8C2f83E3eB2318F090557b7CD0BF76"), + Id: 5, + }, + NewMagnitudes: []uint64{4e17}, + }, + }, + AllocatableMagnitudes: map[gethcommon.Address]uint64{ + gethcommon.HexToAddress("0x49989b32351Eb9b8ab2d5623cF22E7F7C23e5630"): initialMagnitude, + gethcommon.HexToAddress("0x232326fE4F8C2f83E3eB2318F090557b7CD02222"): initialMagnitude, + gethcommon.HexToAddress("0x545456fE4F8C2f83E3eB2318F090557b7CD04567"): initialMagnitude, + }, + }, + }, + { + name: "csv file allocations_duplicate.csv", + config: &updateConfig{ + csvFilePath: "testdata/allocations_duplicate.csv", + operatorAddress: gethcommon.HexToAddress(operatorAddress), + }, + expectError: true, + }, + } + + elReader := newFakeElChainReader( + map[gethcommon.Address]map[gethcommon.Address]uint64{ + gethcommon.HexToAddress("0x2222AAC0C980Cc029624b7ff55B88Bc6F63C538f"): { + gethcommon.HexToAddress(strategyAddress): initialMagnitude, + }, + gethcommon.HexToAddress(operatorAddress): { + gethcommon.HexToAddress("0x49989b32351Eb9b8ab2d5623cF22E7F7C23e5630"): initialMagnitude, + gethcommon.HexToAddress("0x111116fE4F8C2f83E3eB2318F090557b7CD0BF76"): initialMagnitude, + gethcommon.HexToAddress("0x545456fE4F8C2f83E3eB2318F090557b7CD04567"): initialMagnitude, + }, + }, + map[gethcommon.Address]map[gethcommon.Address]uint64{ + gethcommon.HexToAddress("0x2222AAC0C980Cc029624b7ff55B88Bc6F63C538f"): { + gethcommon.HexToAddress(strategyAddress): initialMagnitude, + }, + gethcommon.HexToAddress(operatorAddress): { + gethcommon.HexToAddress("0x49989b32351Eb9b8ab2d5623cF22E7F7C23e5630"): initialMagnitude, + gethcommon.HexToAddress("0x111116fE4F8C2f83E3eB2318F090557b7CD0BF76"): initialMagnitude, + gethcommon.HexToAddress("0x545456fE4F8C2f83E3eB2318F090557b7CD04567"): initialMagnitude, + gethcommon.HexToAddress("0x232326fE4F8C2f83E3eB2318F090557b7CD02222"): initialMagnitude, + }, + }, + ) + + logger := logging.NewTextSLogger(os.Stdout, &logging.SLoggerOptions{}) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + allocations, err := generateAllocationsParams(context.Background(), elReader, tt.config, logger) + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedAllocations, allocations) + } + }) + } +} + +func TestCalculateMagnitudeToUpdate(t *testing.T) { + tests := []struct { + name string + totalMagnitude uint64 + bipsToAllocate uint64 + expectedMagnitude uint64 + }{ + { + name: "Valid inputs", + totalMagnitude: 1e18, + bipsToAllocate: 1000, + expectedMagnitude: 1e17, + }, + { + name: "Zero total magnitude", + totalMagnitude: 0, + bipsToAllocate: 1000, + expectedMagnitude: 0, + }, + { + name: "Zero bips to allocate", + totalMagnitude: 1e18, + bipsToAllocate: 0, + expectedMagnitude: 0, + }, + { + name: "Max uint64 values", + totalMagnitude: math.MaxUint64, + bipsToAllocate: math.MaxUint64, + expectedMagnitude: 0, // Result of overflow + }, + { + name: "Valid inputs 1", + totalMagnitude: 1e18, + bipsToAllocate: 2555, + expectedMagnitude: 2555e14, + }, + { + name: "Valid inputs 2", + totalMagnitude: 1e18, + bipsToAllocate: 313, + expectedMagnitude: 313e14, + }, + { + name: "Valid inputs 3", + totalMagnitude: 1e18, + bipsToAllocate: 3, + expectedMagnitude: 3e14, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := calculateMagnitudeToUpdate(tt.totalMagnitude, tt.bipsToAllocate) + assert.Equal(t, tt.expectedMagnitude, result) + }) + } +} diff --git a/pkg/operator/config/create.go b/pkg/operator/config/create.go index f07e308c..ee12af0c 100644 --- a/pkg/operator/config/create.go +++ b/pkg/operator/config/create.go @@ -6,6 +6,7 @@ import ( "fmt" "math/big" "os" + "strconv" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" @@ -158,6 +159,39 @@ func promptOperatorInfo(config *types.OperatorConfig, p utils.Prompter) (types.O } config.EthRPCUrl = rpcUrl + // Prompt for allocation delay + allocationDelay, err := p.InputInteger( + "Enter your allocation delay (in seconds, default is 17.5 days):", + "1512000", + "", + func(i int64) error { + if i < 0 { + return errors.New("allocation delay should be non-negative") + } + return nil + }, + ) + if err != nil { + return types.OperatorConfig{}, err + } + + // confirm again + confirm, err := p.Confirm( + "Are you sure you want to set the allocation delay to " + strconv.FormatInt( + allocationDelay, + 10, + ) + " seconds? This cannot be changed once set.", + ) + if err != nil { + return types.OperatorConfig{}, err + } + + if confirm { + config.Operator.AllocationDelay = uint32(allocationDelay) + } else { + return types.OperatorConfig{}, errors.New("operator cancelled") + } + // Prompt for network & set chainId chainId, err := p.Select("Select your network:", []string{"mainnet", "holesky", "local"}) if err != nil { diff --git a/pkg/operator/deregister_operator_sets.go b/pkg/operator/deregister_operator_sets.go new file mode 100644 index 00000000..7e908112 --- /dev/null +++ b/pkg/operator/deregister_operator_sets.go @@ -0,0 +1,237 @@ +package operator + +import ( + "fmt" + "math" + "strings" + + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + allocationmanager "github.com/Layr-Labs/eigensdk-go/contracts/bindings/AllocationManager" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + + "github.com/urfave/cli/v2" +) + +func DeregisterCommand(p utils.Prompter) *cli.Command { + getDeregisterCmd := &cli.Command{ + Name: "deregister-operator-sets", + Usage: "Deregister operator from specified operator sets", + UsageText: "deregister-operator-sets [flags]", + Description: ` +Deregister operator from operator sets. +This command doesn't automatically deallocate your slashable stake from that operator set so you will have to use the 'operator allocations update' command to deallocate your stake from the operator set. + +To find what operator set you are part of, use the 'eigenlayer operator allocations show' command. + +`, + Flags: getDeregistrationFlags(), + After: telemetry.AfterRunAction(), + Action: func(context *cli.Context) error { + return deregisterAction(context, p) + }, + } + return getDeregisterCmd +} + +func deregisterAction(cCtx *cli.Context, p utils.Prompter) error { + ctx := cCtx.Context + logger := common.GetLogger(cCtx) + + config, err := readAndValidateDeregisterConfig(cCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError(err, "failed to read and validate deregister config") + } + + cCtx.App.Metadata["network"] = config.chainID.String() + + ethClient, err := ethclient.Dial(config.rpcUrl) + if err != nil { + return eigenSdkUtils.WrapError("failed to create new eth client", err) + } + + if config.broadcast { + if config.signerConfig == nil { + return fmt.Errorf("signer config is required to broadcast the transaction") + } + logger.Info("Signing and broadcasting deregistration transaction") + eLWriter, err := common.GetELWriter( + config.operatorAddress, + config.signerConfig, + ethClient, + elcontracts.Config{ + DelegationManagerAddress: config.delegationManagerAddress, + }, + p, + config.chainID, + logger, + ) + if err != nil { + return eigenSdkUtils.WrapError("failed to get EL writer", err) + } + receipt, err := eLWriter.DeregisterFromOperatorSets( + ctx, + config.operatorAddress, + elcontracts.DeregistrationRequest{ + AVSAddress: config.avsAddress, + OperatorSetIds: config.operatorSetIds, + WaitForReceipt: true, + }) + if err != nil { + return eigenSdkUtils.WrapError("failed to deregister from operator sets", err) + } + common.PrintTransactionInfo(receipt.TxHash.String(), config.chainID) + } else { + noSendTxOpts := common.GetNoSendTxOpts(config.operatorAddress) + _, _, contractBindings, err := elcontracts.BuildClients(elcontracts.Config{ + DelegationManagerAddress: config.delegationManagerAddress, + }, ethClient, nil, logger, nil) + if err != nil { + return err + } + // If operator is a smart contract, we can't estimate gas using geth + // since balance of contract can be 0, as it can be called by an EOA + // to claim. So we hardcode the gas limit to 150_000 so that we can + // create unsigned tx without gas limit estimation from contract bindings + if common.IsSmartContractAddress(config.operatorAddress, ethClient) { + // address is a smart contract + noSendTxOpts.GasLimit = 150_000 + } + unsignedTx, err := contractBindings.AllocationManager.DeregisterFromOperatorSets( + noSendTxOpts, + allocationmanager.IAllocationManagerTypesDeregisterParams{ + Operator: config.operatorAddress, + Avs: config.avsAddress, + OperatorSetIds: config.operatorSetIds, + }, + ) + 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.output) { + err = common.WriteToFile([]byte(calldataHex), config.output) + if err != nil { + return err + } + logger.Infof("Call data written to file: %s", config.output) + } else { + fmt.Println(calldataHex) + } + } else { + if !common.IsEmptyString(config.output) { + fmt.Println("output file not supported for pretty output type") + fmt.Println() + } + fmt.Println() + fmt.Println("Deregitering from operator sets: ", config.operatorSetIds) + } + if !config.isSilent { + txFeeDetails := common.GetTxFeeDetails(unsignedTx) + fmt.Println() + txFeeDetails.Print() + fmt.Println("To broadcast the transaction, use the --broadcast flag") + fmt.Println() + + msg1 := "| NOTE: This command doesn't automatically deallocate your slashable stake from that operator set." + msg2 := "| You will have to use the 'eigenlayer operator allocations update' command to deallocate your stake from the operator set." + width := int(math.Max(float64(len(msg1)), float64(len(msg2))) + 1) + fmt.Println("+" + strings.Repeat("-", width) + "+") + fmt.Println(msg1 + strings.Repeat(" ", width-len(msg1)) + " |") + fmt.Println(msg2 + strings.Repeat(" ", width-len(msg2)) + " |") + fmt.Println("+" + strings.Repeat("-", width) + "+") + } + + } + return nil +} + +func readAndValidateDeregisterConfig(cCtx *cli.Context, logger logging.Logger) (*DeregisterConfig, error) { + network := cCtx.String(flags.NetworkFlag.Name) + environment := cCtx.String(flags.EnvironmentFlag.Name) + logger.Debugf("Using network %s and environment: %s", network, environment) + + rpcUrl := cCtx.String(flags.ETHRpcUrlFlag.Name) + output := cCtx.String(flags.OutputFileFlag.Name) + outputType := cCtx.String(flags.OutputTypeFlag.Name) + broadcast := cCtx.Bool(flags.BroadcastFlag.Name) + isSilent := cCtx.Bool(flags.SilentFlag.Name) + + operatorAddress := gethcommon.HexToAddress(cCtx.String(flags.OperatorAddressFlag.Name)) + avsAddress := gethcommon.HexToAddress(cCtx.String(flags.AVSAddressFlag.Name)) + + // Get signerConfig + signerConfig, err := common.GetSignerConfig(cCtx, logger) + if err != nil { + // We don't want to throw error since people can still use it to generate the claim + // without broadcasting it + logger.Debugf("Failed to get signer config: %s", err) + } + + chainId := utils.NetworkNameToChainId(network) + + delegationManagerAddress := cCtx.String(flags.DelegationManagerAddressFlag.Name) + if delegationManagerAddress == "" { + delegationManagerAddress, err = common.GetDelegationManagerAddress(chainId) + if err != nil { + return nil, err + } + } + + operatorSetIdsString := cCtx.Uint64Slice(flags.OperatorSetIdsFlag.Name) + operatorSetIds := make([]uint32, len(operatorSetIdsString)) + for i, id := range operatorSetIdsString { + operatorSetIds[i] = uint32(id) + } + + config := &DeregisterConfig{ + avsAddress: avsAddress, + operatorSetIds: operatorSetIds, + operatorAddress: operatorAddress, + network: network, + environment: environment, + broadcast: broadcast, + rpcUrl: rpcUrl, + chainID: chainId, + signerConfig: signerConfig, + output: output, + outputType: outputType, + delegationManagerAddress: gethcommon.HexToAddress(delegationManagerAddress), + isSilent: isSilent, + } + + return config, nil +} + +func getDeregistrationFlags() []cli.Flag { + baseFlags := []cli.Flag{ + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + &flags.OutputFileFlag, + &flags.OutputTypeFlag, + &flags.BroadcastFlag, + &flags.VerboseFlag, + &flags.AVSAddressFlag, + &flags.OperatorAddressFlag, + &flags.OperatorSetIdsFlag, + &flags.DelegationManagerAddressFlag, + &flags.SilentFlag, + } + + allFlags := append(baseFlags, flags.GetSignerFlags()...) + sort.Sort(cli.FlagsByName(allFlags)) + return allFlags +} diff --git a/pkg/operator/register.go b/pkg/operator/register.go index c202e082..1b47d3b2 100644 --- a/pkg/operator/register.go +++ b/pkg/operator/register.go @@ -44,6 +44,7 @@ func RegisterCmd(p utils.Prompter) *cli.Command { configurationFilePath := args.Get(0) operatorCfg, err := common.ValidateAndReturnConfig(configurationFilePath, logger) + logger.Debugf("operatorCfg: %v", operatorCfg) if err != nil { return err } diff --git a/pkg/operator/register_operator_sets.go b/pkg/operator/register_operator_sets.go new file mode 100644 index 00000000..a4b76e28 --- /dev/null +++ b/pkg/operator/register_operator_sets.go @@ -0,0 +1,223 @@ +package operator + +import ( + "fmt" + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + allocationmanager "github.com/Layr-Labs/eigensdk-go/contracts/bindings/AllocationManager" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + + "github.com/urfave/cli/v2" +) + +func RegisterOperatorSetsCommand(p utils.Prompter) *cli.Command { + registerOperatorSetsCmd := &cli.Command{ + Name: "register-operator-sets", + Usage: "register operator from specified operator sets", + UsageText: "register-operator-sets [flags]", + Description: ` +register operator sets for operator. + +To find what operator set you are registered for, use the 'eigenlayer operator allocations show' command. + +`, + Flags: getRegistrationFlags(), + After: telemetry.AfterRunAction(), + Action: func(context *cli.Context) error { + return registerOperatorSetsAction(context, p) + }, + } + return registerOperatorSetsCmd +} + +func registerOperatorSetsAction(cCtx *cli.Context, p utils.Prompter) error { + ctx := cCtx.Context + logger := common.GetLogger(cCtx) + + config, err := readAndValidateRegisterOperatorSetsConfig(cCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError(err, "failed to read and validate register config") + } + + cCtx.App.Metadata["network"] = config.chainID.String() + + ethClient, err := ethclient.Dial(config.rpcUrl) + if err != nil { + return eigenSdkUtils.WrapError("failed to create new eth client", err) + } + + if config.broadcast { + if config.signerConfig == nil { + return fmt.Errorf("signer config is required to broadcast the transaction") + } + logger.Info("Signing and broadcasting registration transaction") + eLWriter, err := common.GetELWriter( + config.operatorAddress, + config.signerConfig, + ethClient, + elcontracts.Config{ + DelegationManagerAddress: config.delegationManagerAddress, + }, + p, + config.chainID, + logger, + ) + if err != nil { + return eigenSdkUtils.WrapError("failed to get EL writer", err) + } + receipt, err := eLWriter.RegisterForOperatorSets( + ctx, + elcontracts.RegistrationRequest{ + AVSAddress: config.avsAddress, + OperatorSetIds: config.operatorSetIds, + WaitForReceipt: true, + }) + if err != nil { + return eigenSdkUtils.WrapError("failed to deregister from operator sets", err) + } + common.PrintTransactionInfo(receipt.TxHash.String(), config.chainID) + } else { + noSendTxOpts := common.GetNoSendTxOpts(config.operatorAddress) + _, _, contractBindings, err := elcontracts.BuildClients(elcontracts.Config{ + DelegationManagerAddress: config.delegationManagerAddress, + }, ethClient, nil, logger, nil) + if err != nil { + return err + } + // If operator is a smart contract, we can't estimate gas using geth + // since balance of contract can be 0, as it can be called by an EOA + // to claim. So we hardcode the gas limit to 150_000 so that we can + // create unsigned tx without gas limit estimation from contract bindings + if common.IsSmartContractAddress(config.operatorAddress, ethClient) { + // address is a smart contract + noSendTxOpts.GasLimit = 150_000 + } + unsignedTx, err := contractBindings.AllocationManager.RegisterForOperatorSets( + noSendTxOpts, + config.operatorAddress, + allocationmanager.IAllocationManagerTypesRegisterParams{ + Avs: config.avsAddress, + OperatorSetIds: config.operatorSetIds, + }, + ) + 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.output) { + err = common.WriteToFile([]byte(calldataHex), config.output) + if err != nil { + return err + } + logger.Infof("Call data written to file: %s", config.output) + } else { + fmt.Println(calldataHex) + } + } else { + if !common.IsEmptyString(config.output) { + fmt.Println("output file not supported for pretty output type") + fmt.Println() + } + fmt.Println() + fmt.Println("Registering from operator sets: ", config.operatorSetIds) + } + if !config.isSilent { + txFeeDetails := common.GetTxFeeDetails(unsignedTx) + fmt.Println() + txFeeDetails.Print() + fmt.Println("To broadcast the transaction, use the --broadcast flag") + } + + } + return nil +} + +func readAndValidateRegisterOperatorSetsConfig(cCtx *cli.Context, logger logging.Logger) (*RegisterConfig, error) { + network := cCtx.String(flags.NetworkFlag.Name) + environment := cCtx.String(flags.EnvironmentFlag.Name) + logger.Debugf("Using network %s and environment: %s", network, environment) + + rpcUrl := cCtx.String(flags.ETHRpcUrlFlag.Name) + output := cCtx.String(flags.OutputFileFlag.Name) + outputType := cCtx.String(flags.OutputTypeFlag.Name) + broadcast := cCtx.Bool(flags.BroadcastFlag.Name) + isSilent := cCtx.Bool(flags.SilentFlag.Name) + + operatorAddress := gethcommon.HexToAddress(cCtx.String(flags.OperatorAddressFlag.Name)) + avsAddress := gethcommon.HexToAddress(cCtx.String(flags.AVSAddressFlag.Name)) + + // Get signerConfig + signerConfig, err := common.GetSignerConfig(cCtx, logger) + if err != nil { + // We don't want to throw error since people can still use it to generate the claim + // without broadcasting it + logger.Debugf("Failed to get signer config: %s", err) + } + + chainId := utils.NetworkNameToChainId(network) + + delegationManagerAddress := cCtx.String(flags.DelegationManagerAddressFlag.Name) + if delegationManagerAddress == "" { + delegationManagerAddress, err = common.GetDelegationManagerAddress(chainId) + if err != nil { + return nil, err + } + } + + operatorSetIdsString := cCtx.Uint64Slice(flags.OperatorSetIdsFlag.Name) + operatorSetIds := make([]uint32, len(operatorSetIdsString)) + for i, id := range operatorSetIdsString { + operatorSetIds[i] = uint32(id) + } + + config := &RegisterConfig{ + avsAddress: avsAddress, + operatorSetIds: operatorSetIds, + operatorAddress: operatorAddress, + network: network, + environment: environment, + broadcast: broadcast, + rpcUrl: rpcUrl, + chainID: chainId, + signerConfig: signerConfig, + output: output, + outputType: outputType, + delegationManagerAddress: gethcommon.HexToAddress(delegationManagerAddress), + isSilent: isSilent, + } + + return config, nil +} + +func getRegistrationFlags() []cli.Flag { + baseFlags := []cli.Flag{ + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + &flags.OutputFileFlag, + &flags.OutputTypeFlag, + &flags.BroadcastFlag, + &flags.VerboseFlag, + &flags.AVSAddressFlag, + &flags.OperatorAddressFlag, + &flags.OperatorSetIdsFlag, + &flags.DelegationManagerAddressFlag, + &flags.SilentFlag, + } + + allFlags := append(baseFlags, flags.GetSignerFlags()...) + sort.Sort(cli.FlagsByName(allFlags)) + return allFlags +} diff --git a/pkg/operator/types.go b/pkg/operator/types.go new file mode 100644 index 00000000..2f4b4fdb --- /dev/null +++ b/pkg/operator/types.go @@ -0,0 +1,40 @@ +package operator + +import ( + "math/big" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/types" + "github.com/ethereum/go-ethereum/common" +) + +type DeregisterConfig struct { + avsAddress common.Address + operatorSetIds []uint32 + operatorAddress common.Address + network string + environment string + broadcast bool + rpcUrl string + chainID *big.Int + signerConfig *types.SignerConfig + output string + outputType string + delegationManagerAddress common.Address + isSilent bool +} + +type RegisterConfig struct { + avsAddress common.Address + operatorSetIds []uint32 + operatorAddress common.Address + network string + environment string + broadcast bool + rpcUrl string + chainID *big.Int + signerConfig *types.SignerConfig + output string + outputType string + delegationManagerAddress common.Address + isSilent bool +} diff --git a/pkg/operator/update_metadata_uri.go b/pkg/operator/update_metadata_uri.go index b8588018..ad8ce2a9 100644 --- a/pkg/operator/update_metadata_uri.go +++ b/pkg/operator/update_metadata_uri.go @@ -77,7 +77,12 @@ Requires the same file used for registration as argument return eigenSdkUtils.WrapError("failed to get EL writer", err) } - receipt, err := elWriter.UpdateMetadataURI(context.Background(), operatorCfg.Operator.MetadataUrl, true) + receipt, err := elWriter.UpdateMetadataURI( + context.Background(), + gethcommon.HexToAddress(operatorCfg.Operator.Address), + operatorCfg.Operator.MetadataUrl, + true, + ) if err != nil { fmt.Printf("%s Error while updating operator metadata uri\n", utils.EmojiCrossMark) return err diff --git a/pkg/rewards/claim.go b/pkg/rewards/claim.go index c50a5127..570c9a0a 100644 --- a/pkg/rewards/claim.go +++ b/pkg/rewards/claim.go @@ -44,7 +44,7 @@ type elChainReader interface { GetRootIndexFromHash(ctx context.Context, hash [32]byte) (uint32, error) GetCurrentClaimableDistributionRoot( ctx context.Context, - ) (rewardscoordinator.IRewardsCoordinatorDistributionRoot, error) + ) (rewardscoordinator.IRewardsCoordinatorTypesDistributionRoot, error) CurrRewardsCalculationEndTimestamp(ctx context.Context) (uint32, error) GetCumulativeClaimed(ctx context.Context, earnerAddress, tokenAddress gethcommon.Address) (*big.Int, error) } @@ -114,7 +114,7 @@ func batchClaim( return eigenSdkUtils.WrapError("failed to parse YAML config", err) } - var elClaims []rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim + var elClaims []rewardscoordinator.IRewardsCoordinatorTypesRewardsMerkleClaim var claims []contractrewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim var accounts []merkletree.MerkleTree @@ -163,7 +163,7 @@ func generateClaimPayload( logger logging.Logger, earnerAddress gethcommon.Address, tokenAddresses []gethcommon.Address, -) (*rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim, *contractrewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim, *merkletree.MerkleTree, error) { +) (*rewardscoordinator.IRewardsCoordinatorTypesRewardsMerkleClaim, *contractrewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim, *merkletree.MerkleTree, error) { claimableTokensOrderMap, present := proofData.Distribution.GetTokensForEarner(earnerAddress) if !present { @@ -187,11 +187,11 @@ func generateClaimPayload( return nil, nil, nil, eigenSdkUtils.WrapError("failed to generate claim proof for earner", err) } - elClaim := rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim{ + elClaim := rewardscoordinator.IRewardsCoordinatorTypesRewardsMerkleClaim{ RootIndex: claim.RootIndex, EarnerIndex: claim.EarnerIndex, EarnerTreeProof: claim.EarnerTreeProof, - EarnerLeaf: rewardscoordinator.IRewardsCoordinatorEarnerTreeMerkleLeaf{ + EarnerLeaf: rewardscoordinator.IRewardsCoordinatorTypesEarnerTreeMerkleLeaf{ Earner: claim.EarnerLeaf.Earner, EarnerTokenRoot: claim.EarnerLeaf.EarnerTokenRoot, }, @@ -279,7 +279,7 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error { return err } - elClaims := []rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim{*elClaim} + elClaims := []rewardscoordinator.IRewardsCoordinatorTypesRewardsMerkleClaim{*elClaim} claims := []contractrewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim{*claim} accounts := []merkletree.MerkleTree{*account} err = broadcastClaims(config, ethClient, logger, p, ctx, elClaims, claims, accounts) @@ -293,7 +293,7 @@ func broadcastClaims( logger logging.Logger, p utils.Prompter, ctx context.Context, - elClaims []rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim, + elClaims []rewardscoordinator.IRewardsCoordinatorTypesRewardsMerkleClaim, claims []contractrewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim, accounts []merkletree.MerkleTree, ) error { @@ -346,11 +346,7 @@ func broadcastClaims( // since balance of contract can be 0, as it can be called by an EOA // to claim. So we hardcode the gas limit to 150_000 so that we can // create unsigned tx without gas limit estimation from contract bindings - code, err := ethClient.CodeAt(ctx, config.ClaimerAddress, nil) - if err != nil { - return eigenSdkUtils.WrapError("failed to get code at address", err) - } - if len(code) > 0 { + if common.IsSmartContractAddress(config.ClaimerAddress, ethClient) { // Claimer is a smart contract noSendTxOpts.GasLimit = 150_000 } @@ -528,10 +524,10 @@ func filterClaimableTokenAddresses( func convertClaimTokenLeaves( claimTokenLeaves []contractrewardscoordinator.IRewardsCoordinatorTokenTreeMerkleLeaf, -) []rewardscoordinator.IRewardsCoordinatorTokenTreeMerkleLeaf { - var tokenLeaves []rewardscoordinator.IRewardsCoordinatorTokenTreeMerkleLeaf +) []rewardscoordinator.IRewardsCoordinatorTypesTokenTreeMerkleLeaf { + var tokenLeaves []rewardscoordinator.IRewardsCoordinatorTypesTokenTreeMerkleLeaf for _, claimTokenLeaf := range claimTokenLeaves { - tokenLeaves = append(tokenLeaves, rewardscoordinator.IRewardsCoordinatorTokenTreeMerkleLeaf{ + tokenLeaves = append(tokenLeaves, rewardscoordinator.IRewardsCoordinatorTypesTokenTreeMerkleLeaf{ Token: claimTokenLeaf.Token, CumulativeEarnings: claimTokenLeaf.CumulativeEarnings, }) diff --git a/pkg/rewards/claim_test.go b/pkg/rewards/claim_test.go index 6d9f9e81..e2fd3a4a 100644 --- a/pkg/rewards/claim_test.go +++ b/pkg/rewards/claim_test.go @@ -27,7 +27,7 @@ import ( ) type fakeELReader struct { - roots []rewardscoordinator.IRewardsCoordinatorDistributionRoot + roots []rewardscoordinator.IRewardsCoordinatorTypesDistributionRoot earnerTokenClaimedMap map[common.Address]map[common.Address]*big.Int } @@ -35,8 +35,8 @@ func newFakeELReader( now time.Time, earnerTokenClaimedMap map[common.Address]map[common.Address]*big.Int, ) *fakeELReader { - roots := make([]rewardscoordinator.IRewardsCoordinatorDistributionRoot, 0) - rootOne := rewardscoordinator.IRewardsCoordinatorDistributionRoot{ + roots := make([]rewardscoordinator.IRewardsCoordinatorTypesDistributionRoot, 0) + rootOne := rewardscoordinator.IRewardsCoordinatorTypesDistributionRoot{ Root: [32]byte{0x01}, RewardsCalculationEndTimestamp: uint32(now.Add(-time.Hour).Unix()), ActivatedAt: uint32(now.Add(time.Hour).Unix()), @@ -44,14 +44,14 @@ func newFakeELReader( } // This is the current claimable distribution root - rootTwo := rewardscoordinator.IRewardsCoordinatorDistributionRoot{ + rootTwo := rewardscoordinator.IRewardsCoordinatorTypesDistributionRoot{ Root: [32]byte{0x02}, RewardsCalculationEndTimestamp: uint32(now.Add(48 * -time.Hour).Unix()), ActivatedAt: uint32(now.Add(-24 * time.Hour).Unix()), Disabled: false, } - rootThree := rewardscoordinator.IRewardsCoordinatorDistributionRoot{ + rootThree := rewardscoordinator.IRewardsCoordinatorTypesDistributionRoot{ Root: [32]byte{0x03}, RewardsCalculationEndTimestamp: uint32(now.Add(32 * -time.Hour).Unix()), ActivatedAt: uint32(now.Add(-12 * time.Minute).Unix()), @@ -84,7 +84,7 @@ func (f *fakeELReader) GetRootIndexFromHash(ctx context.Context, hash [32]byte) func (f *fakeELReader) GetCurrentClaimableDistributionRoot( ctx context.Context, -) (rewardscoordinator.IRewardsCoordinatorDistributionRoot, error) { +) (rewardscoordinator.IRewardsCoordinatorTypesDistributionRoot, error) { // iterate from end to start since we want the latest active root // and the roots are sorted in ascending order of activation time for i := len(f.roots) - 1; i >= 0; i-- { @@ -93,7 +93,9 @@ func (f *fakeELReader) GetCurrentClaimableDistributionRoot( } } - return rewardscoordinator.IRewardsCoordinatorDistributionRoot{}, errors.New("no active distribution root found") + return rewardscoordinator.IRewardsCoordinatorTypesDistributionRoot{}, errors.New( + "no active distribution root found", + ) } func (f *fakeELReader) GetCumulativeClaimed( diff --git a/pkg/types/chain_metadata.go b/pkg/types/chain_metadata.go index 531ad353..f59f3538 100644 --- a/pkg/types/chain_metadata.go +++ b/pkg/types/chain_metadata.go @@ -5,6 +5,7 @@ type ChainMetadata struct { ELDelegationManagerAddress string ELAVSDirectoryAddress string ELRewardsCoordinatorAddress string + ELPermissionManagerAddress string WebAppUrl string ProofStoreBaseURL string } diff --git a/pkg/user/admin/accept.go b/pkg/user/admin/accept.go new file mode 100644 index 00000000..9477ac92 --- /dev/null +++ b/pkg/user/admin/accept.go @@ -0,0 +1,33 @@ +package admin + +import ( + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/urfave/cli/v2" +) + +func AcceptCmd() *cli.Command { + acceptCmd := &cli.Command{ + Name: "accept-admin", + Usage: "user admin accept-admin --account-address ", + UsageText: "Accepts a user to become admin who is currently pending admin acceptance.", + Description: ` + Accepts a user to become admin who is currently pending admin acceptance. + `, + After: telemetry.AfterRunAction(), + Flags: acceptFlags(), + } + + return acceptCmd +} + +func acceptFlags() []cli.Flag { + cmdFlags := []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + } + sort.Sort(cli.FlagsByName(cmdFlags)) + return append(cmdFlags, flags.GetSignerFlags()...) +} diff --git a/pkg/user/admin/add_pending.go b/pkg/user/admin/add_pending.go new file mode 100644 index 00000000..a0b2bdd2 --- /dev/null +++ b/pkg/user/admin/add_pending.go @@ -0,0 +1,34 @@ +package admin + +import ( + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/urfave/cli/v2" +) + +func AddPendingCmd() *cli.Command { + addPendingCmd := &cli.Command{ + Name: "add-pending-admin", + Usage: "user admin add-pending-admin --account-address --admin-address ", + UsageText: "Add an admin to be pending until accepted.", + Description: ` + Add an admin to be pending until accepted. + `, + After: telemetry.AfterRunAction(), + Flags: addPendingFlags(), + } + + return addPendingCmd +} + +func addPendingFlags() []cli.Flag { + cmdFlags := []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + &AdminAddressFlag, + } + sort.Sort(cli.FlagsByName(cmdFlags)) + return append(cmdFlags, flags.GetSignerFlags()...) +} diff --git a/pkg/user/admin/admin.go b/pkg/user/admin/admin.go new file mode 100644 index 00000000..653dbc83 --- /dev/null +++ b/pkg/user/admin/admin.go @@ -0,0 +1,34 @@ +package admin + +import ( + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/urfave/cli/v2" +) + +func AdminCmd() *cli.Command { + adminCmd := &cli.Command{ + Name: "admin", + Usage: "user admin ", + UsageText: "Manage admin users.", + Description: ` + Manage admin users. + `, + After: telemetry.AfterRunAction(), + Flags: []cli.Flag{ + &flags.VerboseFlag, + }, + Subcommands: []*cli.Command{ + AcceptCmd(), + AddPendingCmd(), + IsAdminCmd(), + IsPendingCmd(), + ListCmd(), + ListPendingCmd(), + RemoveCmd(), + RemovePendingCmd(), + }, + } + + return adminCmd +} diff --git a/pkg/user/admin/flags.go b/pkg/user/admin/flags.go new file mode 100644 index 00000000..4cec13cd --- /dev/null +++ b/pkg/user/admin/flags.go @@ -0,0 +1,36 @@ +package admin + +import "github.com/urfave/cli/v2" + +var ( + AccountAddressFlag = cli.StringFlag{ + Name: "account-address", + Aliases: []string{"aa"}, + Usage: "user admin ... --account-address \"0x...\"", + EnvVars: []string{"ACCOUNT_ADDRESS"}, + } + AdminAddressFlag = cli.StringFlag{ + Name: "admin-address", + Aliases: []string{"aa"}, + Usage: "user admin ... --admin-address \"0x...\"", + EnvVars: []string{"ADMIN_ADDRESS"}, + } + CallerAddressFlag = cli.StringFlag{ + Name: "caller-address", + Aliases: []string{"ca"}, + Usage: "user admin ... --caller-address \"0x...\"", + EnvVars: []string{"CALLER_ADDRESS"}, + } + PendingAdminAddressFlag = cli.StringFlag{ + Name: "pending-admin-address", + Aliases: []string{"paa"}, + Usage: "user admin ... --pending-admin-address \"0x...\"", + EnvVars: []string{"PENDING_ADMIN_ADDRESS"}, + } + PermissionControllerAddressFlag = cli.StringFlag{ + Name: "permission-controller-address", + Aliases: []string{"pca"}, + Usage: "user admin ... --permission-controller-address \"0x...\"", + EnvVars: []string{"PERMISSION_CONTROLLER_ADDRESS"}, + } +) diff --git a/pkg/user/admin/is_admin.go b/pkg/user/admin/is_admin.go new file mode 100644 index 00000000..6bd2d385 --- /dev/null +++ b/pkg/user/admin/is_admin.go @@ -0,0 +1,26 @@ +package admin + +import ( + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/urfave/cli/v2" +) + +func IsAdminCmd() *cli.Command { + isAdmin := &cli.Command{ + Name: "is-admin", + Usage: "user admin is-admin --account-address --caller-address ", + UsageText: "Checks if a user is an admin.", + Description: ` + Checks if a user is an admin. + `, + After: telemetry.AfterRunAction(), + Flags: []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + &CallerAddressFlag, + }, + } + + return isAdmin +} diff --git a/pkg/user/admin/is_pending.go b/pkg/user/admin/is_pending.go new file mode 100644 index 00000000..72661139 --- /dev/null +++ b/pkg/user/admin/is_pending.go @@ -0,0 +1,26 @@ +package admin + +import ( + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/urfave/cli/v2" +) + +func IsPendingCmd() *cli.Command { + isPendingCmd := &cli.Command{ + Name: "is-pending-admin", + Usage: "user admin is-pending-admin --account-address --pending-admin-address ", + UsageText: "Checks if a user is pending acceptance to admin.", + Description: ` + Checks if a user is pending acceptance to admin. + `, + After: telemetry.AfterRunAction(), + Flags: []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + &PendingAdminAddressFlag, + }, + } + + return isPendingCmd +} diff --git a/pkg/user/admin/list.go b/pkg/user/admin/list.go new file mode 100644 index 00000000..b4fd204d --- /dev/null +++ b/pkg/user/admin/list.go @@ -0,0 +1,25 @@ +package admin + +import ( + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/urfave/cli/v2" +) + +func ListCmd() *cli.Command { + listCmd := &cli.Command{ + Name: "list-admins", + Usage: "user admin list-admins --account-address ", + UsageText: "List all users who are admins.", + Description: ` + List all users who are admins. + `, + After: telemetry.AfterRunAction(), + Flags: []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + }, + } + + return listCmd +} diff --git a/pkg/user/admin/list_pending.go b/pkg/user/admin/list_pending.go new file mode 100644 index 00000000..b12f0c65 --- /dev/null +++ b/pkg/user/admin/list_pending.go @@ -0,0 +1,25 @@ +package admin + +import ( + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/urfave/cli/v2" +) + +func ListPendingCmd() *cli.Command { + listPendingCmd := &cli.Command{ + Name: "list-pending-admins", + Usage: "user admin list-pending-admins --account-address ", + UsageText: "List all users who are pending admin acceptance.", + Description: ` + List all users who are pending admin acceptance. + `, + After: telemetry.AfterRunAction(), + Flags: []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + }, + } + + return listPendingCmd +} diff --git a/pkg/user/admin/remove.go b/pkg/user/admin/remove.go new file mode 100644 index 00000000..cf8600f2 --- /dev/null +++ b/pkg/user/admin/remove.go @@ -0,0 +1,34 @@ +package admin + +import ( + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/urfave/cli/v2" +) + +func RemoveCmd() *cli.Command { + removeCmd := &cli.Command{ + Name: "remove-admin", + Usage: "user admin remove-admin --account-address --admin-address ", + UsageText: "The remove command allows you to remove an admin user.", + Description: ` + The remove command allows you to remove an admin user. + `, + After: telemetry.AfterRunAction(), + Flags: removeFlags(), + } + + return removeCmd +} + +func removeFlags() []cli.Flag { + cmdFlags := []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + &AdminAddressFlag, + } + sort.Sort(cli.FlagsByName(cmdFlags)) + return append(cmdFlags, flags.GetSignerFlags()...) +} diff --git a/pkg/user/admin/remove_pending.go b/pkg/user/admin/remove_pending.go new file mode 100644 index 00000000..9795f32e --- /dev/null +++ b/pkg/user/admin/remove_pending.go @@ -0,0 +1,34 @@ +package admin + +import ( + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/urfave/cli/v2" +) + +func RemovePendingCmd() *cli.Command { + removeCmd := &cli.Command{ + Name: "remove-pending-admin", + Usage: "user admin remove-pending-admin --account-address --admin-address ", + UsageText: "Remove a user who is pending admin acceptance.", + Description: ` + Remove a user who is pending admin acceptance. + `, + After: telemetry.AfterRunAction(), + Flags: removePendingFlags(), + } + + return removeCmd +} + +func removePendingFlags() []cli.Flag { + cmdFlags := []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + &AdminAddressFlag, + } + sort.Sort(cli.FlagsByName(cmdFlags)) + return append(cmdFlags, flags.GetSignerFlags()...) +} diff --git a/pkg/user/appointee/appointee.go b/pkg/user/appointee/appointee.go new file mode 100644 index 00000000..1f1f4282 --- /dev/null +++ b/pkg/user/appointee/appointee.go @@ -0,0 +1,32 @@ +package appointee + +import ( + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/urfave/cli/v2" +) + +func AppointeeCmd() *cli.Command { + appointeeCmd := &cli.Command{ + Name: "appointee", + Usage: "user appointee ", + UsageText: "User permission management operations.", + Description: ` + User permission management operations. + `, + After: telemetry.AfterRunAction(), + Flags: []cli.Flag{ + &flags.VerboseFlag, + }, + Subcommands: []*cli.Command{ + BatchSetCmd(), + canCallCmd(generateUserCanCallReader), + ListCmd(generateListUsersReader), + ListPermissionsCmd(generateListUserPermissionsReader), + RemoveCmd(), + SetCmd(), + }, + } + + return appointeeCmd +} diff --git a/pkg/user/appointee/batch_set.go b/pkg/user/appointee/batch_set.go new file mode 100644 index 00000000..cfd504f4 --- /dev/null +++ b/pkg/user/appointee/batch_set.go @@ -0,0 +1,33 @@ +package appointee + +import ( + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/urfave/cli/v2" +) + +func BatchSetCmd() *cli.Command { + batchSetCmd := &cli.Command{ + Name: "batch set", + Usage: "user appointee batch-set --batch-set-file ", + UsageText: "Appoint multiple users permissions at a time.", + Description: ` + Appoint multiple users permissions at a time. + `, + After: telemetry.AfterRunAction(), + Flags: batchSetFlags(), + } + + return batchSetCmd +} + +func batchSetFlags() []cli.Flag { + cmdFlags := []cli.Flag{ + &flags.VerboseFlag, + &BatchSetFileFlag, + } + sort.Sort(cli.FlagsByName(cmdFlags)) + return append(cmdFlags, flags.GetSignerFlags()...) +} diff --git a/pkg/user/appointee/can_call.go b/pkg/user/appointee/can_call.go new file mode 100644 index 00000000..59d4b958 --- /dev/null +++ b/pkg/user/appointee/can_call.go @@ -0,0 +1,147 @@ +package appointee + +import ( + "context" + "fmt" + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" +) + +type UserCanCallReader interface { + UserCanCall( + ctx context.Context, + userAddress gethcommon.Address, + callerAddress gethcommon.Address, + target gethcommon.Address, + selector [4]byte, + ) (bool, error) +} + +func canCallCmd(readerGenerator func(logging.Logger, *canCallConfig) (UserCanCallReader, error)) *cli.Command { + cmd := &cli.Command{ + Name: "can-call", + Usage: "user appointee can-call --account-address --caller-address --taget-address --selector ", + UsageText: "Checks if a user has a specific permission.", + Description: ` + Checks if a user has a specific permission. + `, + Action: func(c *cli.Context) error { + return canCall(c, readerGenerator) + }, + After: telemetry.AfterRunAction(), + Flags: canCallFlags(), + } + + return cmd +} + +func canCall(cliCtx *cli.Context, generator func(logging.Logger, *canCallConfig) (UserCanCallReader, error)) error { + ctx := cliCtx.Context + logger := common.GetLogger(cliCtx) + + config, err := readAndValidateUserConfig(cliCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + } + cliCtx.App.Metadata["network"] = config.ChainID.String() + elReader, err := generator(logger, config) + if err != nil { + return err + } + + result, err := elReader.UserCanCall(ctx, config.UserAddress, config.CallerAddress, config.Target, config.Selector) + fmt.Printf("CanCall Result: %v\n", result) + fmt.Printf("Selector, Target and User: %s, %x, %s\n", config.Target, string(config.Selector[:]), config.UserAddress) + return err +} + +func readAndValidateUserConfig(cliContext *cli.Context, logger logging.Logger) (*canCallConfig, error) { + userAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddressFlag.Name)) + ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) + network := cliContext.String(flags.NetworkFlag.Name) + environment := cliContext.String(flags.EnvironmentFlag.Name) + target := gethcommon.HexToAddress(cliContext.String(TargetAddressFlag.Name)) + selector := cliContext.String(SelectorFlag.Name) + selectorBytes, err := common.ValidateAndConvertSelectorString(selector) + if err != nil { + return nil, err + } + + if environment == "" { + environment = common.GetEnvFromNetwork(network) + } + + chainID := utils.NetworkNameToChainId(network) + permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + + if common.IsEmptyString(permissionManagerAddress) { + permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + + logger.Debugf( + "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + environment, + network, + chainID, + permissionManagerAddress, + ) + + return &canCallConfig{ + Network: network, + RPCUrl: ethRpcUrl, + UserAddress: userAddress, + CallerAddress: callerAddress, + Target: target, + Selector: selectorBytes, + PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), + ChainID: chainID, + Environment: environment, + }, nil +} + +func generateUserCanCallReader( + logger logging.Logger, + config *canCallConfig, +) (UserCanCallReader, error) { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) + } + elReader, err := elcontracts.NewReaderFromConfig( + elcontracts.Config{ + PermissionsControllerAddress: config.PermissionManagerAddress, + }, + ethClient, + logger, + ) + return elReader, err +} + +func canCallFlags() []cli.Flag { + cmdFlags := []cli.Flag{ + &AccountAddressFlag, + &CallerAddressFlag, + &TargetAddressFlag, + &SelectorFlag, + &PermissionControllerAddressFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + } + sort.Sort(cli.FlagsByName(cmdFlags)) + return cmdFlags +} diff --git a/pkg/user/appointee/can_call_test.go b/pkg/user/appointee/can_call_test.go new file mode 100644 index 00000000..b9f89bcd --- /dev/null +++ b/pkg/user/appointee/can_call_test.go @@ -0,0 +1,137 @@ +package appointee + +import ( + "context" + "errors" + "testing" + + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v2" +) + +type mockElChainReader struct { + canCallFunc func( + ctx context.Context, + userAddress gethcommon.Address, + callerAddress gethcommon.Address, + target gethcommon.Address, + selector [4]byte, + ) (bool, error) +} + +func newMockElChainReader() mockElChainReader { + return mockElChainReader{ + canCallFunc: func(ctx context.Context, userAddress, callerAddress, target gethcommon.Address, selector [4]byte) (bool, error) { + return true, nil + }, + } +} + +func newErrorMockElChainReader(expectedError string) mockElChainReader { + return mockElChainReader{ + canCallFunc: func(ctx context.Context, userAddress, callerAddress, target gethcommon.Address, selector [4]byte) (bool, error) { + return false, errors.New(expectedError) + }, + } +} + +func (m *mockElChainReader) UserCanCall( + ctx context.Context, + userAddress, callerAddress, + target gethcommon.Address, + selector [4]byte, +) (bool, error) { + return m.canCallFunc(ctx, userAddress, callerAddress, target, selector) +} + +func TestCanCallCmd_Success(t *testing.T) { + app := cli.NewApp() + app.Commands = []*cli.Command{canCallCmd(generateMockReader())} + + args := []string{ + "TestCanCallCmd_Success", + "can-call", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--caller-address", "0x9876543210fedcba9876543210fedcba98765432", + "--target-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--selector", "0x1A2B3C4D", + "--network", "holesky", + "--permission-controller-address", "0xe4dB7125ef7a9D99F809B6b7788f75c8D84d8455", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestCanCallCmd_UserCanCallError(t *testing.T) { + errString := "Error while executing call from reader" + mockReader := newErrorMockElChainReader(errString) + + app := cli.NewApp() + app.Commands = []*cli.Command{ + canCallCmd(func(logger logging.Logger, config *canCallConfig) (UserCanCallReader, error) { + return UserCanCallReader(&mockReader), nil + }), + } + + args := []string{ + "TestCanCallCmd_UserCanCallError", + "can-call", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--caller-address", "0x9876543210fedcba9876543210fedcba98765432", + "--target-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--selector", "0x1A2B3C4D", + "--network", "holesky", + "--permission-controller-address", "0xe4dB7125ef7a9D99F809B6b7788f75c8D84d8455", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), errString) +} + +func TestCanCallCmd_InvalidSelector(t *testing.T) { + app := cli.NewApp() + app.Commands = []*cli.Command{canCallCmd(generateMockReader())} + + args := []string{ + "TestCanCallCmd_InvalidSelector", + "can-call", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--caller-address", "0x9876543210fedcba9876543210fedcba98765432", + "--target-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--selector", "incorrect-format", + "--permission-controller-address", "0xe4dB7125ef7a9D99F809B6b7788f75c8D84d8455", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), "selector must be a 4-byte hex string prefixed with '0x'") + + args = []string{ + "TestCanCallCmd_InvalidSelector", + "can-call", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--caller-address", "0x9876543210fedcba9876543210fedcba98765432", + "--target-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--selector", "0xincorrect-format", + "--permission-controller-address", "0xe4dB7125ef7a9D99F809B6b7788f75c8D84d8455", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + } + + err = app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), "selector must be a 4-byte hex string prefixed with '0x'") +} + +func generateMockReader() func(logger logging.Logger, config *canCallConfig) (UserCanCallReader, error) { + return func(logger logging.Logger, config *canCallConfig) (UserCanCallReader, error) { + mockReader := newMockElChainReader() + return UserCanCallReader(&mockReader), nil + } +} diff --git a/pkg/user/appointee/flags.go b/pkg/user/appointee/flags.go new file mode 100644 index 00000000..3c4a6e37 --- /dev/null +++ b/pkg/user/appointee/flags.go @@ -0,0 +1,48 @@ +package appointee + +import "github.com/urfave/cli/v2" + +var ( + AccountAddressFlag = cli.StringFlag{ + Name: "account-address", + Aliases: []string{"aa"}, + Usage: "The Ethereum address of the user. Example: --account-address \"0x...\"", + EnvVars: []string{"ACCOUNT_ADDRESS"}, + } + AppointeeAddressFlag = cli.StringFlag{ + Name: "appointee-address", + Aliases: []string{"appa"}, + Usage: "The Ethereum address of the user. Example: --appointee-address \"0x...\"", + EnvVars: []string{"APPOINTEE_ADDRESS"}, + } + CallerAddressFlag = cli.StringFlag{ + Name: "caller-address", + Aliases: []string{"ca"}, + Usage: "The Ethereum address of the caller. Example: --caller-address \"0x...\"", + EnvVars: []string{"CALLER_ADDRESS"}, + } + SelectorFlag = cli.StringFlag{ + Name: "selector", + Aliases: []string{"s"}, + Usage: "The selector for managing permissions to protocol operations. A selector is a smart contract method.", + EnvVars: []string{"SELECTOR"}, + } + TargetAddressFlag = cli.StringFlag{ + Name: "target-address", + Aliases: []string{"ta"}, + Usage: "The contract address for managing permissions to protocol operations.", + EnvVars: []string{"TARGET_ADDRESS"}, + } + BatchSetFileFlag = cli.StringFlag{ + Name: "batch-set-file", + Aliases: []string{"bsf"}, + Usage: "A YAML file for executing a batch of set permission operations.", + EnvVars: []string{"BATCH_SET_FILE"}, + } + PermissionControllerAddressFlag = cli.StringFlag{ + Name: "permission-controller-address", + Aliases: []string{"pca"}, + Usage: "The Ethereum address of the Permission Manager. Example: --permission-controller-address \"0x...\"", + EnvVars: []string{"PERMISSION_CONTROLLER_ADDRESS"}, + } +) diff --git a/pkg/user/appointee/list.go b/pkg/user/appointee/list.go new file mode 100644 index 00000000..a6278160 --- /dev/null +++ b/pkg/user/appointee/list.go @@ -0,0 +1,161 @@ +package appointee + +import ( + "context" + "fmt" + "sort" + "strings" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" +) + +type ListUsersReader interface { + ListUsers( + ctx context.Context, + userAddress gethcommon.Address, + target gethcommon.Address, + selector [4]byte, + ) ([]gethcommon.Address, error) +} + +func ListCmd(readerGenerator func(logging.Logger, *listUsersConfig) (ListUsersReader, error)) *cli.Command { + listCmd := &cli.Command{ + Name: "list", + Usage: "user appointee list --account-address --target-address --selector ", + UsageText: "Lists all appointed users for an account with the provided permissions.", + Description: ` + Lists all appointed users for an account with the provided permissions. + `, + Action: func(c *cli.Context) error { + return listAppointees(c, readerGenerator) + }, + After: telemetry.AfterRunAction(), + Flags: listFlags(), + } + + return listCmd +} + +func listAppointees( + cliCtx *cli.Context, + generator func(logging.Logger, *listUsersConfig) (ListUsersReader, error), +) error { + ctx := cliCtx.Context + logger := common.GetLogger(cliCtx) + + config, err := readAndValidateListUsersConfig(cliCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + } + + elReader, err := generator(logger, config) + if err != nil { + return err + } + + users, err := elReader.ListUsers(ctx, config.UserAddress, config.Target, config.Selector) + if err != nil { + return err + } + printResults(config, users) + return nil +} + +func printResults(config *listUsersConfig, users []gethcommon.Address) { + fmt.Printf( + "Selector, Target and Appointer: %s, %x, %s", + config.Target, + string(config.Selector[:]), + config.UserAddress, + ) + fmt.Println(strings.Repeat("-", 80)) + + for _, user := range users { + fmt.Printf("User Id: 0x%b\n", user) + } +} + +func readAndValidateListUsersConfig(cliContext *cli.Context, logger logging.Logger) (*listUsersConfig, error) { + userAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) + network := cliContext.String(flags.NetworkFlag.Name) + environment := cliContext.String(flags.EnvironmentFlag.Name) + target := gethcommon.HexToAddress(cliContext.String(TargetAddressFlag.Name)) + selector := cliContext.String(SelectorFlag.Name) + selectorBytes, err := common.ValidateAndConvertSelectorString(selector) + if err != nil { + return nil, err + } + + if environment == "" { + environment = common.GetEnvFromNetwork(network) + } + + chainID := utils.NetworkNameToChainId(network) + cliContext.App.Metadata["network"] = chainID.String() + permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + + if common.IsEmptyString(permissionManagerAddress) { + permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + + logger.Debugf( + "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + environment, + network, + chainID, + permissionManagerAddress, + ) + + return &listUsersConfig{ + Network: network, + RPCUrl: ethRpcUrl, + UserAddress: userAddress, + Target: target, + Selector: selectorBytes, + PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), + ChainID: chainID, + Environment: environment, + }, nil +} + +func generateListUsersReader(logger logging.Logger, config *listUsersConfig) (ListUsersReader, error) { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) + } + elReader, err := elcontracts.NewReaderFromConfig( + elcontracts.Config{ + PermissionsControllerAddress: config.PermissionManagerAddress, + }, + ethClient, + logger, + ) + return elReader, err +} + +func listFlags() []cli.Flag { + cmdFlags := []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + &TargetAddressFlag, + &SelectorFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + } + sort.Sort(cli.FlagsByName(cmdFlags)) + return cmdFlags +} diff --git a/pkg/user/appointee/list_permissions.go b/pkg/user/appointee/list_permissions.go new file mode 100644 index 00000000..bffcee52 --- /dev/null +++ b/pkg/user/appointee/list_permissions.go @@ -0,0 +1,160 @@ +package appointee + +import ( + "context" + "fmt" + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" +) + +type PermissionsReader interface { + ListUserPermissions( + ctx context.Context, + appointed gethcommon.Address, + userAddress gethcommon.Address, + ) ([]gethcommon.Address, [][4]byte, error) +} + +func ListPermissionsCmd( + readerGenerator func(logging.Logger, *listUserPermissionsConfig) (PermissionsReader, error), +) *cli.Command { + cmd := &cli.Command{ + Name: "list-permissions", + Usage: "user appointee list-permissions --account-address --appointee-address ", + UsageText: "List all permissions for a user.", + Description: ` + List all permissions of a user. + `, + Action: func(c *cli.Context) error { + return listPermissions(c, readerGenerator) + }, + After: telemetry.AfterRunAction(), + Flags: listPermissionFlags(), + } + + return cmd +} + +func listPermissions( + cliCtx *cli.Context, + generator func(logging.Logger, *listUserPermissionsConfig) (PermissionsReader, error), +) error { + ctx := cliCtx.Context + logger := common.GetLogger(cliCtx) + + config, err := readAndValidateListUserPermissionsConfig(cliCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate list user permissions config", err) + } + + cliCtx.App.Metadata["network"] = config.ChainID.String() + reader, err := generator(logger, config) + if err != nil { + return err + } + + users, permissions, err := reader.ListUserPermissions(ctx, config.AccountAddress, config.UserAddress) + if err != nil { + return err + } + printPermissions(config, users, permissions) + return nil +} + +func readAndValidateListUserPermissionsConfig( + cliContext *cli.Context, + logger logging.Logger, +) (*listUserPermissionsConfig, error) { + accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + userAddress := gethcommon.HexToAddress(cliContext.String(AppointeeAddressFlag.Name)) + ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) + network := cliContext.String(flags.NetworkFlag.Name) + environment := cliContext.String(flags.EnvironmentFlag.Name) + + if environment == "" { + environment = common.GetEnvFromNetwork(network) + } + + chainID := utils.NetworkNameToChainId(network) + permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + + var err error + if common.IsEmptyString(permissionManagerAddress) { + permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + + logger.Debugf( + "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + environment, + network, + chainID, + permissionManagerAddress, + ) + + return &listUserPermissionsConfig{ + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + UserAddress: userAddress, + PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), + ChainID: chainID, + Environment: environment, + }, nil +} + +func printPermissions(config *listUserPermissionsConfig, targets []gethcommon.Address, selectors [][4]byte) { + fmt.Printf("User: %s\n", config.UserAddress) + fmt.Printf("Appointed by: %s\n", config.AccountAddress) + fmt.Println("====================================================================================") + + for _, target := range targets { + for _, selector := range selectors { + fmt.Printf("Target: %s, Selector: %x\n", target, selector) + } + + } +} + +func generateListUserPermissionsReader( + logger logging.Logger, + config *listUserPermissionsConfig, +) (PermissionsReader, error) { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) + } + elReader, err := elcontracts.NewReaderFromConfig( + elcontracts.Config{ + PermissionsControllerAddress: config.PermissionManagerAddress, + }, + ethClient, + logger, + ) + return elReader, err +} + +func listPermissionFlags() []cli.Flag { + cmdFlags := []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + &AppointeeAddressFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + } + sort.Sort(cli.FlagsByName(cmdFlags)) + return cmdFlags +} diff --git a/pkg/user/appointee/list_permissions_test.go b/pkg/user/appointee/list_permissions_test.go new file mode 100644 index 00000000..e7f71e3d --- /dev/null +++ b/pkg/user/appointee/list_permissions_test.go @@ -0,0 +1,116 @@ +package appointee + +import ( + "context" + "errors" + "testing" + + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v2" +) + +type mockListPermissionsReader struct { + listPermissionsFunc func( + ctx context.Context, + appointed gethcommon.Address, + userAddress gethcommon.Address, + ) ([]gethcommon.Address, [][4]byte, error) +} + +func newMockListPermissionsReader( + users []gethcommon.Address, + permissions [][4]byte, + err error, +) *mockListPermissionsReader { + return &mockListPermissionsReader{ + listPermissionsFunc: func(ctx context.Context, appointed, userAddress gethcommon.Address) ([]gethcommon.Address, [][4]byte, error) { + return users, permissions, err + }, + } +} + +func (m *mockListPermissionsReader) ListUserPermissions( + ctx context.Context, + appointed gethcommon.Address, + userAddress gethcommon.Address, +) ([]gethcommon.Address, [][4]byte, error) { + return m.listPermissionsFunc(ctx, appointed, userAddress) +} + +func generateMockListPermissionsReader( + users []gethcommon.Address, + permissions [][4]byte, + err error, +) func(logging.Logger, *listUserPermissionsConfig) (PermissionsReader, error) { + return func(logger logging.Logger, config *listUserPermissionsConfig) (PermissionsReader, error) { + return newMockListPermissionsReader(users, permissions, err), nil + } +} + +func TestListPermissions_Success(t *testing.T) { + expectedUsers := []gethcommon.Address{ + gethcommon.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + } + expectedPermissions := [][4]byte{ + {0x1A, 0x2B, 0x3C, 0x4D}, + } + + app := cli.NewApp() + app.Commands = []*cli.Command{ + ListPermissionsCmd(generateMockListPermissionsReader(expectedUsers, expectedPermissions, nil)), + } + + args := []string{ + "TestListPermissions_Success", + "list-permissions", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--appointee-address", "0x9876543210fedcba9876543210fedcba98765432", + "--network", "holesky", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestListPermissions_ReaderError(t *testing.T) { + expectedError := "Error fetching permissions" + app := cli.NewApp() + app.Commands = []*cli.Command{ + ListPermissionsCmd(generateMockListPermissionsReader(nil, nil, errors.New(expectedError))), + } + + args := []string{ + "TestListPermissions_ReaderError", + "list-permissions", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--appointee-address", "0x9876543210fedcba9876543210fedcba98765432", + "--network", "holesky", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} + +func TestListPermissions_NoPermissions(t *testing.T) { + app := cli.NewApp() + app.Commands = []*cli.Command{ + ListPermissionsCmd(generateMockListPermissionsReader([]gethcommon.Address{}, [][4]byte{}, nil)), + } + + args := []string{ + "TestListPermissions_NoPermissions", + "list-permissions", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--appointee-address", "0x9876543210fedcba9876543210fedcba98765432", + "--network", "holesky", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + } + + err := app.Run(args) + assert.NoError(t, err) +} diff --git a/pkg/user/appointee/list_test.go b/pkg/user/appointee/list_test.go new file mode 100644 index 00000000..cbc6911c --- /dev/null +++ b/pkg/user/appointee/list_test.go @@ -0,0 +1,134 @@ +package appointee + +import ( + "context" + "errors" + "testing" + + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v2" +) + +type mockListUsersReader struct { + listUsersFunc func( + ctx context.Context, + userAddress gethcommon.Address, + target gethcommon.Address, + selector [4]byte, + ) ([]gethcommon.Address, error) +} + +func newMockListUsersReader(users []gethcommon.Address, err error) *mockListUsersReader { + return &mockListUsersReader{ + listUsersFunc: func(ctx context.Context, userAddress, target gethcommon.Address, selector [4]byte) ([]gethcommon.Address, error) { + return users, err + }, + } +} + +func (m *mockListUsersReader) ListUsers( + ctx context.Context, + userAddress, target gethcommon.Address, + selector [4]byte, +) ([]gethcommon.Address, error) { + return m.listUsersFunc(ctx, userAddress, target, selector) +} + +func generateMockListReader( + users []gethcommon.Address, + err error, +) func(logging.Logger, *listUsersConfig) (ListUsersReader, error) { + return func(logger logging.Logger, config *listUsersConfig) (ListUsersReader, error) { + return newMockListUsersReader(users, err), nil + } +} + +func TestListAppointees_Success(t *testing.T) { + expectedUsers := []gethcommon.Address{ + gethcommon.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + gethcommon.HexToAddress("0x9876543210fedcba9876543210fedcba98765432"), + } + + app := cli.NewApp() + app.Commands = []*cli.Command{ + ListCmd(generateMockListReader(expectedUsers, nil)), + } + + args := []string{ + "TestListAppointees_Success", + "list", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--target-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--selector", "0x1A2B3C4D", + "--network", "holesky", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestListAppointees_ReaderError(t *testing.T) { + expectedError := "Error fetching appointees" + app := cli.NewApp() + app.Commands = []*cli.Command{ + ListCmd(generateMockListReader(nil, errors.New(expectedError))), + } + + args := []string{ + "TestListAppointees_ReaderError", + "list", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--target-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--selector", "0x1A2B3C4D", + "--network", "holesky", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} + +func TestListAppointees_InvalidSelector(t *testing.T) { + app := cli.NewApp() + app.Commands = []*cli.Command{ + ListCmd(generateMockListReader(nil, nil)), + } + + args := []string{ + "TestListAppointees_InvalidSelector", + "list", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--target-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--selector", "invalid", + "--network", "holesky", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), "selector must be a 4-byte hex string prefixed with '0x'") +} + +func TestListAppointees_NoUsers(t *testing.T) { + app := cli.NewApp() + app.Commands = []*cli.Command{ + ListCmd(generateMockListReader([]gethcommon.Address{}, nil)), + } + + args := []string{ + "TestListAppointees_NoUsers", + "list", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--target-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--selector", "0x1A2B3C4D", + "--network", "holesky", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + } + + err := app.Run(args) + assert.NoError(t, err) +} diff --git a/pkg/user/appointee/remove.go b/pkg/user/appointee/remove.go new file mode 100644 index 00000000..3cf7d684 --- /dev/null +++ b/pkg/user/appointee/remove.go @@ -0,0 +1,36 @@ +package appointee + +import ( + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/urfave/cli/v2" +) + +func RemoveCmd() *cli.Command { + removeCmd := &cli.Command{ + Name: "remove", + Usage: "user appointee remove --account-address --appointee-address --target-address --selector ", + UsageText: "Remove a user's permission", + Description: ` + Remove a user's permission'. + `, + After: telemetry.AfterRunAction(), + Flags: removeCommandFlags(), + } + + return removeCmd +} + +func removeCommandFlags() []cli.Flag { + cmdFlags := []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + &AppointeeAddressFlag, + &TargetAddressFlag, + &SelectorFlag, + } + sort.Sort(cli.FlagsByName(cmdFlags)) + return append(cmdFlags, flags.GetSignerFlags()...) +} diff --git a/pkg/user/appointee/set.go b/pkg/user/appointee/set.go new file mode 100644 index 00000000..87e34d39 --- /dev/null +++ b/pkg/user/appointee/set.go @@ -0,0 +1,36 @@ +package appointee + +import ( + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/urfave/cli/v2" +) + +func SetCmd() *cli.Command { + setCmd := &cli.Command{ + Name: "set", + Usage: "user appointee set --account-address --appointee-address --target-address --selector ", + UsageText: "Grant a user a permission.", + Description: ` + Grant a user a permission.'. + `, + After: telemetry.AfterRunAction(), + Flags: setCommandFlags(), + } + + return setCmd +} + +func setCommandFlags() []cli.Flag { + cmdFlags := []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + &AppointeeAddressFlag, + &TargetAddressFlag, + &SelectorFlag, + } + sort.Sort(cli.FlagsByName(cmdFlags)) + return append(cmdFlags, flags.GetSignerFlags()...) +} diff --git a/pkg/user/appointee/types.go b/pkg/user/appointee/types.go new file mode 100644 index 00000000..9ec83ffc --- /dev/null +++ b/pkg/user/appointee/types.go @@ -0,0 +1,41 @@ +package appointee + +import ( + "math/big" + + gethcommon "github.com/ethereum/go-ethereum/common" +) + +type canCallConfig struct { + Network string + RPCUrl string + UserAddress gethcommon.Address + CallerAddress gethcommon.Address + Target gethcommon.Address + Selector [4]byte + PermissionManagerAddress gethcommon.Address + ChainID *big.Int + Environment string +} + +type listUsersConfig struct { + Network string + RPCUrl string + AccountAddress gethcommon.Address + UserAddress gethcommon.Address + Target gethcommon.Address + Selector [4]byte + PermissionManagerAddress gethcommon.Address + ChainID *big.Int + Environment string +} + +type listUserPermissionsConfig struct { + Network string + RPCUrl string + AccountAddress gethcommon.Address + UserAddress gethcommon.Address + PermissionManagerAddress gethcommon.Address + ChainID *big.Int + Environment string +} diff --git a/pkg/users.go b/pkg/users.go new file mode 100644 index 00000000..44b09b51 --- /dev/null +++ b/pkg/users.go @@ -0,0 +1,20 @@ +package pkg + +import ( + "github.com/Layr-Labs/eigenlayer-cli/pkg/user/admin" + "github.com/Layr-Labs/eigenlayer-cli/pkg/user/appointee" + "github.com/urfave/cli/v2" +) + +func UserCmd() *cli.Command { + var userCmd = &cli.Command{ + Name: "user", + Usage: "Manage user permissions", + Subcommands: []*cli.Command{ + admin.AdminCmd(), + appointee.AppointeeCmd(), + }, + } + + return userCmd +} From e8bbe9b1dd76465370a154cefb3319f31ba5d63f Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Mon, 9 Dec 2024 21:31:22 -0800 Subject: [PATCH 02/34] fix allocation delay --- pkg/operator/allocations/set_allocation_delay.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/operator/allocations/set_allocation_delay.go b/pkg/operator/allocations/set_allocation_delay.go index 82af2b8c..87b64d8e 100644 --- a/pkg/operator/allocations/set_allocation_delay.go +++ b/pkg/operator/allocations/set_allocation_delay.go @@ -155,7 +155,7 @@ func readAndValidateAllocationDelayConfig(c *cli.Context, logger logging.Logger) } allocationDelayString := c.Args().First() - allocationDelayInt, err := strconv.Atoi(allocationDelayString) + allocationDelayUint, err := strconv.ParseUint(allocationDelayString, 10, 32) if err != nil { return nil, eigenSdkUtils.WrapError("failed to convert allocation delay to int", err) } @@ -203,6 +203,6 @@ func readAndValidateAllocationDelayConfig(c *cli.Context, logger logging.Logger) operatorAddress: gethcommon.HexToAddress(operatorAddress), signerConfig: signerConfig, delegationManagerAddress: gethcommon.HexToAddress(delegationManagerAddress), - allocationDelay: uint32(allocationDelayInt), + allocationDelay: uint32(allocationDelayUint), }, nil } From 86236245058834bb2a68969d39cc4d26191ac088 Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Tue, 10 Dec 2024 14:49:33 -0800 Subject: [PATCH 03/34] Adding `appointee` `remove` and `set` command implementation and unit tests. (#250) Co-authored-by: Brandon Chatham --- cmd/eigenlayer/main.go | 2 +- go.mod | 3 +- go.sum | 4 +- pkg/user/appointee/appointee.go | 7 +- pkg/user/appointee/list_permissions_test.go | 1 + pkg/user/appointee/list_test.go | 1 + pkg/user/appointee/remove.go | 140 +++++++++++++++++++- pkg/user/appointee/remove_test.go | 108 +++++++++++++++ pkg/user/appointee/set.go | 140 +++++++++++++++++++- pkg/user/appointee/set_test.go | 107 +++++++++++++++ pkg/user/appointee/types.go | 27 ++++ pkg/users.go | 5 +- 12 files changed, 533 insertions(+), 12 deletions(-) create mode 100644 pkg/user/appointee/remove_test.go create mode 100644 pkg/user/appointee/set_test.go diff --git a/cmd/eigenlayer/main.go b/cmd/eigenlayer/main.go index a7f23519..29e414d8 100644 --- a/cmd/eigenlayer/main.go +++ b/cmd/eigenlayer/main.go @@ -43,7 +43,7 @@ func main() { app.Commands = append(app.Commands, pkg.RewardsCmd(prompter)) app.Commands = append(app.Commands, pkg.KeysCmd(prompter)) app.Commands = append(app.Commands, pkg.EigenPodCmd(prompter)) - app.Commands = append(app.Commands, pkg.UserCmd()) + app.Commands = append(app.Commands, pkg.UserCmd(prompter)) if err := app.Run(os.Args); err != nil { _, err := fmt.Fprintln(os.Stderr, err) diff --git a/go.mod b/go.mod index bb732316..e16d3a04 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,7 @@ require ( //<<<<<<< HEAD // github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212190947-9985122d81fe //======= - github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210000422-beb1a3c4502f - //>>>>>>> 070a30e (feat: slashing commands) + github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210222107-c2ed40624db7 github.com/blang/semver/v4 v4.0.0 github.com/consensys/gnark-crypto v0.12.1 github.com/ethereum/go-ethereum v1.14.5 diff --git a/go.sum b/go.sum index 7def4b86..e01a1ba8 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 h1:G5Q1SnLmFbEjhOkky3vIHk github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12/go.mod h1:OlJd1QjqEW53wfWG/lJyPCGvrXwWVEjPQsP4TV+gttQ= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e h1:DvW0/kWHV9mZsbH2KOjEHKTSIONNPUj6X05FJvUohy4= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e/go.mod h1:T7tYN8bTdca2pkMnz9G2+ZwXYWw5gWqQUIu4KLgC/vM= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210000422-beb1a3c4502f h1:D94Vf+dALr9W0Ie18lZ8QDPvOAFX8FBbIpyVAtCUL1A= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210000422-beb1a3c4502f/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210222107-c2ed40624db7 h1:1kehcGgMyVloGzrd36CSibYz+fC2BkKV0fqeYCpovIQ= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210222107-c2ed40624db7/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= diff --git a/pkg/user/appointee/appointee.go b/pkg/user/appointee/appointee.go index 1f1f4282..4e2a5d46 100644 --- a/pkg/user/appointee/appointee.go +++ b/pkg/user/appointee/appointee.go @@ -3,10 +3,11 @@ package appointee import ( "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" "github.com/urfave/cli/v2" ) -func AppointeeCmd() *cli.Command { +func AppointeeCmd(prompter utils.Prompter) *cli.Command { appointeeCmd := &cli.Command{ Name: "appointee", Usage: "user appointee ", @@ -23,8 +24,8 @@ func AppointeeCmd() *cli.Command { canCallCmd(generateUserCanCallReader), ListCmd(generateListUsersReader), ListPermissionsCmd(generateListUserPermissionsReader), - RemoveCmd(), - SetCmd(), + RemoveCmd(generateRemoveUserPermissionWriter(prompter)), + SetCmd(generateSetUserPermissionWriter(prompter)), }, } diff --git a/pkg/user/appointee/list_permissions_test.go b/pkg/user/appointee/list_permissions_test.go index e7f71e3d..1fa90bdc 100644 --- a/pkg/user/appointee/list_permissions_test.go +++ b/pkg/user/appointee/list_permissions_test.go @@ -8,6 +8,7 @@ import ( "github.com/Layr-Labs/eigensdk-go/logging" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v2" ) diff --git a/pkg/user/appointee/list_test.go b/pkg/user/appointee/list_test.go index cbc6911c..8f240a43 100644 --- a/pkg/user/appointee/list_test.go +++ b/pkg/user/appointee/list_test.go @@ -8,6 +8,7 @@ import ( "github.com/Layr-Labs/eigensdk-go/logging" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v2" ) diff --git a/pkg/user/appointee/remove.go b/pkg/user/appointee/remove.go index 3cf7d684..0c7c3b54 100644 --- a/pkg/user/appointee/remove.go +++ b/pkg/user/appointee/remove.go @@ -1,14 +1,31 @@ package appointee import ( + "context" "sort" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" ) -func RemoveCmd() *cli.Command { +type RemoveUserPermissionWriter interface { + RemovePermission( + ctx context.Context, + request elcontracts.RemovePermissionRequest, + ) (*gethtypes.Receipt, error) +} + +func RemoveCmd(readerGenerator func(logging.Logger, *removeConfig) (RemoveUserPermissionWriter, error)) *cli.Command { removeCmd := &cli.Command{ Name: "remove", Usage: "user appointee remove --account-address --appointee-address --target-address --selector ", @@ -17,12 +34,130 @@ func RemoveCmd() *cli.Command { Remove a user's permission'. `, After: telemetry.AfterRunAction(), + Action: func(c *cli.Context) error { + return removeUserPermission(c, readerGenerator) + }, Flags: removeCommandFlags(), } return removeCmd } +func removeUserPermission( + cliCtx *cli.Context, + generator func(logging.Logger, *removeConfig) (RemoveUserPermissionWriter, error), +) error { + ctx := cliCtx.Context + logger := common.GetLogger(cliCtx) + + config, err := readAndValidateRemoveConfig(cliCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + } + cliCtx.App.Metadata["network"] = config.ChainID.String() + permissionWriter, err := generator(logger, config) + if err != nil { + return err + } + receipt, err := permissionWriter.RemovePermission( + ctx, + elcontracts.RemovePermissionRequest{ + Account: config.AccountAddress, + UserAddress: config.UserAddress, + Target: config.Target, + Selector: config.Selector, + WaitForReceipt: true, + }, + ) + if err != nil { + return err + } + common.PrintTransactionInfo(receipt.TxHash.String(), config.ChainID) + return nil +} + +func generateRemoveUserPermissionWriter( + prompter utils.Prompter, +) func( + logger logging.Logger, + config *removeConfig, +) (RemoveUserPermissionWriter, error) { + return func(logger logging.Logger, config *removeConfig) (RemoveUserPermissionWriter, error) { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) + } + elWriter, err := common.GetELWriter( + config.AccountAddress, + &config.SignerConfig, + ethClient, + elcontracts.Config{ + PermissionsControllerAddress: config.PermissionManagerAddress, + }, + prompter, + config.ChainID, + logger, + ) + return elWriter, err + } +} + +func readAndValidateRemoveConfig(cliContext *cli.Context, logger logging.Logger) (*removeConfig, error) { + accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + userAddress := gethcommon.HexToAddress(cliContext.String(AppointeeAddressFlag.Name)) + ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) + network := cliContext.String(flags.NetworkFlag.Name) + environment := cliContext.String(flags.EnvironmentFlag.Name) + target := gethcommon.HexToAddress(cliContext.String(TargetAddressFlag.Name)) + selector := cliContext.String(SelectorFlag.Name) + selectorBytes, err := common.ValidateAndConvertSelectorString(selector) + if err != nil { + return nil, err + } + signerConfig, err := common.GetSignerConfig(cliContext, logger) + if err != nil { + // We don't want to throw error since people can still use it to generate the claim + // without broadcasting it + logger.Debugf("Failed to get signer config: %s", err) + } + + if environment == "" { + environment = common.GetEnvFromNetwork(network) + } + + chainID := utils.NetworkNameToChainId(network) + cliContext.App.Metadata["network"] = chainID.String() + permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + + if common.IsEmptyString(permissionManagerAddress) { + permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + + logger.Debugf( + "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + environment, + network, + chainID, + permissionManagerAddress, + ) + + return &removeConfig{ + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + UserAddress: userAddress, + Target: target, + Selector: selectorBytes, + SignerConfig: *signerConfig, + PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), + ChainID: chainID, + Environment: environment, + }, nil +} + func removeCommandFlags() []cli.Flag { cmdFlags := []cli.Flag{ &flags.VerboseFlag, @@ -30,6 +165,9 @@ func removeCommandFlags() []cli.Flag { &AppointeeAddressFlag, &TargetAddressFlag, &SelectorFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, } sort.Sort(cli.FlagsByName(cmdFlags)) return append(cmdFlags, flags.GetSignerFlags()...) diff --git a/pkg/user/appointee/remove_test.go b/pkg/user/appointee/remove_test.go new file mode 100644 index 00000000..f08d71b4 --- /dev/null +++ b/pkg/user/appointee/remove_test.go @@ -0,0 +1,108 @@ +package appointee + +import ( + "context" + "errors" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "testing" + + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + "github.com/stretchr/testify/assert" + + "github.com/urfave/cli/v2" +) + +type mockRemoveUserPermissionWriter struct { + removePermissionFunc func(ctx context.Context, request elcontracts.RemovePermissionRequest) (*gethtypes.Receipt, error) +} + +func (m *mockRemoveUserPermissionWriter) RemovePermission( + ctx context.Context, + request elcontracts.RemovePermissionRequest, +) (*gethtypes.Receipt, error) { + return m.removePermissionFunc(ctx, request) +} + +func generateMockRemoveWriter(err error) func(logging.Logger, *removeConfig) (RemoveUserPermissionWriter, error) { + return func(logger logging.Logger, config *removeConfig) (RemoveUserPermissionWriter, error) { + return &mockRemoveUserPermissionWriter{ + removePermissionFunc: func(ctx context.Context, request elcontracts.RemovePermissionRequest) (*gethtypes.Receipt, error) { + return &gethtypes.Receipt{}, err + }, + }, nil + } +} + +func TestRemoveCmd_Success(t *testing.T) { + app := cli.NewApp() + app.Commands = []*cli.Command{ + RemoveCmd(generateMockRemoveWriter(nil)), + } + + args := []string{ + "TestRemoveCmd_Success", + "remove", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--appointee-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--target-address", "0x9876543210fedcba9876543210fedcba98765432", + "--selector", "0x1A2B3C4D", + "--network", "holesky", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestRemoveCmd_GeneratorError(t *testing.T) { + expectedError := "failed to create permission writer" + app := cli.NewApp() + app.Commands = []*cli.Command{ + RemoveCmd(func(logger logging.Logger, config *removeConfig) (RemoveUserPermissionWriter, error) { + return nil, errors.New(expectedError) + }), + } + + args := []string{ + "TestRemoveCmd_GeneratorError", + "remove", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--appointee-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--target-address", "0x9876543210fedcba9876543210fedcba98765432", + "--selector", "0x1A2B3C4D", + "--network", "holesky", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} + +func TestRemoveCmd_RemovePermissionError(t *testing.T) { + expectedError := "error removing permission" + app := cli.NewApp() + app.Commands = []*cli.Command{ + RemoveCmd(generateMockRemoveWriter(errors.New(expectedError))), + } + + args := []string{ + "TestRemoveCmd_RemovePermissionError", + "remove", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--appointee-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--target-address", "0x9876543210fedcba9876543210fedcba98765432", + "--selector", "0x1A2B3C4D", + "--network", "holesky", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "--path-to-key-store", "/path/to/keystore.json", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} diff --git a/pkg/user/appointee/set.go b/pkg/user/appointee/set.go index 87e34d39..822d0867 100644 --- a/pkg/user/appointee/set.go +++ b/pkg/user/appointee/set.go @@ -1,14 +1,31 @@ package appointee import ( + "context" "sort" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" ) -func SetCmd() *cli.Command { +type SetUserPermissionWriter interface { + SetPermission( + ctx context.Context, + request elcontracts.SetPermissionRequest, + ) (*gethtypes.Receipt, error) +} + +func SetCmd(generator func(logging.Logger, *setConfig) (SetUserPermissionWriter, error)) *cli.Command { setCmd := &cli.Command{ Name: "set", Usage: "user appointee set --account-address --appointee-address --target-address --selector ", @@ -16,6 +33,9 @@ func SetCmd() *cli.Command { Description: ` Grant a user a permission.'. `, + Action: func(c *cli.Context) error { + return setUserPermission(c, generator) + }, After: telemetry.AfterRunAction(), Flags: setCommandFlags(), } @@ -23,6 +43,121 @@ func SetCmd() *cli.Command { return setCmd } +func setUserPermission( + cliCtx *cli.Context, + generator func(logging.Logger, *setConfig) (SetUserPermissionWriter, error), +) error { + ctx := cliCtx.Context + logger := common.GetLogger(cliCtx) + + config, err := readAndValidateSetConfig(cliCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + } + cliCtx.App.Metadata["network"] = config.ChainID.String() + permissionWriter, err := generator(logger, config) + if err != nil { + return err + } + receipt, err := permissionWriter.SetPermission( + ctx, + elcontracts.SetPermissionRequest{ + Account: config.AccountAddress, + UserAddress: config.UserAddress, + Target: config.Target, + Selector: config.Selector, + WaitForReceipt: true, + }, + ) + if err != nil { + return err + } + common.PrintTransactionInfo(receipt.TxHash.String(), config.ChainID) + return nil +} + +func generateSetUserPermissionWriter( + prompter utils.Prompter, +) func( + logger logging.Logger, + config *setConfig, +) (SetUserPermissionWriter, error) { + return func(logger logging.Logger, config *setConfig) (SetUserPermissionWriter, error) { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) + } + elWriter, err := common.GetELWriter( + config.AccountAddress, + &config.SignerConfig, + ethClient, + elcontracts.Config{ + PermissionsControllerAddress: config.PermissionManagerAddress, + }, + prompter, + config.ChainID, + logger, + ) + return elWriter, err + } +} + +func readAndValidateSetConfig(cliContext *cli.Context, logger logging.Logger) (*setConfig, error) { + accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + userAddress := gethcommon.HexToAddress(cliContext.String(AppointeeAddressFlag.Name)) + ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) + network := cliContext.String(flags.NetworkFlag.Name) + environment := cliContext.String(flags.EnvironmentFlag.Name) + target := gethcommon.HexToAddress(cliContext.String(TargetAddressFlag.Name)) + selector := cliContext.String(SelectorFlag.Name) + selectorBytes, err := common.ValidateAndConvertSelectorString(selector) + if err != nil { + return nil, err + } + signerConfig, err := common.GetSignerConfig(cliContext, logger) + if err != nil { + // We don't want to throw error since people can still use it to generate the claim + // without broadcasting it + logger.Debugf("Failed to get signer config: %s", err) + } + + if environment == "" { + environment = common.GetEnvFromNetwork(network) + } + + chainID := utils.NetworkNameToChainId(network) + cliContext.App.Metadata["network"] = chainID.String() + permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + + if common.IsEmptyString(permissionManagerAddress) { + permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + + logger.Debugf( + "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + environment, + network, + chainID, + permissionManagerAddress, + ) + + return &setConfig{ + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + UserAddress: userAddress, + Target: target, + Selector: selectorBytes, + SignerConfig: *signerConfig, + PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), + ChainID: chainID, + Environment: environment, + }, nil +} + func setCommandFlags() []cli.Flag { cmdFlags := []cli.Flag{ &flags.VerboseFlag, @@ -30,6 +165,9 @@ func setCommandFlags() []cli.Flag { &AppointeeAddressFlag, &TargetAddressFlag, &SelectorFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, } sort.Sort(cli.FlagsByName(cmdFlags)) return append(cmdFlags, flags.GetSignerFlags()...) diff --git a/pkg/user/appointee/set_test.go b/pkg/user/appointee/set_test.go new file mode 100644 index 00000000..14a0b95f --- /dev/null +++ b/pkg/user/appointee/set_test.go @@ -0,0 +1,107 @@ +package appointee + +import ( + "context" + "errors" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "testing" + + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + "github.com/stretchr/testify/assert" + + "github.com/urfave/cli/v2" +) + +type mockSetUserPermissionWriter struct { + setPermissionFunc func(ctx context.Context, request elcontracts.SetPermissionRequest) (*gethtypes.Receipt, error) +} + +func (m *mockSetUserPermissionWriter) SetPermission( + ctx context.Context, + request elcontracts.SetPermissionRequest, +) (*gethtypes.Receipt, error) { + return m.setPermissionFunc(ctx, request) +} + +func generateMockSetWriter(err error) func(logging.Logger, *setConfig) (SetUserPermissionWriter, error) { + return func(logger logging.Logger, config *setConfig) (SetUserPermissionWriter, error) { + return &mockSetUserPermissionWriter{ + setPermissionFunc: func(ctx context.Context, request elcontracts.SetPermissionRequest) (*gethtypes.Receipt, error) { + return &gethtypes.Receipt{}, err + }, + }, nil + } +} + +func TestSetCmd_Success(t *testing.T) { + app := cli.NewApp() + app.Commands = []*cli.Command{ + SetCmd(generateMockSetWriter(nil)), + } + + args := []string{ + "TestSetCmd_Success", + "set", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--appointee-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--target-address", "0x9876543210fedcba9876543210fedcba98765432", + "--selector", "0x1A2B3C4D", + "--network", "holesky", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestSetCmd_GeneratorError(t *testing.T) { + expectedError := "failed to create permission writer" + app := cli.NewApp() + app.Commands = []*cli.Command{ + SetCmd(func(logger logging.Logger, config *setConfig) (SetUserPermissionWriter, error) { + return nil, errors.New(expectedError) + }), + } + + args := []string{ + "TestSetCmd_GeneratorError", + "set", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--appointee-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--target-address", "0x9876543210fedcba9876543210fedcba98765432", + "--selector", "0x1A2B3C4D", + "--network", "holesky", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} + +func TestSetCmd_SetPermissionError(t *testing.T) { + expectedError := "error setting permission" + app := cli.NewApp() + app.Commands = []*cli.Command{ + SetCmd(generateMockSetWriter(errors.New(expectedError))), + } + + args := []string{ + "TestSetCmd_SetPermissionError", + "set", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--appointee-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--target-address", "0x9876543210fedcba9876543210fedcba98765432", + "--selector", "0x1A2B3C4D", + "--network", "holesky", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} diff --git a/pkg/user/appointee/types.go b/pkg/user/appointee/types.go index 9ec83ffc..6e17194f 100644 --- a/pkg/user/appointee/types.go +++ b/pkg/user/appointee/types.go @@ -3,6 +3,7 @@ package appointee import ( "math/big" + "github.com/Layr-Labs/eigenlayer-cli/pkg/types" gethcommon "github.com/ethereum/go-ethereum/common" ) @@ -39,3 +40,29 @@ type listUserPermissionsConfig struct { ChainID *big.Int Environment string } + +type removeConfig struct { + Network string + RPCUrl string + AccountAddress gethcommon.Address + UserAddress gethcommon.Address + Target gethcommon.Address + SignerConfig types.SignerConfig + Selector [4]byte + PermissionManagerAddress gethcommon.Address + ChainID *big.Int + Environment string +} + +type setConfig struct { + Network string + RPCUrl string + AccountAddress gethcommon.Address + UserAddress gethcommon.Address + Target gethcommon.Address + SignerConfig types.SignerConfig + Selector [4]byte + PermissionManagerAddress gethcommon.Address + ChainID *big.Int + Environment string +} diff --git a/pkg/users.go b/pkg/users.go index 44b09b51..99448433 100644 --- a/pkg/users.go +++ b/pkg/users.go @@ -3,16 +3,17 @@ package pkg import ( "github.com/Layr-Labs/eigenlayer-cli/pkg/user/admin" "github.com/Layr-Labs/eigenlayer-cli/pkg/user/appointee" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" "github.com/urfave/cli/v2" ) -func UserCmd() *cli.Command { +func UserCmd(prompter utils.Prompter) *cli.Command { var userCmd = &cli.Command{ Name: "user", Usage: "Manage user permissions", Subcommands: []*cli.Command{ admin.AdminCmd(), - appointee.AppointeeCmd(), + appointee.AppointeeCmd(prompter), }, } From 8cd2bbd2f9e0b8ce77da4f800110a3eb0fec96ba Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Wed, 11 Dec 2024 11:13:46 -0800 Subject: [PATCH 04/34] Adding admin read commands with unit tests. (#251) Co-authored-by: Brandon Chatham --- go.mod | 2 +- go.sum | 4 +- pkg/user/admin/admin.go | 8 +- pkg/user/admin/is_admin.go | 136 +++++++++++++++++++++++++-- pkg/user/admin/is_admin_test.go | 117 +++++++++++++++++++++++ pkg/user/admin/is_pending.go | 140 ++++++++++++++++++++++++++-- pkg/user/admin/is_pending_test.go | 120 ++++++++++++++++++++++++ pkg/user/admin/list.go | 131 +++++++++++++++++++++++++- pkg/user/admin/list_pending.go | 139 ++++++++++++++++++++++++++- pkg/user/admin/list_pending_test.go | 105 +++++++++++++++++++++ pkg/user/admin/list_test.go | 120 ++++++++++++++++++++++++ pkg/user/admin/types.go | 44 +++++++++ pkg/user/appointee/list.go | 2 +- 13 files changed, 1036 insertions(+), 32 deletions(-) create mode 100644 pkg/user/admin/is_admin_test.go create mode 100644 pkg/user/admin/is_pending_test.go create mode 100644 pkg/user/admin/list_pending_test.go create mode 100644 pkg/user/admin/list_test.go create mode 100644 pkg/user/admin/types.go diff --git a/go.mod b/go.mod index e16d3a04..decb1fba 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( //<<<<<<< HEAD // github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212190947-9985122d81fe //======= - github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210222107-c2ed40624db7 + github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210234612-fdae59339a81 github.com/blang/semver/v4 v4.0.0 github.com/consensys/gnark-crypto v0.12.1 github.com/ethereum/go-ethereum v1.14.5 diff --git a/go.sum b/go.sum index e01a1ba8..ced3d77e 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 h1:G5Q1SnLmFbEjhOkky3vIHk github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12/go.mod h1:OlJd1QjqEW53wfWG/lJyPCGvrXwWVEjPQsP4TV+gttQ= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e h1:DvW0/kWHV9mZsbH2KOjEHKTSIONNPUj6X05FJvUohy4= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e/go.mod h1:T7tYN8bTdca2pkMnz9G2+ZwXYWw5gWqQUIu4KLgC/vM= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210222107-c2ed40624db7 h1:1kehcGgMyVloGzrd36CSibYz+fC2BkKV0fqeYCpovIQ= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210222107-c2ed40624db7/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210234612-fdae59339a81 h1:max9ka+a5hx9/i/mbH1Y9GToXOCEtfsrt1BX02CAdYA= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210234612-fdae59339a81/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= diff --git a/pkg/user/admin/admin.go b/pkg/user/admin/admin.go index 653dbc83..a90ef861 100644 --- a/pkg/user/admin/admin.go +++ b/pkg/user/admin/admin.go @@ -21,10 +21,10 @@ func AdminCmd() *cli.Command { Subcommands: []*cli.Command{ AcceptCmd(), AddPendingCmd(), - IsAdminCmd(), - IsPendingCmd(), - ListCmd(), - ListPendingCmd(), + IsAdminCmd(generateIsAdminReader), + IsPendingCmd(generateIsPendingAdminReader), + ListCmd(generateListAdminsReader), + ListPendingCmd(generateListPendingAdminsReader), RemoveCmd(), RemovePendingCmd(), }, diff --git a/pkg/user/admin/is_admin.go b/pkg/user/admin/is_admin.go index 6bd2d385..d9fea46f 100644 --- a/pkg/user/admin/is_admin.go +++ b/pkg/user/admin/is_admin.go @@ -1,26 +1,146 @@ package admin import ( + "context" + "fmt" + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" ) -func IsAdminCmd() *cli.Command { - isAdmin := &cli.Command{ +type IsAdminReader interface { + IsAdmin( + ctx context.Context, + accountAddress gethcommon.Address, + pendingAdminAddress gethcommon.Address, + ) (bool, error) +} + +func IsAdminCmd(readerGenerator func(logging.Logger, *isAdminConfig) (IsAdminReader, error)) *cli.Command { + cmd := &cli.Command{ Name: "is-admin", Usage: "user admin is-admin --account-address --caller-address ", UsageText: "Checks if a user is an admin.", Description: ` Checks if a user is an admin. `, - After: telemetry.AfterRunAction(), - Flags: []cli.Flag{ - &flags.VerboseFlag, - &AccountAddressFlag, - &CallerAddressFlag, + Action: func(c *cli.Context) error { + return isAdmin(c, readerGenerator) }, + After: telemetry.AfterRunAction(), + Flags: IsAdminFlags(), + } + + return cmd +} + +func isAdmin(cliCtx *cli.Context, generator func(logging.Logger, *isAdminConfig) (IsAdminReader, error)) error { + ctx := cliCtx.Context + logger := common.GetLogger(cliCtx) + + config, err := readAndValidateIsAdminConfig(cliCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + } + cliCtx.App.Metadata["network"] = config.ChainID.String() + elReader, err := generator(logger, config) + if err != nil { + return err } - return isAdmin + result, err := elReader.IsAdmin(ctx, config.AccountAddress, config.AdminAddress) + if err != nil { + return err + } + printIsAdminResult(result) + return nil +} + +func readAndValidateIsAdminConfig(cliContext *cli.Context, logger logging.Logger) (*isAdminConfig, error) { + accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + adminAddress := gethcommon.HexToAddress(cliContext.String(AdminAddressFlag.Name)) + ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) + network := cliContext.String(flags.NetworkFlag.Name) + environment := cliContext.String(flags.EnvironmentFlag.Name) + if environment == "" { + environment = common.GetEnvFromNetwork(network) + } + + chainID := utils.NetworkNameToChainId(network) + permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + + var err error + if common.IsEmptyString(permissionManagerAddress) { + permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + + logger.Debugf( + "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + environment, + network, + chainID, + permissionManagerAddress, + ) + + return &isAdminConfig{ + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + AdminAddress: adminAddress, + PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), + ChainID: chainID, + Environment: environment, + }, nil +} + +func printIsAdminResult(result bool) { + if result { + fmt.Printf("Address provided is an admin.") + } else { + fmt.Printf("Address provided is not an admin.") + } +} + +func generateIsAdminReader(logger logging.Logger, config *isAdminConfig) (IsAdminReader, error) { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) + } + elReader, err := elcontracts.NewReaderFromConfig( + elcontracts.Config{ + PermissionsControllerAddress: config.PermissionManagerAddress, + }, + ethClient, + logger, + ) + return elReader, err +} + +func IsAdminFlags() []cli.Flag { + cmdFlags := []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + &CallerAddressFlag, + &flags.OutputTypeFlag, + &flags.OutputFileFlag, + &PermissionControllerAddressFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + } + sort.Sort(cli.FlagsByName(cmdFlags)) + return cmdFlags } diff --git a/pkg/user/admin/is_admin_test.go b/pkg/user/admin/is_admin_test.go new file mode 100644 index 00000000..f6c74aae --- /dev/null +++ b/pkg/user/admin/is_admin_test.go @@ -0,0 +1,117 @@ +package admin + +import ( + "context" + "errors" + "testing" + + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + + "github.com/urfave/cli/v2" +) + +type mockIsAdminReader struct { + isAdminFunc func(ctx context.Context, accountAddress gethcommon.Address, adminAddress gethcommon.Address) (bool, error) +} + +func (m *mockIsAdminReader) IsAdmin( + ctx context.Context, + accountAddress gethcommon.Address, + adminAddress gethcommon.Address, +) (bool, error) { + return m.isAdminFunc(ctx, accountAddress, adminAddress) +} + +func generateMockIsAdminReader(result bool, err error) func(logging.Logger, *isAdminConfig) (IsAdminReader, error) { + return func(logger logging.Logger, config *isAdminConfig) (IsAdminReader, error) { + return &mockIsAdminReader{ + isAdminFunc: func(ctx context.Context, accountAddress gethcommon.Address, adminAddress gethcommon.Address) (bool, error) { + return result, err + }, + }, nil + } +} + +func TestIsAdminCmd_Success(t *testing.T) { + app := cli.NewApp() + app.Commands = []*cli.Command{ + IsAdminCmd(generateMockIsAdminReader(true, nil)), + } + + args := []string{ + "TestIsAdminCmd_Success", + "is-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--caller-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestIsAdminCmd_NotAdmin(t *testing.T) { + app := cli.NewApp() + app.Commands = []*cli.Command{ + IsAdminCmd(generateMockIsAdminReader(false, nil)), + } + + args := []string{ + "TestIsAdminCmd_NotAdmin", + "is-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--caller-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestIsAdminCmd_GeneratorError(t *testing.T) { + expectedError := "failed to create admin reader" + app := cli.NewApp() + app.Commands = []*cli.Command{ + IsAdminCmd(func(logger logging.Logger, config *isAdminConfig) (IsAdminReader, error) { + return nil, errors.New(expectedError) + }), + } + + args := []string{ + "TestIsAdminCmd_GeneratorError", + "is-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--caller-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} + +func TestIsAdminCmd_IsAdminError(t *testing.T) { + expectedError := "error checking admin status" + app := cli.NewApp() + app.Commands = []*cli.Command{ + IsAdminCmd(generateMockIsAdminReader(false, errors.New(expectedError))), + } + + args := []string{ + "TestIsAdminCmd_IsAdminError", + "is-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--caller-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} diff --git a/pkg/user/admin/is_pending.go b/pkg/user/admin/is_pending.go index 72661139..b4f0c9ce 100644 --- a/pkg/user/admin/is_pending.go +++ b/pkg/user/admin/is_pending.go @@ -1,12 +1,34 @@ package admin import ( + "context" + "fmt" + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" ) -func IsPendingCmd() *cli.Command { +type IsPendingAdminReader interface { + IsPendingAdmin( + ctx context.Context, + accountAddress gethcommon.Address, + pendingAdminAddress gethcommon.Address, + ) (bool, error) +} + +func IsPendingCmd( + readerGenerator func(logging.Logger, *isPendingAdminConfig) (IsPendingAdminReader, error), +) *cli.Command { isPendingCmd := &cli.Command{ Name: "is-pending-admin", Usage: "user admin is-pending-admin --account-address --pending-admin-address ", @@ -14,13 +36,119 @@ func IsPendingCmd() *cli.Command { Description: ` Checks if a user is pending acceptance to admin. `, - After: telemetry.AfterRunAction(), - Flags: []cli.Flag{ - &flags.VerboseFlag, - &AccountAddressFlag, - &PendingAdminAddressFlag, + Action: func(c *cli.Context) error { + return isPendingAdmin(c, readerGenerator) }, + After: telemetry.AfterRunAction(), + Flags: isPendingAdminFlags(), } return isPendingCmd } + +func isPendingAdmin( + cliCtx *cli.Context, + generator func(logging.Logger, *isPendingAdminConfig) (IsPendingAdminReader, error), +) error { + ctx := cliCtx.Context + logger := common.GetLogger(cliCtx) + + config, err := readAndValidateIsPendingAdminConfig(cliCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + } + cliCtx.App.Metadata["network"] = config.ChainID.String() + elReader, err := generator(logger, config) + if err != nil { + return err + } + + result, err := elReader.IsPendingAdmin(ctx, config.AccountAddress, config.PendingAdminAddress) + if err != nil { + return err + } + printIsPendingAdminResult(result) + return nil +} + +func readAndValidateIsPendingAdminConfig( + cliContext *cli.Context, + logger logging.Logger, +) (*isPendingAdminConfig, error) { + accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + pendingAdminAddress := gethcommon.HexToAddress(cliContext.String(PendingAdminAddressFlag.Name)) + ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) + network := cliContext.String(flags.NetworkFlag.Name) + environment := cliContext.String(flags.EnvironmentFlag.Name) + if environment == "" { + environment = common.GetEnvFromNetwork(network) + } + + chainID := utils.NetworkNameToChainId(network) + permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + + var err error + if common.IsEmptyString(permissionManagerAddress) { + permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + + logger.Debugf( + "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + environment, + network, + chainID, + permissionManagerAddress, + ) + + return &isPendingAdminConfig{ + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + PendingAdminAddress: pendingAdminAddress, + PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), + ChainID: chainID, + Environment: environment, + }, nil +} + +func printIsPendingAdminResult(result bool) { + if result { + fmt.Printf("Address provided is a pending admin.") + } else { + fmt.Printf("Address provided is not a pending admin.") + } +} + +func generateIsPendingAdminReader(logger logging.Logger, config *isPendingAdminConfig) (IsPendingAdminReader, error) { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) + } + elReader, err := elcontracts.NewReaderFromConfig( + elcontracts.Config{ + PermissionsControllerAddress: config.PermissionManagerAddress, + }, + ethClient, + logger, + ) + return elReader, err +} + +func isPendingAdminFlags() []cli.Flag { + cmdFlags := []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + &PendingAdminAddressFlag, + &flags.OutputTypeFlag, + &flags.OutputFileFlag, + &PermissionControllerAddressFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + } + sort.Sort(cli.FlagsByName(cmdFlags)) + return cmdFlags +} diff --git a/pkg/user/admin/is_pending_test.go b/pkg/user/admin/is_pending_test.go new file mode 100644 index 00000000..9116ef63 --- /dev/null +++ b/pkg/user/admin/is_pending_test.go @@ -0,0 +1,120 @@ +package admin + +import ( + "context" + "errors" + "testing" + + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + + "github.com/urfave/cli/v2" +) + +type mockIsPendingAdminReader struct { + isPendingAdminFunc func(ctx context.Context, accountAddress gethcommon.Address, pendingAdminAddress gethcommon.Address) (bool, error) +} + +func (m *mockIsPendingAdminReader) IsPendingAdmin( + ctx context.Context, + accountAddress gethcommon.Address, + pendingAdminAddress gethcommon.Address, +) (bool, error) { + return m.isPendingAdminFunc(ctx, accountAddress, pendingAdminAddress) +} + +func generateMockIsPendingAdminReader( + result bool, + err error, +) func(logging.Logger, *isPendingAdminConfig) (IsPendingAdminReader, error) { + return func(logger logging.Logger, config *isPendingAdminConfig) (IsPendingAdminReader, error) { + return &mockIsPendingAdminReader{ + isPendingAdminFunc: func(ctx context.Context, accountAddress gethcommon.Address, pendingAdminAddress gethcommon.Address) (bool, error) { + return result, err + }, + }, nil + } +} + +func TestIsPendingCmd_Success(t *testing.T) { + app := cli.NewApp() + app.Commands = []*cli.Command{ + IsPendingCmd(generateMockIsPendingAdminReader(true, nil)), + } + + args := []string{ + "TestIsPendingCmd_Success", + "is-pending-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--pending-admin-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestIsPendingCmd_NotPending(t *testing.T) { + app := cli.NewApp() + app.Commands = []*cli.Command{ + IsPendingCmd(generateMockIsPendingAdminReader(false, nil)), + } + + args := []string{ + "TestIsPendingCmd_NotPending", + "is-pending-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--pending-admin-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestIsPendingCmd_GeneratorError(t *testing.T) { + expectedError := "failed to create pending admin reader" + app := cli.NewApp() + app.Commands = []*cli.Command{ + IsPendingCmd(func(logger logging.Logger, config *isPendingAdminConfig) (IsPendingAdminReader, error) { + return nil, errors.New(expectedError) + }), + } + + args := []string{ + "TestIsPendingCmd_GeneratorError", + "is-pending-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--pending-admin-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} + +func TestIsPendingCmd_IsPendingAdminError(t *testing.T) { + expectedError := "error checking pending admin status" + app := cli.NewApp() + app.Commands = []*cli.Command{ + IsPendingCmd(generateMockIsPendingAdminReader(false, errors.New(expectedError))), + } + + args := []string{ + "TestIsPendingCmd_IsPendingAdminError", + "is-pending-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--pending-admin-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} diff --git a/pkg/user/admin/list.go b/pkg/user/admin/list.go index b4fd204d..f85577ae 100644 --- a/pkg/user/admin/list.go +++ b/pkg/user/admin/list.go @@ -1,12 +1,32 @@ package admin import ( + "context" + "fmt" + "sort" + "strings" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" ) -func ListCmd() *cli.Command { +type ListAdminsReader interface { + ListAdmins( + ctx context.Context, + userAddress gethcommon.Address, + ) ([]gethcommon.Address, error) +} + +func ListCmd(readerGenerator func(logging.Logger, *listAdminsConfig) (ListAdminsReader, error)) *cli.Command { listCmd := &cli.Command{ Name: "list-admins", Usage: "user admin list-admins --account-address ", @@ -14,12 +34,113 @@ func ListCmd() *cli.Command { Description: ` List all users who are admins. `, - After: telemetry.AfterRunAction(), - Flags: []cli.Flag{ - &flags.VerboseFlag, - &AccountAddressFlag, + Action: func(c *cli.Context) error { + return listAdmins(c, readerGenerator) }, + After: telemetry.AfterRunAction(), + Flags: listAdminFlags(), } return listCmd } + +func listAdmins( + cliCtx *cli.Context, + generator func(logging.Logger, *listAdminsConfig) (ListAdminsReader, error), +) error { + ctx := cliCtx.Context + logger := common.GetLogger(cliCtx) + + config, err := readAndValidateListAdminsConfig(cliCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + } + cliCtx.App.Metadata["network"] = config.ChainID.String() + elReader, err := generator(logger, config) + if err != nil { + return err + } + + pendingAdmins, err := elReader.ListAdmins(ctx, config.AccountAddress) + if err != nil { + return err + } + printAdmins(config.AccountAddress, pendingAdmins) + return nil +} + +func printAdmins(account gethcommon.Address, admins []gethcommon.Address) { + fmt.Printf("Admins for Account: %s \n", account) + fmt.Println(strings.Repeat("=", 60)) + for _, admin := range admins { + fmt.Printf("%s \n", admin.String()) + } +} + +func readAndValidateListAdminsConfig(cliContext *cli.Context, logger logging.Logger) (*listAdminsConfig, error) { + accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) + network := cliContext.String(flags.NetworkFlag.Name) + environment := cliContext.String(flags.EnvironmentFlag.Name) + if environment == "" { + environment = common.GetEnvFromNetwork(network) + } + + chainID := utils.NetworkNameToChainId(network) + permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + + var err error + if common.IsEmptyString(permissionManagerAddress) { + permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + + logger.Debugf( + "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + environment, + network, + chainID, + permissionManagerAddress, + ) + + return &listAdminsConfig{ + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), + ChainID: chainID, + Environment: environment, + }, nil +} + +func generateListAdminsReader(logger logging.Logger, config *listAdminsConfig) (ListAdminsReader, error) { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) + } + elReader, err := elcontracts.NewReaderFromConfig( + elcontracts.Config{ + PermissionsControllerAddress: config.PermissionManagerAddress, + }, + ethClient, + logger, + ) + return elReader, err +} + +func listAdminFlags() []cli.Flag { + cmdFlags := []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + &flags.OutputFileFlag, + &flags.OutputTypeFlag, + &PermissionControllerAddressFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + } + sort.Sort(cli.FlagsByName(cmdFlags)) + return cmdFlags +} diff --git a/pkg/user/admin/list_pending.go b/pkg/user/admin/list_pending.go index b12f0c65..dbae1298 100644 --- a/pkg/user/admin/list_pending.go +++ b/pkg/user/admin/list_pending.go @@ -1,12 +1,34 @@ package admin import ( + "context" + "fmt" + "sort" + "strings" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" ) -func ListPendingCmd() *cli.Command { +type ListPendingAdminsReader interface { + ListPendingAdmins( + ctx context.Context, + userAddress gethcommon.Address, + ) ([]gethcommon.Address, error) +} + +func ListPendingCmd( + readerGenerator func(logging.Logger, *listPendingAdminsConfig) (ListPendingAdminsReader, error), +) *cli.Command { listPendingCmd := &cli.Command{ Name: "list-pending-admins", Usage: "user admin list-pending-admins --account-address ", @@ -14,12 +36,119 @@ func ListPendingCmd() *cli.Command { Description: ` List all users who are pending admin acceptance. `, - After: telemetry.AfterRunAction(), - Flags: []cli.Flag{ - &flags.VerboseFlag, - &AccountAddressFlag, + Action: func(c *cli.Context) error { + return listPendingAdmins(c, readerGenerator) }, + After: telemetry.AfterRunAction(), + Flags: listPendingAdminsFlags(), } return listPendingCmd } + +func listPendingAdmins( + cliCtx *cli.Context, + generator func(logging.Logger, *listPendingAdminsConfig) (ListPendingAdminsReader, error), +) error { + ctx := cliCtx.Context + logger := common.GetLogger(cliCtx) + + config, err := readAndValidateListPendingAdminsConfig(cliCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + } + cliCtx.App.Metadata["network"] = config.ChainID.String() + elReader, err := generator(logger, config) + if err != nil { + return err + } + + pendingAdmins, err := elReader.ListPendingAdmins(ctx, config.AccountAddress) + if err != nil { + return err + } + printPendingAdmins(config.AccountAddress, pendingAdmins) + return nil +} + +func printPendingAdmins(account gethcommon.Address, admins []gethcommon.Address) { + fmt.Printf("Pending Admins\n for Account: %s", account) + fmt.Println(strings.Repeat("=", 60)) + for _, admin := range admins { + fmt.Printf("%s \n", admin.String()) + } +} + +func readAndValidateListPendingAdminsConfig( + cliContext *cli.Context, + logger logging.Logger, +) (*listPendingAdminsConfig, error) { + accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) + network := cliContext.String(flags.NetworkFlag.Name) + environment := cliContext.String(flags.EnvironmentFlag.Name) + if environment == "" { + environment = common.GetEnvFromNetwork(network) + } + + chainID := utils.NetworkNameToChainId(network) + permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + + var err error + if common.IsEmptyString(permissionManagerAddress) { + permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + + logger.Debugf( + "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + environment, + network, + chainID, + permissionManagerAddress, + ) + + return &listPendingAdminsConfig{ + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), + ChainID: chainID, + Environment: environment, + }, nil +} + +func generateListPendingAdminsReader( + logger logging.Logger, + config *listPendingAdminsConfig, +) (ListPendingAdminsReader, error) { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) + } + elReader, err := elcontracts.NewReaderFromConfig( + elcontracts.Config{ + PermissionsControllerAddress: config.PermissionManagerAddress, + }, + ethClient, + logger, + ) + return elReader, err +} + +func listPendingAdminsFlags() []cli.Flag { + cmdFlags := []cli.Flag{ + &flags.VerboseFlag, + &AccountAddressFlag, + &flags.OutputTypeFlag, + &flags.OutputFileFlag, + &PermissionControllerAddressFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + } + sort.Sort(cli.FlagsByName(cmdFlags)) + return cmdFlags +} diff --git a/pkg/user/admin/list_pending_test.go b/pkg/user/admin/list_pending_test.go new file mode 100644 index 00000000..d6363fca --- /dev/null +++ b/pkg/user/admin/list_pending_test.go @@ -0,0 +1,105 @@ +package admin + +import ( + "context" + "errors" + "testing" + + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + + "github.com/urfave/cli/v2" +) + +type mockListPendingAdminsReader struct { + listPendingAdminsFunc func(ctx context.Context, userAddress gethcommon.Address) ([]gethcommon.Address, error) +} + +func (m *mockListPendingAdminsReader) ListPendingAdmins( + ctx context.Context, + userAddress gethcommon.Address, +) ([]gethcommon.Address, error) { + return m.listPendingAdminsFunc(ctx, userAddress) +} + +func generateMockListPendingAdminsReader( + admins []gethcommon.Address, + err error, +) func(logging.Logger, *listPendingAdminsConfig) (ListPendingAdminsReader, error) { + return func(logger logging.Logger, config *listPendingAdminsConfig) (ListPendingAdminsReader, error) { + return &mockListPendingAdminsReader{ + listPendingAdminsFunc: func(ctx context.Context, userAddress gethcommon.Address) ([]gethcommon.Address, error) { + return admins, err + }, + }, nil + } +} + +func TestListPendingCmd_Success(t *testing.T) { + expectedAdmins := []gethcommon.Address{ + gethcommon.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + gethcommon.HexToAddress("0xabcdef1234567890abcdef1234567890abcdef12"), + } + + app := cli.NewApp() + app.Commands = []*cli.Command{ + ListPendingCmd(generateMockListPendingAdminsReader(expectedAdmins, nil)), + } + + args := []string{ + "TestListPendingCmd_Success", + "list-pending-admins", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--permission-controller-address", "0xe4dB7125ef7a9D99F809B6b7788f75c8D84d8455", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestListPendingCmd_GeneratorError(t *testing.T) { + expectedError := "failed to create pending admins reader" + app := cli.NewApp() + app.Commands = []*cli.Command{ + ListPendingCmd(func(logger logging.Logger, config *listPendingAdminsConfig) (ListPendingAdminsReader, error) { + return nil, errors.New(expectedError) + }), + } + + args := []string{ + "TestListPendingCmd_GeneratorError", + "list-pending-admins", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--permission-controller-address", "0xe4dB7125ef7a9D99F809B6b7788f75c8D84d8455", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} + +func TestListPendingCmd_ListPendingError(t *testing.T) { + expectedError := "failed to fetch pending admins" + app := cli.NewApp() + app.Commands = []*cli.Command{ + ListPendingCmd(generateMockListPendingAdminsReader(nil, errors.New(expectedError))), + } + + args := []string{ + "TestListPendingCmd_ListPendingError", + "list-pending-admins", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--permission-controller-address", "0xe4dB7125ef7a9D99F809B6b7788f75c8D84d8455", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} diff --git a/pkg/user/admin/list_test.go b/pkg/user/admin/list_test.go new file mode 100644 index 00000000..d925d135 --- /dev/null +++ b/pkg/user/admin/list_test.go @@ -0,0 +1,120 @@ +package admin + +import ( + "context" + "errors" + "testing" + + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + + "github.com/urfave/cli/v2" +) + +type mockListAdminsReader struct { + listAdminsFunc func(ctx context.Context, userAddress gethcommon.Address) ([]gethcommon.Address, error) +} + +func (m *mockListAdminsReader) ListAdmins( + ctx context.Context, + userAddress gethcommon.Address, +) ([]gethcommon.Address, error) { + return m.listAdminsFunc(ctx, userAddress) +} + +func generateMockListAdminsReader( + admins []gethcommon.Address, + err error, +) func(logging.Logger, *listAdminsConfig) (ListAdminsReader, error) { + return func(logger logging.Logger, config *listAdminsConfig) (ListAdminsReader, error) { + return &mockListAdminsReader{ + listAdminsFunc: func(ctx context.Context, userAddress gethcommon.Address) ([]gethcommon.Address, error) { + return admins, err + }, + }, nil + } +} + +func TestListCmd_Success(t *testing.T) { + expectedAdmins := []gethcommon.Address{ + gethcommon.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + gethcommon.HexToAddress("0xabcdef1234567890abcdef1234567890abcdef12"), + } + + app := cli.NewApp() + app.Commands = []*cli.Command{ + ListCmd(generateMockListAdminsReader(expectedAdmins, nil)), + } + + args := []string{ + "TestListCmd_Success", + "list-admins", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestListCmd_NoAdmins(t *testing.T) { + app := cli.NewApp() + app.Commands = []*cli.Command{ + ListCmd(generateMockListAdminsReader([]gethcommon.Address{}, nil)), + } + + args := []string{ + "TestListCmd_NoAdmins", + "list-admins", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestListCmd_GeneratorError(t *testing.T) { + expectedError := "failed to create admin reader" + app := cli.NewApp() + app.Commands = []*cli.Command{ + ListCmd(func(logger logging.Logger, config *listAdminsConfig) (ListAdminsReader, error) { + return nil, errors.New(expectedError) + }), + } + + args := []string{ + "TestListCmd_GeneratorError", + "list-admins", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} + +func TestListCmd_ListAdminsError(t *testing.T) { + expectedError := "failed to fetch admins" + app := cli.NewApp() + app.Commands = []*cli.Command{ + ListCmd(generateMockListAdminsReader(nil, errors.New(expectedError))), + } + + args := []string{ + "TestListCmd_ListAdminsError", + "list-admins", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} diff --git a/pkg/user/admin/types.go b/pkg/user/admin/types.go new file mode 100644 index 00000000..bc13e9f5 --- /dev/null +++ b/pkg/user/admin/types.go @@ -0,0 +1,44 @@ +package admin + +import ( + gethcommon "github.com/ethereum/go-ethereum/common" + "math/big" +) + +type listPendingAdminsConfig struct { + Network string + RPCUrl string + AccountAddress gethcommon.Address + PermissionManagerAddress gethcommon.Address + ChainID *big.Int + Environment string +} + +type listAdminsConfig struct { + Network string + RPCUrl string + AccountAddress gethcommon.Address + PermissionManagerAddress gethcommon.Address + ChainID *big.Int + Environment string +} + +type isPendingAdminConfig struct { + Network string + RPCUrl string + AccountAddress gethcommon.Address + PendingAdminAddress gethcommon.Address + PermissionManagerAddress gethcommon.Address + ChainID *big.Int + Environment string +} + +type isAdminConfig struct { + Network string + RPCUrl string + AccountAddress gethcommon.Address + AdminAddress gethcommon.Address + PermissionManagerAddress gethcommon.Address + ChainID *big.Int + Environment string +} diff --git a/pkg/user/appointee/list.go b/pkg/user/appointee/list.go index a6278160..6377c3c7 100644 --- a/pkg/user/appointee/list.go +++ b/pkg/user/appointee/list.go @@ -77,7 +77,7 @@ func printResults(config *listUsersConfig, users []gethcommon.Address) { string(config.Selector[:]), config.UserAddress, ) - fmt.Println(strings.Repeat("-", 80)) + fmt.Println(strings.Repeat("=", 80)) for _, user := range users { fmt.Printf("User Id: 0x%b\n", user) From 64f623d22f7b7c524e70f4b43a90113d4135700c Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Wed, 11 Dec 2024 14:52:30 -0800 Subject: [PATCH 05/34] Adding implementation and tests for admin write commands. (#252) Co-authored-by: Brandon Chatham --- go.mod | 2 +- go.sum | 4 +- pkg/user/admin/accept.go | 125 ++++++++++++++++++++++- pkg/user/admin/accept_test.go | 106 ++++++++++++++++++++ pkg/user/admin/add_pending.go | 131 +++++++++++++++++++++++- pkg/user/admin/add_pending_test.go | 109 ++++++++++++++++++++ pkg/user/admin/admin.go | 11 ++- pkg/user/admin/flags.go | 2 +- pkg/user/admin/is_admin.go | 2 +- pkg/user/admin/is_pending.go | 2 +- pkg/user/admin/list.go | 4 +- pkg/user/admin/list_pending.go | 4 +- pkg/user/admin/remove.go | 131 +++++++++++++++++++++++- pkg/user/admin/remove_pending.go | 137 +++++++++++++++++++++++++- pkg/user/admin/remove_pending_test.go | 111 +++++++++++++++++++++ pkg/user/admin/remove_test.go | 109 ++++++++++++++++++++ pkg/user/admin/types.go | 44 +++++++++ pkg/user/appointee/list.go | 2 +- pkg/user/appointee/remove.go | 8 +- pkg/user/appointee/set.go | 4 +- pkg/users.go | 2 +- 21 files changed, 1021 insertions(+), 29 deletions(-) create mode 100644 pkg/user/admin/accept_test.go create mode 100644 pkg/user/admin/add_pending_test.go create mode 100644 pkg/user/admin/remove_pending_test.go create mode 100644 pkg/user/admin/remove_test.go diff --git a/go.mod b/go.mod index decb1fba..124b1094 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( //<<<<<<< HEAD // github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212190947-9985122d81fe //======= - github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210234612-fdae59339a81 + github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241211204646-f49e96f7ee7a github.com/blang/semver/v4 v4.0.0 github.com/consensys/gnark-crypto v0.12.1 github.com/ethereum/go-ethereum v1.14.5 diff --git a/go.sum b/go.sum index ced3d77e..d96a5d6a 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 h1:G5Q1SnLmFbEjhOkky3vIHk github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12/go.mod h1:OlJd1QjqEW53wfWG/lJyPCGvrXwWVEjPQsP4TV+gttQ= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e h1:DvW0/kWHV9mZsbH2KOjEHKTSIONNPUj6X05FJvUohy4= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e/go.mod h1:T7tYN8bTdca2pkMnz9G2+ZwXYWw5gWqQUIu4KLgC/vM= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210234612-fdae59339a81 h1:max9ka+a5hx9/i/mbH1Y9GToXOCEtfsrt1BX02CAdYA= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241210234612-fdae59339a81/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241211204646-f49e96f7ee7a h1:U1pibFpUsMfL8h9EGrHtm/z94nKKuEvLdWEpkCkCZr4= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241211204646-f49e96f7ee7a/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= diff --git a/pkg/user/admin/accept.go b/pkg/user/admin/accept.go index 9477ac92..d91468eb 100644 --- a/pkg/user/admin/accept.go +++ b/pkg/user/admin/accept.go @@ -1,14 +1,31 @@ package admin import ( + "context" "sort" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" ) -func AcceptCmd() *cli.Command { +type AcceptAdminWriter interface { + AcceptAdmin( + ctx context.Context, + request elcontracts.AcceptAdminRequest, + ) (*gethtypes.Receipt, error) +} + +func AcceptCmd(generator func(logging.Logger, *acceptAdminConfig) (AcceptAdminWriter, error)) *cli.Command { acceptCmd := &cli.Command{ Name: "accept-admin", Usage: "user admin accept-admin --account-address ", @@ -16,6 +33,9 @@ func AcceptCmd() *cli.Command { Description: ` Accepts a user to become admin who is currently pending admin acceptance. `, + Action: func(c *cli.Context) error { + return acceptAdmin(c, generator) + }, After: telemetry.AfterRunAction(), Flags: acceptFlags(), } @@ -23,11 +43,114 @@ func AcceptCmd() *cli.Command { return acceptCmd } +func acceptAdmin( + cliCtx *cli.Context, + generator func(logging.Logger, *acceptAdminConfig) (AcceptAdminWriter, error), +) error { + ctx := cliCtx.Context + logger := common.GetLogger(cliCtx) + + config, err := readAndValidateAcceptAdminConfig(cliCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate user admin accept config", err) + } + cliCtx.App.Metadata["network"] = config.ChainID.String() + elWriter, err := generator(logger, config) + if err != nil { + return err + } + + receipt, err := elWriter.AcceptAdmin( + ctx, + elcontracts.AcceptAdminRequest{AccountAddress: config.AccountAddress, WaitForReceipt: true}, + ) + if err != nil { + return err + } + common.PrintTransactionInfo(receipt.TxHash.String(), config.ChainID) + return nil +} + +func readAndValidateAcceptAdminConfig( + cliContext *cli.Context, + logger logging.Logger, +) (*acceptAdminConfig, error) { + accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) + network := cliContext.String(flags.NetworkFlag.Name) + environment := cliContext.String(flags.EnvironmentFlag.Name) + if environment == "" { + environment = common.GetEnvFromNetwork(network) + } + signerConfig, err := common.GetSignerConfig(cliContext, logger) + if err != nil { + // We don't want to throw error since people can still use it to generate the claim + // without broadcasting it + logger.Debugf("Failed to get signer config: %s", err) + } + + chainID := utils.NetworkNameToChainId(network) + permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + + if common.IsEmptyString(permissionManagerAddress) { + permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + + logger.Debugf( + "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + environment, + network, + chainID, + permissionManagerAddress, + ) + + return &acceptAdminConfig{ + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), + SignerConfig: *signerConfig, + ChainID: chainID, + Environment: environment, + }, nil +} + func acceptFlags() []cli.Flag { cmdFlags := []cli.Flag{ &flags.VerboseFlag, &AccountAddressFlag, + &flags.OutputTypeFlag, + &flags.OutputFileFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + &PermissionControllerAddressFlag, } sort.Sort(cli.FlagsByName(cmdFlags)) return append(cmdFlags, flags.GetSignerFlags()...) } + +func generateAcceptAdminWriter( + prompter utils.Prompter, +) func(logger logging.Logger, config *acceptAdminConfig) (AcceptAdminWriter, error) { + return func(logger logging.Logger, config *acceptAdminConfig) (AcceptAdminWriter, error) { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) + } + return common.GetELWriter( + config.AccountAddress, + &config.SignerConfig, + ethClient, + elcontracts.Config{ + PermissionsControllerAddress: config.PermissionManagerAddress, + }, + prompter, + config.ChainID, + logger, + ) + } +} diff --git a/pkg/user/admin/accept_test.go b/pkg/user/admin/accept_test.go new file mode 100644 index 00000000..45b8e0cd --- /dev/null +++ b/pkg/user/admin/accept_test.go @@ -0,0 +1,106 @@ +package admin + +import ( + "context" + "errors" + "testing" + + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/assert" + + "github.com/urfave/cli/v2" +) + +type mockAcceptAdminWriter struct { + acceptAdminFunc func(ctx context.Context, request elcontracts.AcceptAdminRequest) (*gethtypes.Receipt, error) +} + +func (m *mockAcceptAdminWriter) AcceptAdmin( + ctx context.Context, + request elcontracts.AcceptAdminRequest, +) (*gethtypes.Receipt, error) { + return m.acceptAdminFunc(ctx, request) +} + +func generateMockAcceptAdminWriter( + receipt *gethtypes.Receipt, + err error, +) func(logging.Logger, *acceptAdminConfig) (AcceptAdminWriter, error) { + return func(logger logging.Logger, config *acceptAdminConfig) (AcceptAdminWriter, error) { + return &mockAcceptAdminWriter{ + acceptAdminFunc: func(ctx context.Context, request elcontracts.AcceptAdminRequest) (*gethtypes.Receipt, error) { + return receipt, err + }, + }, nil + } +} + +func TestAcceptCmd_Success(t *testing.T) { + mockReceipt := &gethtypes.Receipt{ + TxHash: gethcommon.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), + } + + app := cli.NewApp() + app.Commands = []*cli.Command{ + AcceptCmd(generateMockAcceptAdminWriter(mockReceipt, nil)), + } + + args := []string{ + "TestAcceptCmd_Success", + "accept-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestAcceptCmd_GeneratorError(t *testing.T) { + expectedError := "failed to create admin writer" + app := cli.NewApp() + app.Commands = []*cli.Command{ + AcceptCmd(func(logger logging.Logger, config *acceptAdminConfig) (AcceptAdminWriter, error) { + return nil, errors.New(expectedError) + }), + } + + args := []string{ + "TestAcceptCmd_GeneratorError", + "accept-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} + +func TestAcceptCmd_AcceptAdminError(t *testing.T) { + expectedError := "error accepting admin" + app := cli.NewApp() + app.Commands = []*cli.Command{ + AcceptCmd(generateMockAcceptAdminWriter(nil, errors.New(expectedError))), + } + + args := []string{ + "TestAcceptCmd_AcceptAdminError", + "accept-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} diff --git a/pkg/user/admin/add_pending.go b/pkg/user/admin/add_pending.go index a0b2bdd2..5c5d4ddf 100644 --- a/pkg/user/admin/add_pending.go +++ b/pkg/user/admin/add_pending.go @@ -1,14 +1,31 @@ package admin import ( + "context" "sort" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" ) -func AddPendingCmd() *cli.Command { +type AddPendingAdminWriter interface { + AddPendingAdmin( + ctx context.Context, + request elcontracts.AddPendingAdminRequest, + ) (*gethtypes.Receipt, error) +} + +func AddPendingCmd(generator func(logging.Logger, *addPendingAdminConfig) (AddPendingAdminWriter, error)) *cli.Command { addPendingCmd := &cli.Command{ Name: "add-pending-admin", Usage: "user admin add-pending-admin --account-address --admin-address ", @@ -16,6 +33,9 @@ func AddPendingCmd() *cli.Command { Description: ` Add an admin to be pending until accepted. `, + Action: func(context *cli.Context) error { + return addPendingAdmin(context, generator) + }, After: telemetry.AfterRunAction(), Flags: addPendingFlags(), } @@ -23,11 +43,120 @@ func AddPendingCmd() *cli.Command { return addPendingCmd } +func addPendingAdmin( + cliCtx *cli.Context, + generator func(logging.Logger, *addPendingAdminConfig) (AddPendingAdminWriter, error), +) error { + ctx := cliCtx.Context + logger := common.GetLogger(cliCtx) + + config, err := readAndValidateAddPendingAdminConfig(cliCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate user admin add pending config", err) + } + cliCtx.App.Metadata["network"] = config.ChainID.String() + elWriter, err := generator(logger, config) + if err != nil { + return err + } + + receipt, err := elWriter.AddPendingAdmin( + ctx, + elcontracts.AddPendingAdminRequest{ + AccountAddress: config.AccountAddress, + AdminAddress: config.AdminAddress, + WaitForReceipt: true, + }, + ) + if err != nil { + return err + } + common.PrintTransactionInfo(receipt.TxHash.String(), config.ChainID) + return nil +} + +func readAndValidateAddPendingAdminConfig( + cliContext *cli.Context, + logger logging.Logger, +) (*addPendingAdminConfig, error) { + accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + adminAddress := gethcommon.HexToAddress(cliContext.String(AdminAddressFlag.Name)) + ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) + network := cliContext.String(flags.NetworkFlag.Name) + environment := cliContext.String(flags.EnvironmentFlag.Name) + if environment == "" { + environment = common.GetEnvFromNetwork(network) + } + signerConfig, err := common.GetSignerConfig(cliContext, logger) + if err != nil { + // We don't want to throw error since people can still use it to generate the claim + // without broadcasting it + logger.Debugf("Failed to get signer config: %s", err) + } + + chainID := utils.NetworkNameToChainId(network) + permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + + if common.IsEmptyString(permissionManagerAddress) { + permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + + logger.Debugf( + "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + environment, + network, + chainID, + permissionManagerAddress, + ) + + return &addPendingAdminConfig{ + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + AdminAddress: adminAddress, + SignerConfig: *signerConfig, + PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), + ChainID: chainID, + Environment: environment, + }, nil +} + +func generateAddPendingAdminWriter( + prompter utils.Prompter, +) func(logger logging.Logger, config *addPendingAdminConfig) (AddPendingAdminWriter, error) { + return func(logger logging.Logger, config *addPendingAdminConfig) (AddPendingAdminWriter, error) { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) + } + return common.GetELWriter( + config.AccountAddress, + &config.SignerConfig, + ethClient, + elcontracts.Config{ + PermissionsControllerAddress: config.PermissionManagerAddress, + }, + prompter, + config.ChainID, + logger, + ) + } +} + func addPendingFlags() []cli.Flag { cmdFlags := []cli.Flag{ &flags.VerboseFlag, &AccountAddressFlag, &AdminAddressFlag, + &flags.OutputTypeFlag, + &flags.OutputFileFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + &PermissionControllerAddressFlag, } sort.Sort(cli.FlagsByName(cmdFlags)) return append(cmdFlags, flags.GetSignerFlags()...) diff --git a/pkg/user/admin/add_pending_test.go b/pkg/user/admin/add_pending_test.go new file mode 100644 index 00000000..a9e8dce0 --- /dev/null +++ b/pkg/user/admin/add_pending_test.go @@ -0,0 +1,109 @@ +package admin + +import ( + "context" + "errors" + "testing" + + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v2" +) + +type mockAddPendingAdminWriter struct { + addPendingAdminFunc func(ctx context.Context, request elcontracts.AddPendingAdminRequest) (*gethtypes.Receipt, error) +} + +func (m *mockAddPendingAdminWriter) AddPendingAdmin( + ctx context.Context, + request elcontracts.AddPendingAdminRequest, +) (*gethtypes.Receipt, error) { + return m.addPendingAdminFunc(ctx, request) +} + +func generateMockAddPendingAdminWriter( + receipt *gethtypes.Receipt, + err error, +) func(logging.Logger, *addPendingAdminConfig) (AddPendingAdminWriter, error) { + return func(logger logging.Logger, config *addPendingAdminConfig) (AddPendingAdminWriter, error) { + return &mockAddPendingAdminWriter{ + addPendingAdminFunc: func(ctx context.Context, request elcontracts.AddPendingAdminRequest) (*gethtypes.Receipt, error) { + return receipt, err + }, + }, nil + } +} + +func TestAddPendingCmd_Success(t *testing.T) { + mockReceipt := &gethtypes.Receipt{ + TxHash: gethcommon.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), + } + + app := cli.NewApp() + app.Commands = []*cli.Command{ + AddPendingCmd(generateMockAddPendingAdminWriter(mockReceipt, nil)), + } + + args := []string{ + "TestAddPendingCmd_Success", + "add-pending-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--admin-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestAddPendingCmd_GeneratorError(t *testing.T) { + expectedError := "failed to create admin writer" + app := cli.NewApp() + app.Commands = []*cli.Command{ + AddPendingCmd(func(logger logging.Logger, config *addPendingAdminConfig) (AddPendingAdminWriter, error) { + return nil, errors.New(expectedError) + }), + } + + args := []string{ + "TestAddPendingCmd_GeneratorError", + "add-pending-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--admin-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} + +func TestAddPendingCmd_AddPendingError(t *testing.T) { + expectedError := "error adding pending admin" + app := cli.NewApp() + app.Commands = []*cli.Command{ + AddPendingCmd(generateMockAddPendingAdminWriter(nil, errors.New(expectedError))), + } + + args := []string{ + "TestAddPendingCmd_AddPendingError", + "add-pending-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--admin-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} diff --git a/pkg/user/admin/admin.go b/pkg/user/admin/admin.go index a90ef861..d512e7b4 100644 --- a/pkg/user/admin/admin.go +++ b/pkg/user/admin/admin.go @@ -3,10 +3,11 @@ package admin import ( "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" "github.com/urfave/cli/v2" ) -func AdminCmd() *cli.Command { +func AdminCmd(prompter utils.Prompter) *cli.Command { adminCmd := &cli.Command{ Name: "admin", Usage: "user admin ", @@ -19,14 +20,14 @@ func AdminCmd() *cli.Command { &flags.VerboseFlag, }, Subcommands: []*cli.Command{ - AcceptCmd(), - AddPendingCmd(), + AcceptCmd(generateAcceptAdminWriter(prompter)), + AddPendingCmd(generateAddPendingAdminWriter(prompter)), IsAdminCmd(generateIsAdminReader), IsPendingCmd(generateIsPendingAdminReader), ListCmd(generateListAdminsReader), ListPendingCmd(generateListPendingAdminsReader), - RemoveCmd(), - RemovePendingCmd(), + RemoveCmd(generateRemoveAdminWriter(prompter)), + RemovePendingCmd(generateRemovePendingAdminWriter(prompter)), }, } diff --git a/pkg/user/admin/flags.go b/pkg/user/admin/flags.go index 4cec13cd..cea56c79 100644 --- a/pkg/user/admin/flags.go +++ b/pkg/user/admin/flags.go @@ -11,7 +11,7 @@ var ( } AdminAddressFlag = cli.StringFlag{ Name: "admin-address", - Aliases: []string{"aa"}, + Aliases: []string{"ada"}, Usage: "user admin ... --admin-address \"0x...\"", EnvVars: []string{"ADMIN_ADDRESS"}, } diff --git a/pkg/user/admin/is_admin.go b/pkg/user/admin/is_admin.go index d9fea46f..e29ca22d 100644 --- a/pkg/user/admin/is_admin.go +++ b/pkg/user/admin/is_admin.go @@ -50,7 +50,7 @@ func isAdmin(cliCtx *cli.Context, generator func(logging.Logger, *isAdminConfig) config, err := readAndValidateIsAdminConfig(cliCtx, logger) if err != nil { - return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + return eigenSdkUtils.WrapError("failed to read and validate user admin is admin config", err) } cliCtx.App.Metadata["network"] = config.ChainID.String() elReader, err := generator(logger, config) diff --git a/pkg/user/admin/is_pending.go b/pkg/user/admin/is_pending.go index b4f0c9ce..3c224c62 100644 --- a/pkg/user/admin/is_pending.go +++ b/pkg/user/admin/is_pending.go @@ -55,7 +55,7 @@ func isPendingAdmin( config, err := readAndValidateIsPendingAdminConfig(cliCtx, logger) if err != nil { - return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + return eigenSdkUtils.WrapError("failed to read and validate user admin is pending config", err) } cliCtx.App.Metadata["network"] = config.ChainID.String() elReader, err := generator(logger, config) diff --git a/pkg/user/admin/list.go b/pkg/user/admin/list.go index f85577ae..c4423864 100644 --- a/pkg/user/admin/list.go +++ b/pkg/user/admin/list.go @@ -53,7 +53,7 @@ func listAdmins( config, err := readAndValidateListAdminsConfig(cliCtx, logger) if err != nil { - return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + return eigenSdkUtils.WrapError("failed to read and validate user admin list config", err) } cliCtx.App.Metadata["network"] = config.ChainID.String() elReader, err := generator(logger, config) @@ -70,7 +70,7 @@ func listAdmins( } func printAdmins(account gethcommon.Address, admins []gethcommon.Address) { - fmt.Printf("Admins for Account: %s \n", account) + fmt.Printf("Admins for AccountAddress: %s \n", account) fmt.Println(strings.Repeat("=", 60)) for _, admin := range admins { fmt.Printf("%s \n", admin.String()) diff --git a/pkg/user/admin/list_pending.go b/pkg/user/admin/list_pending.go index dbae1298..bf1b60ae 100644 --- a/pkg/user/admin/list_pending.go +++ b/pkg/user/admin/list_pending.go @@ -55,7 +55,7 @@ func listPendingAdmins( config, err := readAndValidateListPendingAdminsConfig(cliCtx, logger) if err != nil { - return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + return eigenSdkUtils.WrapError("failed to read and validate user admin list pending config", err) } cliCtx.App.Metadata["network"] = config.ChainID.String() elReader, err := generator(logger, config) @@ -72,7 +72,7 @@ func listPendingAdmins( } func printPendingAdmins(account gethcommon.Address, admins []gethcommon.Address) { - fmt.Printf("Pending Admins\n for Account: %s", account) + fmt.Printf("Pending Admins\n for AccountAddress: %s", account) fmt.Println(strings.Repeat("=", 60)) for _, admin := range admins { fmt.Printf("%s \n", admin.String()) diff --git a/pkg/user/admin/remove.go b/pkg/user/admin/remove.go index cf8600f2..4c0cd963 100644 --- a/pkg/user/admin/remove.go +++ b/pkg/user/admin/remove.go @@ -1,14 +1,31 @@ package admin import ( + "context" "sort" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" ) -func RemoveCmd() *cli.Command { +type RemoveAdminWriter interface { + RemoveAdmin( + ctx context.Context, + request elcontracts.RemoveAdminRequest, + ) (*gethtypes.Receipt, error) +} + +func RemoveCmd(generator func(logging.Logger, *removeAdminConfig) (RemoveAdminWriter, error)) *cli.Command { removeCmd := &cli.Command{ Name: "remove-admin", Usage: "user admin remove-admin --account-address --admin-address ", @@ -16,6 +33,9 @@ func RemoveCmd() *cli.Command { Description: ` The remove command allows you to remove an admin user. `, + Action: func(context *cli.Context) error { + return removeAdmin(context, generator) + }, After: telemetry.AfterRunAction(), Flags: removeFlags(), } @@ -23,11 +43,120 @@ func RemoveCmd() *cli.Command { return removeCmd } +func removeAdmin( + cliCtx *cli.Context, + generator func(logging.Logger, *removeAdminConfig) (RemoveAdminWriter, error), +) error { + ctx := cliCtx.Context + logger := common.GetLogger(cliCtx) + + config, err := readAndValidateRemoveAdminConfig(cliCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate user admin remove config", err) + } + cliCtx.App.Metadata["network"] = config.ChainID.String() + elWriter, err := generator(logger, config) + if err != nil { + return err + } + + receipt, err := elWriter.RemoveAdmin( + ctx, + elcontracts.RemoveAdminRequest{ + AccountAddress: config.AccountAddress, + AdminAddress: config.AdminAddress, + WaitForReceipt: true, + }, + ) + if err != nil { + return err + } + common.PrintTransactionInfo(receipt.TxHash.String(), config.ChainID) + return nil +} + +func readAndValidateRemoveAdminConfig( + cliContext *cli.Context, + logger logging.Logger, +) (*removeAdminConfig, error) { + accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + adminAddress := gethcommon.HexToAddress(cliContext.String(AdminAddressFlag.Name)) + ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) + network := cliContext.String(flags.NetworkFlag.Name) + environment := cliContext.String(flags.EnvironmentFlag.Name) + if environment == "" { + environment = common.GetEnvFromNetwork(network) + } + signerConfig, err := common.GetSignerConfig(cliContext, logger) + if err != nil { + // We don't want to throw error since people can still use it to generate the claim + // without broadcasting it + logger.Debugf("Failed to get signer config: %s", err) + } + + chainID := utils.NetworkNameToChainId(network) + permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + + if common.IsEmptyString(permissionManagerAddress) { + permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + + logger.Debugf( + "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + environment, + network, + chainID, + permissionManagerAddress, + ) + + return &removeAdminConfig{ + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + AdminAddress: adminAddress, + PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), + SignerConfig: *signerConfig, + ChainID: chainID, + Environment: environment, + }, nil +} + +func generateRemoveAdminWriter( + prompter utils.Prompter, +) func(logger logging.Logger, config *removeAdminConfig) (RemoveAdminWriter, error) { + return func(logger logging.Logger, config *removeAdminConfig) (RemoveAdminWriter, error) { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) + } + return common.GetELWriter( + config.AccountAddress, + &config.SignerConfig, + ethClient, + elcontracts.Config{ + PermissionsControllerAddress: config.PermissionManagerAddress, + }, + prompter, + config.ChainID, + logger, + ) + } +} + func removeFlags() []cli.Flag { cmdFlags := []cli.Flag{ &flags.VerboseFlag, &AccountAddressFlag, &AdminAddressFlag, + &flags.OutputTypeFlag, + &flags.OutputFileFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + &PermissionControllerAddressFlag, } sort.Sort(cli.FlagsByName(cmdFlags)) return append(cmdFlags, flags.GetSignerFlags()...) diff --git a/pkg/user/admin/remove_pending.go b/pkg/user/admin/remove_pending.go index 9795f32e..d9401028 100644 --- a/pkg/user/admin/remove_pending.go +++ b/pkg/user/admin/remove_pending.go @@ -1,14 +1,33 @@ package admin import ( + "context" "sort" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" ) -func RemovePendingCmd() *cli.Command { +type RemovePendingAdminWriter interface { + RemovePendingAdmin( + ctx context.Context, + request elcontracts.RemovePendingAdminRequest, + ) (*gethtypes.Receipt, error) +} + +func RemovePendingCmd( + generator func(logging.Logger, *removePendingAdminConfig) (RemovePendingAdminWriter, error), +) *cli.Command { removeCmd := &cli.Command{ Name: "remove-pending-admin", Usage: "user admin remove-pending-admin --account-address --admin-address ", @@ -16,18 +35,130 @@ func RemovePendingCmd() *cli.Command { Description: ` Remove a user who is pending admin acceptance. `, + Action: func(context *cli.Context) error { + return removePendingAdmin(context, generator) + }, After: telemetry.AfterRunAction(), - Flags: removePendingFlags(), + Flags: removePendingAdminFlags(), } return removeCmd } -func removePendingFlags() []cli.Flag { +func removePendingAdmin( + cliCtx *cli.Context, + generator func(logging.Logger, *removePendingAdminConfig) (RemovePendingAdminWriter, error), +) error { + ctx := cliCtx.Context + logger := common.GetLogger(cliCtx) + + config, err := readAndValidateRemovePendingAdminConfig(cliCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate user admin remove pending config", err) + } + cliCtx.App.Metadata["network"] = config.ChainID.String() + elWriter, err := generator(logger, config) + if err != nil { + return err + } + + receipt, err := elWriter.RemovePendingAdmin( + ctx, + elcontracts.RemovePendingAdminRequest{ + AccountAddress: config.AccountAddress, + AdminAddress: config.AdminAddress, + WaitForReceipt: true, + }, + ) + if err != nil { + return err + } + common.PrintTransactionInfo(receipt.TxHash.String(), config.ChainID) + return nil +} + +func readAndValidateRemovePendingAdminConfig( + cliContext *cli.Context, + logger logging.Logger, +) (*removePendingAdminConfig, error) { + accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + adminAddress := gethcommon.HexToAddress(cliContext.String(AdminAddressFlag.Name)) + ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) + network := cliContext.String(flags.NetworkFlag.Name) + environment := cliContext.String(flags.EnvironmentFlag.Name) + if environment == "" { + environment = common.GetEnvFromNetwork(network) + } + signerConfig, err := common.GetSignerConfig(cliContext, logger) + if err != nil { + // We don't want to throw error since people can still use it to generate the claim + // without broadcasting it + logger.Debugf("Failed to get signer config: %s", err) + } + + chainID := utils.NetworkNameToChainId(network) + permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + + if common.IsEmptyString(permissionManagerAddress) { + permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + + logger.Debugf( + "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + environment, + network, + chainID, + permissionManagerAddress, + ) + + return &removePendingAdminConfig{ + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + AdminAddress: adminAddress, + PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), + SignerConfig: *signerConfig, + ChainID: chainID, + Environment: environment, + }, nil +} + +func generateRemovePendingAdminWriter( + prompter utils.Prompter, +) func(logger logging.Logger, config *removePendingAdminConfig) (RemovePendingAdminWriter, error) { + return func(logger logging.Logger, config *removePendingAdminConfig) (RemovePendingAdminWriter, error) { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) + } + return common.GetELWriter( + config.AccountAddress, + &config.SignerConfig, + ethClient, + elcontracts.Config{ + PermissionsControllerAddress: config.PermissionManagerAddress, + }, + prompter, + config.ChainID, + logger, + ) + } +} + +func removePendingAdminFlags() []cli.Flag { cmdFlags := []cli.Flag{ &flags.VerboseFlag, &AccountAddressFlag, &AdminAddressFlag, + &flags.OutputTypeFlag, + &flags.OutputFileFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + &PermissionControllerAddressFlag, } sort.Sort(cli.FlagsByName(cmdFlags)) return append(cmdFlags, flags.GetSignerFlags()...) diff --git a/pkg/user/admin/remove_pending_test.go b/pkg/user/admin/remove_pending_test.go new file mode 100644 index 00000000..ee292a7e --- /dev/null +++ b/pkg/user/admin/remove_pending_test.go @@ -0,0 +1,111 @@ +package admin + +import ( + "context" + "errors" + "testing" + + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v2" +) + +type mockRemovePendingAdminWriter struct { + removePendingAdminFunc func(ctx context.Context, request elcontracts.RemovePendingAdminRequest) (*gethtypes.Receipt, error) +} + +func (m *mockRemovePendingAdminWriter) RemovePendingAdmin( + ctx context.Context, + request elcontracts.RemovePendingAdminRequest, +) (*gethtypes.Receipt, error) { + return m.removePendingAdminFunc(ctx, request) +} + +func generateMockRemovePendingAdminWriter( + receipt *gethtypes.Receipt, + err error, +) func(logging.Logger, *removePendingAdminConfig) (RemovePendingAdminWriter, error) { + return func(logger logging.Logger, config *removePendingAdminConfig) (RemovePendingAdminWriter, error) { + return &mockRemovePendingAdminWriter{ + removePendingAdminFunc: func(ctx context.Context, request elcontracts.RemovePendingAdminRequest) (*gethtypes.Receipt, error) { + return receipt, err + }, + }, nil + } +} + +func TestRemovePendingCmd_Success(t *testing.T) { + mockReceipt := &gethtypes.Receipt{ + TxHash: gethcommon.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), + } + + app := cli.NewApp() + app.Commands = []*cli.Command{ + RemovePendingCmd(generateMockRemovePendingAdminWriter(mockReceipt, nil)), + } + + args := []string{ + "TestRemovePendingCmd_Success", + "remove-pending-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--admin-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestRemovePendingCmd_GeneratorError(t *testing.T) { + expectedError := "failed to create admin writer" + app := cli.NewApp() + app.Commands = []*cli.Command{ + RemovePendingCmd( + func(logger logging.Logger, config *removePendingAdminConfig) (RemovePendingAdminWriter, error) { + return nil, errors.New(expectedError) + }, + ), + } + + args := []string{ + "TestRemovePendingCmd_GeneratorError", + "remove-pending-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--admin-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} + +func TestRemovePendingCmd_RemovePendingError(t *testing.T) { + expectedError := "error removing pending admin" + app := cli.NewApp() + app.Commands = []*cli.Command{ + RemovePendingCmd(generateMockRemovePendingAdminWriter(nil, errors.New(expectedError))), + } + + args := []string{ + "TestRemovePendingCmd_RemovePendingError", + "remove-pending-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--admin-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} diff --git a/pkg/user/admin/remove_test.go b/pkg/user/admin/remove_test.go new file mode 100644 index 00000000..0ba4a735 --- /dev/null +++ b/pkg/user/admin/remove_test.go @@ -0,0 +1,109 @@ +package admin + +import ( + "context" + "errors" + "testing" + + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v2" +) + +type mockRemoveAdminWriter struct { + removeAdminFunc func(ctx context.Context, request elcontracts.RemoveAdminRequest) (*gethtypes.Receipt, error) +} + +func (m *mockRemoveAdminWriter) RemoveAdmin( + ctx context.Context, + request elcontracts.RemoveAdminRequest, +) (*gethtypes.Receipt, error) { + return m.removeAdminFunc(ctx, request) +} + +func generateMockRemoveAdminWriter( + receipt *gethtypes.Receipt, + err error, +) func(logging.Logger, *removeAdminConfig) (RemoveAdminWriter, error) { + return func(logger logging.Logger, config *removeAdminConfig) (RemoveAdminWriter, error) { + return &mockRemoveAdminWriter{ + removeAdminFunc: func(ctx context.Context, request elcontracts.RemoveAdminRequest) (*gethtypes.Receipt, error) { + return receipt, err + }, + }, nil + } +} + +func TestRemoveCmd_Success(t *testing.T) { + mockReceipt := &gethtypes.Receipt{ + TxHash: gethcommon.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), + } + + app := cli.NewApp() + app.Commands = []*cli.Command{ + RemoveCmd(generateMockRemoveAdminWriter(mockReceipt, nil)), + } + + args := []string{ + "TestRemoveCmd_Success", + "remove-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--admin-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.NoError(t, err) +} + +func TestRemoveCmd_GeneratorError(t *testing.T) { + expectedError := "failed to create admin writer" + app := cli.NewApp() + app.Commands = []*cli.Command{ + RemoveCmd(func(logger logging.Logger, config *removeAdminConfig) (RemoveAdminWriter, error) { + return nil, errors.New(expectedError) + }), + } + + args := []string{ + "TestRemoveCmd_GeneratorError", + "remove-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--admin-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} + +func TestRemoveCmd_RemoveAdminError(t *testing.T) { + expectedError := "error removing admin" + app := cli.NewApp() + app.Commands = []*cli.Command{ + RemoveCmd(generateMockRemoveAdminWriter(nil, errors.New(expectedError))), + } + + args := []string{ + "TestRemoveCmd_RemoveAdminError", + "remove-admin", + "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--admin-address", "0x1234567890abcdef1234567890abcdef12345678", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--network", "holesky", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} diff --git a/pkg/user/admin/types.go b/pkg/user/admin/types.go index bc13e9f5..a0e73378 100644 --- a/pkg/user/admin/types.go +++ b/pkg/user/admin/types.go @@ -1,6 +1,7 @@ package admin import ( + "github.com/Layr-Labs/eigenlayer-cli/pkg/types" gethcommon "github.com/ethereum/go-ethereum/common" "math/big" ) @@ -42,3 +43,46 @@ type isAdminConfig struct { ChainID *big.Int Environment string } + +type acceptAdminConfig struct { + Network string + RPCUrl string + AccountAddress gethcommon.Address + PermissionManagerAddress gethcommon.Address + SignerConfig types.SignerConfig + ChainID *big.Int + Environment string +} + +type addPendingAdminConfig struct { + Network string + RPCUrl string + AccountAddress gethcommon.Address + AdminAddress gethcommon.Address + PermissionManagerAddress gethcommon.Address + SignerConfig types.SignerConfig + ChainID *big.Int + Environment string +} + +type removeAdminConfig struct { + Network string + RPCUrl string + AccountAddress gethcommon.Address + AdminAddress gethcommon.Address + PermissionManagerAddress gethcommon.Address + SignerConfig types.SignerConfig + ChainID *big.Int + Environment string +} + +type removePendingAdminConfig struct { + Network string + RPCUrl string + AccountAddress gethcommon.Address + AdminAddress gethcommon.Address + PermissionManagerAddress gethcommon.Address + SignerConfig types.SignerConfig + ChainID *big.Int + Environment string +} diff --git a/pkg/user/appointee/list.go b/pkg/user/appointee/list.go index 6377c3c7..c3487411 100644 --- a/pkg/user/appointee/list.go +++ b/pkg/user/appointee/list.go @@ -54,7 +54,7 @@ func listAppointees( config, err := readAndValidateListUsersConfig(cliCtx, logger) if err != nil { - return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + return eigenSdkUtils.WrapError("failed to read and validate user appointee list config", err) } elReader, err := generator(logger, config) diff --git a/pkg/user/appointee/remove.go b/pkg/user/appointee/remove.go index 0c7c3b54..0026a3ee 100644 --- a/pkg/user/appointee/remove.go +++ b/pkg/user/appointee/remove.go @@ -25,7 +25,7 @@ type RemoveUserPermissionWriter interface { ) (*gethtypes.Receipt, error) } -func RemoveCmd(readerGenerator func(logging.Logger, *removeConfig) (RemoveUserPermissionWriter, error)) *cli.Command { +func RemoveCmd(generator func(logging.Logger, *removeConfig) (RemoveUserPermissionWriter, error)) *cli.Command { removeCmd := &cli.Command{ Name: "remove", Usage: "user appointee remove --account-address --appointee-address --target-address --selector ", @@ -35,7 +35,7 @@ func RemoveCmd(readerGenerator func(logging.Logger, *removeConfig) (RemoveUserPe `, After: telemetry.AfterRunAction(), Action: func(c *cli.Context) error { - return removeUserPermission(c, readerGenerator) + return removeUserPermission(c, generator) }, Flags: removeCommandFlags(), } @@ -52,7 +52,7 @@ func removeUserPermission( config, err := readAndValidateRemoveConfig(cliCtx, logger) if err != nil { - return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + return eigenSdkUtils.WrapError("failed to read and validate user appointee remove config", err) } cliCtx.App.Metadata["network"] = config.ChainID.String() permissionWriter, err := generator(logger, config) @@ -62,7 +62,7 @@ func removeUserPermission( receipt, err := permissionWriter.RemovePermission( ctx, elcontracts.RemovePermissionRequest{ - Account: config.AccountAddress, + AccountAddress: config.AccountAddress, UserAddress: config.UserAddress, Target: config.Target, Selector: config.Selector, diff --git a/pkg/user/appointee/set.go b/pkg/user/appointee/set.go index 822d0867..5a1de8f7 100644 --- a/pkg/user/appointee/set.go +++ b/pkg/user/appointee/set.go @@ -52,7 +52,7 @@ func setUserPermission( config, err := readAndValidateSetConfig(cliCtx, logger) if err != nil { - return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) + return eigenSdkUtils.WrapError("failed to read and validate user appointee set config", err) } cliCtx.App.Metadata["network"] = config.ChainID.String() permissionWriter, err := generator(logger, config) @@ -62,7 +62,7 @@ func setUserPermission( receipt, err := permissionWriter.SetPermission( ctx, elcontracts.SetPermissionRequest{ - Account: config.AccountAddress, + AccountAddress: config.AccountAddress, UserAddress: config.UserAddress, Target: config.Target, Selector: config.Selector, diff --git a/pkg/users.go b/pkg/users.go index 99448433..af88a528 100644 --- a/pkg/users.go +++ b/pkg/users.go @@ -12,7 +12,7 @@ func UserCmd(prompter utils.Prompter) *cli.Command { Name: "user", Usage: "Manage user permissions", Subcommands: []*cli.Command{ - admin.AdminCmd(), + admin.AdminCmd(prompter), appointee.AppointeeCmd(prompter), }, } From 5c292157c801e665bd221d19f470c66795282789 Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Wed, 11 Dec 2024 15:43:44 -0800 Subject: [PATCH 06/34] Cleaning up user command flags. (#253) Co-authored-by: Brandon Chatham --- pkg/user/admin/accept.go | 6 ++++-- pkg/user/admin/add_pending.go | 6 ++++-- pkg/user/admin/is_admin.go | 2 -- pkg/user/admin/is_pending.go | 2 -- pkg/user/admin/list.go | 2 -- pkg/user/admin/list_pending.go | 2 -- pkg/user/admin/remove.go | 6 ++++-- pkg/user/admin/remove_pending.go | 6 ++++-- pkg/user/appointee/batch_set.go | 9 ++++++++- pkg/user/appointee/list.go | 1 + pkg/user/appointee/list_permissions.go | 1 + pkg/user/appointee/remove.go | 5 ++++- pkg/user/appointee/set.go | 5 ++++- 13 files changed, 34 insertions(+), 19 deletions(-) diff --git a/pkg/user/admin/accept.go b/pkg/user/admin/accept.go index d91468eb..ca4a6ee5 100644 --- a/pkg/user/admin/accept.go +++ b/pkg/user/admin/accept.go @@ -122,15 +122,17 @@ func acceptFlags() []cli.Flag { cmdFlags := []cli.Flag{ &flags.VerboseFlag, &AccountAddressFlag, + &PermissionControllerAddressFlag, &flags.OutputTypeFlag, &flags.OutputFileFlag, + &flags.BroadcastFlag, &flags.NetworkFlag, &flags.EnvironmentFlag, &flags.ETHRpcUrlFlag, - &PermissionControllerAddressFlag, } + cmdFlags = append(cmdFlags, flags.GetSignerFlags()...) sort.Sort(cli.FlagsByName(cmdFlags)) - return append(cmdFlags, flags.GetSignerFlags()...) + return cmdFlags } func generateAcceptAdminWriter( diff --git a/pkg/user/admin/add_pending.go b/pkg/user/admin/add_pending.go index 5c5d4ddf..95877759 100644 --- a/pkg/user/admin/add_pending.go +++ b/pkg/user/admin/add_pending.go @@ -151,13 +151,15 @@ func addPendingFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &AdminAddressFlag, + &PermissionControllerAddressFlag, &flags.OutputTypeFlag, &flags.OutputFileFlag, + &flags.BroadcastFlag, &flags.NetworkFlag, &flags.EnvironmentFlag, &flags.ETHRpcUrlFlag, - &PermissionControllerAddressFlag, } + cmdFlags = append(cmdFlags, flags.GetSignerFlags()...) sort.Sort(cli.FlagsByName(cmdFlags)) - return append(cmdFlags, flags.GetSignerFlags()...) + return cmdFlags } diff --git a/pkg/user/admin/is_admin.go b/pkg/user/admin/is_admin.go index e29ca22d..05a2aac7 100644 --- a/pkg/user/admin/is_admin.go +++ b/pkg/user/admin/is_admin.go @@ -134,8 +134,6 @@ func IsAdminFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &CallerAddressFlag, - &flags.OutputTypeFlag, - &flags.OutputFileFlag, &PermissionControllerAddressFlag, &flags.NetworkFlag, &flags.EnvironmentFlag, diff --git a/pkg/user/admin/is_pending.go b/pkg/user/admin/is_pending.go index 3c224c62..511bed08 100644 --- a/pkg/user/admin/is_pending.go +++ b/pkg/user/admin/is_pending.go @@ -142,8 +142,6 @@ func isPendingAdminFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &PendingAdminAddressFlag, - &flags.OutputTypeFlag, - &flags.OutputFileFlag, &PermissionControllerAddressFlag, &flags.NetworkFlag, &flags.EnvironmentFlag, diff --git a/pkg/user/admin/list.go b/pkg/user/admin/list.go index c4423864..5169b4f7 100644 --- a/pkg/user/admin/list.go +++ b/pkg/user/admin/list.go @@ -134,8 +134,6 @@ func listAdminFlags() []cli.Flag { cmdFlags := []cli.Flag{ &flags.VerboseFlag, &AccountAddressFlag, - &flags.OutputFileFlag, - &flags.OutputTypeFlag, &PermissionControllerAddressFlag, &flags.NetworkFlag, &flags.EnvironmentFlag, diff --git a/pkg/user/admin/list_pending.go b/pkg/user/admin/list_pending.go index bf1b60ae..893d76de 100644 --- a/pkg/user/admin/list_pending.go +++ b/pkg/user/admin/list_pending.go @@ -142,8 +142,6 @@ func listPendingAdminsFlags() []cli.Flag { cmdFlags := []cli.Flag{ &flags.VerboseFlag, &AccountAddressFlag, - &flags.OutputTypeFlag, - &flags.OutputFileFlag, &PermissionControllerAddressFlag, &flags.NetworkFlag, &flags.EnvironmentFlag, diff --git a/pkg/user/admin/remove.go b/pkg/user/admin/remove.go index 4c0cd963..69c063ee 100644 --- a/pkg/user/admin/remove.go +++ b/pkg/user/admin/remove.go @@ -151,13 +151,15 @@ func removeFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &AdminAddressFlag, + &PermissionControllerAddressFlag, &flags.OutputTypeFlag, &flags.OutputFileFlag, + &flags.BroadcastFlag, &flags.NetworkFlag, &flags.EnvironmentFlag, &flags.ETHRpcUrlFlag, - &PermissionControllerAddressFlag, } + cmdFlags = append(cmdFlags, flags.GetSignerFlags()...) sort.Sort(cli.FlagsByName(cmdFlags)) - return append(cmdFlags, flags.GetSignerFlags()...) + return cmdFlags } diff --git a/pkg/user/admin/remove_pending.go b/pkg/user/admin/remove_pending.go index d9401028..b70edd29 100644 --- a/pkg/user/admin/remove_pending.go +++ b/pkg/user/admin/remove_pending.go @@ -153,13 +153,15 @@ func removePendingAdminFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &AdminAddressFlag, + &PermissionControllerAddressFlag, + &flags.BroadcastFlag, &flags.OutputTypeFlag, &flags.OutputFileFlag, &flags.NetworkFlag, &flags.EnvironmentFlag, &flags.ETHRpcUrlFlag, - &PermissionControllerAddressFlag, } + cmdFlags = append(cmdFlags, flags.GetSignerFlags()...) sort.Sort(cli.FlagsByName(cmdFlags)) - return append(cmdFlags, flags.GetSignerFlags()...) + return cmdFlags } diff --git a/pkg/user/appointee/batch_set.go b/pkg/user/appointee/batch_set.go index cfd504f4..0861807e 100644 --- a/pkg/user/appointee/batch_set.go +++ b/pkg/user/appointee/batch_set.go @@ -27,7 +27,14 @@ func batchSetFlags() []cli.Flag { cmdFlags := []cli.Flag{ &flags.VerboseFlag, &BatchSetFileFlag, + // TODO: any other flags related to command inputs. + &PermissionControllerAddressFlag, + &flags.NetworkFlag, + &flags.EnvironmentFlag, + &flags.ETHRpcUrlFlag, + &flags.BroadcastFlag, } + cmdFlags = append(cmdFlags, flags.GetSignerFlags()...) sort.Sort(cli.FlagsByName(cmdFlags)) - return append(cmdFlags, flags.GetSignerFlags()...) + return cmdFlags } diff --git a/pkg/user/appointee/list.go b/pkg/user/appointee/list.go index c3487411..b6258715 100644 --- a/pkg/user/appointee/list.go +++ b/pkg/user/appointee/list.go @@ -152,6 +152,7 @@ func listFlags() []cli.Flag { &AccountAddressFlag, &TargetAddressFlag, &SelectorFlag, + &PermissionControllerAddressFlag, &flags.NetworkFlag, &flags.EnvironmentFlag, &flags.ETHRpcUrlFlag, diff --git a/pkg/user/appointee/list_permissions.go b/pkg/user/appointee/list_permissions.go index bffcee52..885d9a6c 100644 --- a/pkg/user/appointee/list_permissions.go +++ b/pkg/user/appointee/list_permissions.go @@ -151,6 +151,7 @@ func listPermissionFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &AppointeeAddressFlag, + &PermissionControllerAddressFlag, &flags.NetworkFlag, &flags.EnvironmentFlag, &flags.ETHRpcUrlFlag, diff --git a/pkg/user/appointee/remove.go b/pkg/user/appointee/remove.go index 0026a3ee..b052019a 100644 --- a/pkg/user/appointee/remove.go +++ b/pkg/user/appointee/remove.go @@ -165,10 +165,13 @@ func removeCommandFlags() []cli.Flag { &AppointeeAddressFlag, &TargetAddressFlag, &SelectorFlag, + &PermissionControllerAddressFlag, &flags.NetworkFlag, &flags.EnvironmentFlag, &flags.ETHRpcUrlFlag, + &flags.BroadcastFlag, } + cmdFlags = append(cmdFlags, flags.GetSignerFlags()...) sort.Sort(cli.FlagsByName(cmdFlags)) - return append(cmdFlags, flags.GetSignerFlags()...) + return cmdFlags } diff --git a/pkg/user/appointee/set.go b/pkg/user/appointee/set.go index 5a1de8f7..fd715ec0 100644 --- a/pkg/user/appointee/set.go +++ b/pkg/user/appointee/set.go @@ -165,10 +165,13 @@ func setCommandFlags() []cli.Flag { &AppointeeAddressFlag, &TargetAddressFlag, &SelectorFlag, + &PermissionControllerAddressFlag, &flags.NetworkFlag, &flags.EnvironmentFlag, &flags.ETHRpcUrlFlag, + &flags.BroadcastFlag, } + cmdFlags = append(cmdFlags, flags.GetSignerFlags()...) sort.Sort(cli.FlagsByName(cmdFlags)) - return append(cmdFlags, flags.GetSignerFlags()...) + return cmdFlags } From 77c61f5ee4cf0d20a13298dfaeaafe99c638a437 Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Thu, 12 Dec 2024 10:10:06 -0800 Subject: [PATCH 07/34] Refactoring user commands to remove user nomenclature and replacing with appointee (#254) Co-authored-by: Brandon Chatham --- go.mod | 2 +- go.sum | 4 +- pkg/user/admin/flags.go | 6 --- pkg/user/admin/is_admin.go | 4 +- pkg/user/admin/is_admin_test.go | 8 ++-- pkg/user/appointee/appointee.go | 10 ++--- pkg/user/appointee/can_call.go | 45 ++++++++++++--------- pkg/user/appointee/can_call_test.go | 36 ++++++++--------- pkg/user/appointee/flags.go | 6 --- pkg/user/appointee/list.go | 45 +++++++++++---------- pkg/user/appointee/list_permissions.go | 34 ++++++++-------- pkg/user/appointee/list_permissions_test.go | 30 +++++++------- pkg/user/appointee/list_test.go | 37 ++++++++--------- pkg/user/appointee/remove.go | 34 ++++++++-------- pkg/user/appointee/remove_test.go | 12 +++--- pkg/user/appointee/set.go | 34 ++++++++-------- pkg/user/appointee/set_test.go | 12 +++--- pkg/user/appointee/types.go | 15 ++++--- 18 files changed, 185 insertions(+), 189 deletions(-) diff --git a/go.mod b/go.mod index 124b1094..563fd038 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( //<<<<<<< HEAD // github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212190947-9985122d81fe //======= - github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241211204646-f49e96f7ee7a + github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212003044-37139a7d8ea8 github.com/blang/semver/v4 v4.0.0 github.com/consensys/gnark-crypto v0.12.1 github.com/ethereum/go-ethereum v1.14.5 diff --git a/go.sum b/go.sum index d96a5d6a..fb06eb80 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 h1:G5Q1SnLmFbEjhOkky3vIHk github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12/go.mod h1:OlJd1QjqEW53wfWG/lJyPCGvrXwWVEjPQsP4TV+gttQ= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e h1:DvW0/kWHV9mZsbH2KOjEHKTSIONNPUj6X05FJvUohy4= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e/go.mod h1:T7tYN8bTdca2pkMnz9G2+ZwXYWw5gWqQUIu4KLgC/vM= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241211204646-f49e96f7ee7a h1:U1pibFpUsMfL8h9EGrHtm/z94nKKuEvLdWEpkCkCZr4= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241211204646-f49e96f7ee7a/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212003044-37139a7d8ea8 h1:flOwiF9Z9xburwDodNbd6VBi8rzzNSbRy2KG5kqoAak= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212003044-37139a7d8ea8/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= diff --git a/pkg/user/admin/flags.go b/pkg/user/admin/flags.go index cea56c79..d7fb6a15 100644 --- a/pkg/user/admin/flags.go +++ b/pkg/user/admin/flags.go @@ -15,12 +15,6 @@ var ( Usage: "user admin ... --admin-address \"0x...\"", EnvVars: []string{"ADMIN_ADDRESS"}, } - CallerAddressFlag = cli.StringFlag{ - Name: "caller-address", - Aliases: []string{"ca"}, - Usage: "user admin ... --caller-address \"0x...\"", - EnvVars: []string{"CALLER_ADDRESS"}, - } PendingAdminAddressFlag = cli.StringFlag{ Name: "pending-admin-address", Aliases: []string{"paa"}, diff --git a/pkg/user/admin/is_admin.go b/pkg/user/admin/is_admin.go index 05a2aac7..d8d736a0 100644 --- a/pkg/user/admin/is_admin.go +++ b/pkg/user/admin/is_admin.go @@ -29,7 +29,7 @@ type IsAdminReader interface { func IsAdminCmd(readerGenerator func(logging.Logger, *isAdminConfig) (IsAdminReader, error)) *cli.Command { cmd := &cli.Command{ Name: "is-admin", - Usage: "user admin is-admin --account-address --caller-address ", + Usage: "user admin is-admin --account-address --admin-address ", UsageText: "Checks if a user is an admin.", Description: ` Checks if a user is an admin. @@ -133,7 +133,7 @@ func IsAdminFlags() []cli.Flag { cmdFlags := []cli.Flag{ &flags.VerboseFlag, &AccountAddressFlag, - &CallerAddressFlag, + &AdminAddressFlag, &PermissionControllerAddressFlag, &flags.NetworkFlag, &flags.EnvironmentFlag, diff --git a/pkg/user/admin/is_admin_test.go b/pkg/user/admin/is_admin_test.go index f6c74aae..ba93d5fa 100644 --- a/pkg/user/admin/is_admin_test.go +++ b/pkg/user/admin/is_admin_test.go @@ -44,7 +44,7 @@ func TestIsAdminCmd_Success(t *testing.T) { "TestIsAdminCmd_Success", "is-admin", "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", - "--caller-address", "0x1234567890abcdef1234567890abcdef12345678", + "--admin-address", "0x1234567890abcdef1234567890abcdef12345678", "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--network", "holesky", } @@ -63,7 +63,7 @@ func TestIsAdminCmd_NotAdmin(t *testing.T) { "TestIsAdminCmd_NotAdmin", "is-admin", "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", - "--caller-address", "0x1234567890abcdef1234567890abcdef12345678", + "--admin-address", "0x1234567890abcdef1234567890abcdef12345678", "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--network", "holesky", } @@ -85,7 +85,7 @@ func TestIsAdminCmd_GeneratorError(t *testing.T) { "TestIsAdminCmd_GeneratorError", "is-admin", "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", - "--caller-address", "0x1234567890abcdef1234567890abcdef12345678", + "--admin-address", "0x1234567890abcdef1234567890abcdef12345678", "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--network", "holesky", } @@ -106,7 +106,7 @@ func TestIsAdminCmd_IsAdminError(t *testing.T) { "TestIsAdminCmd_IsAdminError", "is-admin", "--account-address", "0xabcdef1234567890abcdef1234567890abcdef12", - "--caller-address", "0x1234567890abcdef1234567890abcdef12345678", + "--admin-address", "0x1234567890abcdef1234567890abcdef12345678", "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--network", "holesky", } diff --git a/pkg/user/appointee/appointee.go b/pkg/user/appointee/appointee.go index 4e2a5d46..d9db68b4 100644 --- a/pkg/user/appointee/appointee.go +++ b/pkg/user/appointee/appointee.go @@ -21,11 +21,11 @@ func AppointeeCmd(prompter utils.Prompter) *cli.Command { }, Subcommands: []*cli.Command{ BatchSetCmd(), - canCallCmd(generateUserCanCallReader), - ListCmd(generateListUsersReader), - ListPermissionsCmd(generateListUserPermissionsReader), - RemoveCmd(generateRemoveUserPermissionWriter(prompter)), - SetCmd(generateSetUserPermissionWriter(prompter)), + canCallCmd(generateCanCallReader), + ListCmd(generateListAppointeesReader), + ListPermissionsCmd(generateListAppointeePermissionsReader), + RemoveCmd(generateRemoveAppointeePermissionWriter(prompter)), + SetCmd(generateSetAppointeePermissionWriter(prompter)), }, } diff --git a/pkg/user/appointee/can_call.go b/pkg/user/appointee/can_call.go index 59d4b958..e13f7be6 100644 --- a/pkg/user/appointee/can_call.go +++ b/pkg/user/appointee/can_call.go @@ -17,23 +17,23 @@ import ( "github.com/urfave/cli/v2" ) -type UserCanCallReader interface { - UserCanCall( +type CanCallReader interface { + CanCall( ctx context.Context, - userAddress gethcommon.Address, - callerAddress gethcommon.Address, + accountAddress gethcommon.Address, + appointeeAddress gethcommon.Address, target gethcommon.Address, selector [4]byte, ) (bool, error) } -func canCallCmd(readerGenerator func(logging.Logger, *canCallConfig) (UserCanCallReader, error)) *cli.Command { +func canCallCmd(readerGenerator func(logging.Logger, *canCallConfig) (CanCallReader, error)) *cli.Command { cmd := &cli.Command{ Name: "can-call", - Usage: "user appointee can-call --account-address --caller-address --taget-address --selector ", - UsageText: "Checks if a user has a specific permission.", + Usage: "user appointee can-call --account-address --appointee-address --target-address --selector ", + UsageText: "Checks if an appointee has a specific permission.", Description: ` - Checks if a user has a specific permission. + Checks if an appointee has a specific permission. `, Action: func(c *cli.Context) error { return canCall(c, readerGenerator) @@ -45,11 +45,11 @@ func canCallCmd(readerGenerator func(logging.Logger, *canCallConfig) (UserCanCal return cmd } -func canCall(cliCtx *cli.Context, generator func(logging.Logger, *canCallConfig) (UserCanCallReader, error)) error { +func canCall(cliCtx *cli.Context, generator func(logging.Logger, *canCallConfig) (CanCallReader, error)) error { ctx := cliCtx.Context logger := common.GetLogger(cliCtx) - config, err := readAndValidateUserConfig(cliCtx, logger) + config, err := readAndValidateCanCallConfig(cliCtx, logger) if err != nil { return eigenSdkUtils.WrapError("failed to read and validate user can call config", err) } @@ -59,15 +59,20 @@ func canCall(cliCtx *cli.Context, generator func(logging.Logger, *canCallConfig) return err } - result, err := elReader.UserCanCall(ctx, config.UserAddress, config.CallerAddress, config.Target, config.Selector) + result, err := elReader.CanCall(ctx, config.AppointeeAddress, config.AccountAddress, config.Target, config.Selector) fmt.Printf("CanCall Result: %v\n", result) - fmt.Printf("Selector, Target and User: %s, %x, %s\n", config.Target, string(config.Selector[:]), config.UserAddress) + fmt.Printf( + "Selector, Target and Appointee: %s, %x, %s\n", + config.Target, + string(config.Selector[:]), + config.AppointeeAddress, + ) return err } -func readAndValidateUserConfig(cliContext *cli.Context, logger logging.Logger) (*canCallConfig, error) { - userAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) - callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddressFlag.Name)) +func readAndValidateCanCallConfig(cliContext *cli.Context, logger logging.Logger) (*canCallConfig, error) { + accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + appointeeAddress := gethcommon.HexToAddress(cliContext.String(AppointeeAddressFlag.Name)) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -103,8 +108,8 @@ func readAndValidateUserConfig(cliContext *cli.Context, logger logging.Logger) ( return &canCallConfig{ Network: network, RPCUrl: ethRpcUrl, - UserAddress: userAddress, - CallerAddress: callerAddress, + AccountAddress: accountAddress, + AppointeeAddress: appointeeAddress, Target: target, Selector: selectorBytes, PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), @@ -113,10 +118,10 @@ func readAndValidateUserConfig(cliContext *cli.Context, logger logging.Logger) ( }, nil } -func generateUserCanCallReader( +func generateCanCallReader( logger logging.Logger, config *canCallConfig, -) (UserCanCallReader, error) { +) (CanCallReader, error) { ethClient, err := ethclient.Dial(config.RPCUrl) if err != nil { return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) @@ -134,7 +139,7 @@ func generateUserCanCallReader( func canCallFlags() []cli.Flag { cmdFlags := []cli.Flag{ &AccountAddressFlag, - &CallerAddressFlag, + &AppointeeAddressFlag, &TargetAddressFlag, &SelectorFlag, &PermissionControllerAddressFlag, diff --git a/pkg/user/appointee/can_call_test.go b/pkg/user/appointee/can_call_test.go index b9f89bcd..f32a893b 100644 --- a/pkg/user/appointee/can_call_test.go +++ b/pkg/user/appointee/can_call_test.go @@ -14,8 +14,8 @@ import ( type mockElChainReader struct { canCallFunc func( ctx context.Context, - userAddress gethcommon.Address, - callerAddress gethcommon.Address, + accountAddress gethcommon.Address, + appointeeAddress gethcommon.Address, target gethcommon.Address, selector [4]byte, ) (bool, error) @@ -23,7 +23,7 @@ type mockElChainReader struct { func newMockElChainReader() mockElChainReader { return mockElChainReader{ - canCallFunc: func(ctx context.Context, userAddress, callerAddress, target gethcommon.Address, selector [4]byte) (bool, error) { + canCallFunc: func(ctx context.Context, accountAddress, appointeeAddress, target gethcommon.Address, selector [4]byte) (bool, error) { return true, nil }, } @@ -31,19 +31,19 @@ func newMockElChainReader() mockElChainReader { func newErrorMockElChainReader(expectedError string) mockElChainReader { return mockElChainReader{ - canCallFunc: func(ctx context.Context, userAddress, callerAddress, target gethcommon.Address, selector [4]byte) (bool, error) { + canCallFunc: func(ctx context.Context, accountAddress, appointeeAddress, target gethcommon.Address, selector [4]byte) (bool, error) { return false, errors.New(expectedError) }, } } -func (m *mockElChainReader) UserCanCall( +func (m *mockElChainReader) CanCall( ctx context.Context, - userAddress, callerAddress, + accountAddress, appointeeAddress, target gethcommon.Address, selector [4]byte, ) (bool, error) { - return m.canCallFunc(ctx, userAddress, callerAddress, target, selector) + return m.canCallFunc(ctx, accountAddress, appointeeAddress, target, selector) } func TestCanCallCmd_Success(t *testing.T) { @@ -54,7 +54,7 @@ func TestCanCallCmd_Success(t *testing.T) { "TestCanCallCmd_Success", "can-call", "--account-address", "0x1234567890abcdef1234567890abcdef12345678", - "--caller-address", "0x9876543210fedcba9876543210fedcba98765432", + "--appointee-address", "0x9876543210fedcba9876543210fedcba98765432", "--target-address", "0xabcdef1234567890abcdef1234567890abcdef12", "--selector", "0x1A2B3C4D", "--network", "holesky", @@ -66,22 +66,22 @@ func TestCanCallCmd_Success(t *testing.T) { assert.NoError(t, err) } -func TestCanCallCmd_UserCanCallError(t *testing.T) { +func TestCanCallCmd_CanCallError(t *testing.T) { errString := "Error while executing call from reader" mockReader := newErrorMockElChainReader(errString) app := cli.NewApp() app.Commands = []*cli.Command{ - canCallCmd(func(logger logging.Logger, config *canCallConfig) (UserCanCallReader, error) { - return UserCanCallReader(&mockReader), nil + canCallCmd(func(logger logging.Logger, config *canCallConfig) (CanCallReader, error) { + return CanCallReader(&mockReader), nil }), } args := []string{ - "TestCanCallCmd_UserCanCallError", + "TestCanCallCmd_CanCallError", "can-call", "--account-address", "0x1234567890abcdef1234567890abcdef12345678", - "--caller-address", "0x9876543210fedcba9876543210fedcba98765432", + "--appointee-address", "0x9876543210fedcba9876543210fedcba98765432", "--target-address", "0xabcdef1234567890abcdef1234567890abcdef12", "--selector", "0x1A2B3C4D", "--network", "holesky", @@ -102,7 +102,7 @@ func TestCanCallCmd_InvalidSelector(t *testing.T) { "TestCanCallCmd_InvalidSelector", "can-call", "--account-address", "0x1234567890abcdef1234567890abcdef12345678", - "--caller-address", "0x9876543210fedcba9876543210fedcba98765432", + "--appointee-address", "0x9876543210fedcba9876543210fedcba98765432", "--target-address", "0xabcdef1234567890abcdef1234567890abcdef12", "--selector", "incorrect-format", "--permission-controller-address", "0xe4dB7125ef7a9D99F809B6b7788f75c8D84d8455", @@ -117,7 +117,7 @@ func TestCanCallCmd_InvalidSelector(t *testing.T) { "TestCanCallCmd_InvalidSelector", "can-call", "--account-address", "0x1234567890abcdef1234567890abcdef12345678", - "--caller-address", "0x9876543210fedcba9876543210fedcba98765432", + "--appointee-address", "0x9876543210fedcba9876543210fedcba98765432", "--target-address", "0xabcdef1234567890abcdef1234567890abcdef12", "--selector", "0xincorrect-format", "--permission-controller-address", "0xe4dB7125ef7a9D99F809B6b7788f75c8D84d8455", @@ -129,9 +129,9 @@ func TestCanCallCmd_InvalidSelector(t *testing.T) { assert.Contains(t, err.Error(), "selector must be a 4-byte hex string prefixed with '0x'") } -func generateMockReader() func(logger logging.Logger, config *canCallConfig) (UserCanCallReader, error) { - return func(logger logging.Logger, config *canCallConfig) (UserCanCallReader, error) { +func generateMockReader() func(logger logging.Logger, config *canCallConfig) (CanCallReader, error) { + return func(logger logging.Logger, config *canCallConfig) (CanCallReader, error) { mockReader := newMockElChainReader() - return UserCanCallReader(&mockReader), nil + return CanCallReader(&mockReader), nil } } diff --git a/pkg/user/appointee/flags.go b/pkg/user/appointee/flags.go index 3c4a6e37..8b060bc6 100644 --- a/pkg/user/appointee/flags.go +++ b/pkg/user/appointee/flags.go @@ -15,12 +15,6 @@ var ( Usage: "The Ethereum address of the user. Example: --appointee-address \"0x...\"", EnvVars: []string{"APPOINTEE_ADDRESS"}, } - CallerAddressFlag = cli.StringFlag{ - Name: "caller-address", - Aliases: []string{"ca"}, - Usage: "The Ethereum address of the caller. Example: --caller-address \"0x...\"", - EnvVars: []string{"CALLER_ADDRESS"}, - } SelectorFlag = cli.StringFlag{ Name: "selector", Aliases: []string{"s"}, diff --git a/pkg/user/appointee/list.go b/pkg/user/appointee/list.go index b6258715..c65e52d8 100644 --- a/pkg/user/appointee/list.go +++ b/pkg/user/appointee/list.go @@ -18,22 +18,22 @@ import ( "github.com/urfave/cli/v2" ) -type ListUsersReader interface { - ListUsers( +type ListAppointeesReader interface { + ListAppointees( ctx context.Context, - userAddress gethcommon.Address, + accountAddress gethcommon.Address, target gethcommon.Address, selector [4]byte, ) ([]gethcommon.Address, error) } -func ListCmd(readerGenerator func(logging.Logger, *listUsersConfig) (ListUsersReader, error)) *cli.Command { +func ListCmd(readerGenerator func(logging.Logger, *listAppointeesConfig) (ListAppointeesReader, error)) *cli.Command { listCmd := &cli.Command{ Name: "list", Usage: "user appointee list --account-address --target-address --selector ", - UsageText: "Lists all appointed users for an account with the provided permissions.", + UsageText: "Lists all appointed addresses for an account with the provided permissions.", Description: ` - Lists all appointed users for an account with the provided permissions. + Lists all appointed addresses for an account with the provided permissions. `, Action: func(c *cli.Context) error { return listAppointees(c, readerGenerator) @@ -47,12 +47,12 @@ func ListCmd(readerGenerator func(logging.Logger, *listUsersConfig) (ListUsersRe func listAppointees( cliCtx *cli.Context, - generator func(logging.Logger, *listUsersConfig) (ListUsersReader, error), + generator func(logging.Logger, *listAppointeesConfig) (ListAppointeesReader, error), ) error { ctx := cliCtx.Context logger := common.GetLogger(cliCtx) - config, err := readAndValidateListUsersConfig(cliCtx, logger) + config, err := readAndValidateListAppointeesConfig(cliCtx, logger) if err != nil { return eigenSdkUtils.WrapError("failed to read and validate user appointee list config", err) } @@ -62,30 +62,33 @@ func listAppointees( return err } - users, err := elReader.ListUsers(ctx, config.UserAddress, config.Target, config.Selector) + appointees, err := elReader.ListAppointees(ctx, config.AccountAddress, config.Target, config.Selector) if err != nil { return err } - printResults(config, users) + printResults(config, appointees) return nil } -func printResults(config *listUsersConfig, users []gethcommon.Address) { +func printResults(config *listAppointeesConfig, appointees []gethcommon.Address) { fmt.Printf( - "Selector, Target and Appointer: %s, %x, %s", + "Target, Selector and Appointer: %s, %x, %s", config.Target, string(config.Selector[:]), - config.UserAddress, + config.AccountAddress, ) - fmt.Println(strings.Repeat("=", 80)) + fmt.Println(strings.Repeat("=", 60)) - for _, user := range users { - fmt.Printf("User Id: 0x%b\n", user) + for _, appointee := range appointees { + fmt.Printf("Appointee address: %s\n", appointee) } } -func readAndValidateListUsersConfig(cliContext *cli.Context, logger logging.Logger) (*listUsersConfig, error) { - userAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) +func readAndValidateListAppointeesConfig( + cliContext *cli.Context, + logger logging.Logger, +) (*listAppointeesConfig, error) { + accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -119,10 +122,10 @@ func readAndValidateListUsersConfig(cliContext *cli.Context, logger logging.Logg permissionManagerAddress, ) - return &listUsersConfig{ + return &listAppointeesConfig{ Network: network, RPCUrl: ethRpcUrl, - UserAddress: userAddress, + AccountAddress: accountAddress, Target: target, Selector: selectorBytes, PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), @@ -131,7 +134,7 @@ func readAndValidateListUsersConfig(cliContext *cli.Context, logger logging.Logg }, nil } -func generateListUsersReader(logger logging.Logger, config *listUsersConfig) (ListUsersReader, error) { +func generateListAppointeesReader(logger logging.Logger, config *listAppointeesConfig) (ListAppointeesReader, error) { ethClient, err := ethclient.Dial(config.RPCUrl) if err != nil { return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) diff --git a/pkg/user/appointee/list_permissions.go b/pkg/user/appointee/list_permissions.go index 885d9a6c..4a9ecbcb 100644 --- a/pkg/user/appointee/list_permissions.go +++ b/pkg/user/appointee/list_permissions.go @@ -18,15 +18,15 @@ import ( ) type PermissionsReader interface { - ListUserPermissions( + ListAppointeePermissions( ctx context.Context, - appointed gethcommon.Address, - userAddress gethcommon.Address, + accountAddress gethcommon.Address, + appointeeAddress gethcommon.Address, ) ([]gethcommon.Address, [][4]byte, error) } func ListPermissionsCmd( - readerGenerator func(logging.Logger, *listUserPermissionsConfig) (PermissionsReader, error), + readerGenerator func(logging.Logger, *listAppointeePermissionsConfig) (PermissionsReader, error), ) *cli.Command { cmd := &cli.Command{ Name: "list-permissions", @@ -47,12 +47,12 @@ func ListPermissionsCmd( func listPermissions( cliCtx *cli.Context, - generator func(logging.Logger, *listUserPermissionsConfig) (PermissionsReader, error), + generator func(logging.Logger, *listAppointeePermissionsConfig) (PermissionsReader, error), ) error { ctx := cliCtx.Context logger := common.GetLogger(cliCtx) - config, err := readAndValidateListUserPermissionsConfig(cliCtx, logger) + config, err := readAndValidateListAppointeePermissionsConfig(cliCtx, logger) if err != nil { return eigenSdkUtils.WrapError("failed to read and validate list user permissions config", err) } @@ -63,20 +63,20 @@ func listPermissions( return err } - users, permissions, err := reader.ListUserPermissions(ctx, config.AccountAddress, config.UserAddress) + appointees, permissions, err := reader.ListAppointeePermissions(ctx, config.AccountAddress, config.AppointeeAddress) if err != nil { return err } - printPermissions(config, users, permissions) + printPermissions(config, appointees, permissions) return nil } -func readAndValidateListUserPermissionsConfig( +func readAndValidateListAppointeePermissionsConfig( cliContext *cli.Context, logger logging.Logger, -) (*listUserPermissionsConfig, error) { +) (*listAppointeePermissionsConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) - userAddress := gethcommon.HexToAddress(cliContext.String(AppointeeAddressFlag.Name)) + appointeeAddress := gethcommon.HexToAddress(cliContext.String(AppointeeAddressFlag.Name)) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -104,19 +104,19 @@ func readAndValidateListUserPermissionsConfig( permissionManagerAddress, ) - return &listUserPermissionsConfig{ + return &listAppointeePermissionsConfig{ Network: network, RPCUrl: ethRpcUrl, AccountAddress: accountAddress, - UserAddress: userAddress, + AppointeeAddress: appointeeAddress, PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), ChainID: chainID, Environment: environment, }, nil } -func printPermissions(config *listUserPermissionsConfig, targets []gethcommon.Address, selectors [][4]byte) { - fmt.Printf("User: %s\n", config.UserAddress) +func printPermissions(config *listAppointeePermissionsConfig, targets []gethcommon.Address, selectors [][4]byte) { + fmt.Printf("Appointee address: %s\n", config.AppointeeAddress) fmt.Printf("Appointed by: %s\n", config.AccountAddress) fmt.Println("====================================================================================") @@ -128,9 +128,9 @@ func printPermissions(config *listUserPermissionsConfig, targets []gethcommon.Ad } } -func generateListUserPermissionsReader( +func generateListAppointeePermissionsReader( logger logging.Logger, - config *listUserPermissionsConfig, + config *listAppointeePermissionsConfig, ) (PermissionsReader, error) { ethClient, err := ethclient.Dial(config.RPCUrl) if err != nil { diff --git a/pkg/user/appointee/list_permissions_test.go b/pkg/user/appointee/list_permissions_test.go index 1fa90bdc..9024bfde 100644 --- a/pkg/user/appointee/list_permissions_test.go +++ b/pkg/user/appointee/list_permissions_test.go @@ -15,43 +15,43 @@ import ( type mockListPermissionsReader struct { listPermissionsFunc func( ctx context.Context, - appointed gethcommon.Address, - userAddress gethcommon.Address, + accountAddress gethcommon.Address, + appointeeAddress gethcommon.Address, ) ([]gethcommon.Address, [][4]byte, error) } func newMockListPermissionsReader( - users []gethcommon.Address, + appointeeAddresses []gethcommon.Address, permissions [][4]byte, err error, ) *mockListPermissionsReader { return &mockListPermissionsReader{ - listPermissionsFunc: func(ctx context.Context, appointed, userAddress gethcommon.Address) ([]gethcommon.Address, [][4]byte, error) { - return users, permissions, err + listPermissionsFunc: func(ctx context.Context, accountAddress, appointeeAddress gethcommon.Address) ([]gethcommon.Address, [][4]byte, error) { + return appointeeAddresses, permissions, err }, } } -func (m *mockListPermissionsReader) ListUserPermissions( +func (m *mockListPermissionsReader) ListAppointeePermissions( ctx context.Context, - appointed gethcommon.Address, - userAddress gethcommon.Address, + accountAddress gethcommon.Address, + appointeeAddress gethcommon.Address, ) ([]gethcommon.Address, [][4]byte, error) { - return m.listPermissionsFunc(ctx, appointed, userAddress) + return m.listPermissionsFunc(ctx, accountAddress, appointeeAddress) } func generateMockListPermissionsReader( - users []gethcommon.Address, + appointeeAddresses []gethcommon.Address, permissions [][4]byte, err error, -) func(logging.Logger, *listUserPermissionsConfig) (PermissionsReader, error) { - return func(logger logging.Logger, config *listUserPermissionsConfig) (PermissionsReader, error) { - return newMockListPermissionsReader(users, permissions, err), nil +) func(logging.Logger, *listAppointeePermissionsConfig) (PermissionsReader, error) { + return func(logger logging.Logger, config *listAppointeePermissionsConfig) (PermissionsReader, error) { + return newMockListPermissionsReader(appointeeAddresses, permissions, err), nil } } func TestListPermissions_Success(t *testing.T) { - expectedUsers := []gethcommon.Address{ + expectedAppointees := []gethcommon.Address{ gethcommon.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), } expectedPermissions := [][4]byte{ @@ -60,7 +60,7 @@ func TestListPermissions_Success(t *testing.T) { app := cli.NewApp() app.Commands = []*cli.Command{ - ListPermissionsCmd(generateMockListPermissionsReader(expectedUsers, expectedPermissions, nil)), + ListPermissionsCmd(generateMockListPermissionsReader(expectedAppointees, expectedPermissions, nil)), } args := []string{ diff --git a/pkg/user/appointee/list_test.go b/pkg/user/appointee/list_test.go index 8f240a43..44321c2a 100644 --- a/pkg/user/appointee/list_test.go +++ b/pkg/user/appointee/list_test.go @@ -12,49 +12,50 @@ import ( "github.com/urfave/cli/v2" ) -type mockListUsersReader struct { - listUsersFunc func( +type mockListAppointeesReader struct { + listAppointeesFunc func( ctx context.Context, - userAddress gethcommon.Address, + accountAddress gethcommon.Address, target gethcommon.Address, selector [4]byte, ) ([]gethcommon.Address, error) } -func newMockListUsersReader(users []gethcommon.Address, err error) *mockListUsersReader { - return &mockListUsersReader{ - listUsersFunc: func(ctx context.Context, userAddress, target gethcommon.Address, selector [4]byte) ([]gethcommon.Address, error) { - return users, err +func newMockListAppointeesReader(appointeeAddresses []gethcommon.Address, err error) *mockListAppointeesReader { + return &mockListAppointeesReader{ + listAppointeesFunc: func(ctx context.Context, accountAddress, target gethcommon.Address, selector [4]byte) ([]gethcommon.Address, error) { + return appointeeAddresses, err }, } } -func (m *mockListUsersReader) ListUsers( +func (m *mockListAppointeesReader) ListAppointees( ctx context.Context, - userAddress, target gethcommon.Address, + accountAddress, + target gethcommon.Address, selector [4]byte, ) ([]gethcommon.Address, error) { - return m.listUsersFunc(ctx, userAddress, target, selector) + return m.listAppointeesFunc(ctx, accountAddress, target, selector) } func generateMockListReader( - users []gethcommon.Address, + appointeeAddresses []gethcommon.Address, err error, -) func(logging.Logger, *listUsersConfig) (ListUsersReader, error) { - return func(logger logging.Logger, config *listUsersConfig) (ListUsersReader, error) { - return newMockListUsersReader(users, err), nil +) func(logging.Logger, *listAppointeesConfig) (ListAppointeesReader, error) { + return func(logger logging.Logger, config *listAppointeesConfig) (ListAppointeesReader, error) { + return newMockListAppointeesReader(appointeeAddresses, err), nil } } func TestListAppointees_Success(t *testing.T) { - expectedUsers := []gethcommon.Address{ + expectedAppointees := []gethcommon.Address{ gethcommon.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), gethcommon.HexToAddress("0x9876543210fedcba9876543210fedcba98765432"), } app := cli.NewApp() app.Commands = []*cli.Command{ - ListCmd(generateMockListReader(expectedUsers, nil)), + ListCmd(generateMockListReader(expectedAppointees, nil)), } args := []string{ @@ -114,14 +115,14 @@ func TestListAppointees_InvalidSelector(t *testing.T) { assert.Contains(t, err.Error(), "selector must be a 4-byte hex string prefixed with '0x'") } -func TestListAppointees_NoUsers(t *testing.T) { +func TestListAppointees_NoAppointees(t *testing.T) { app := cli.NewApp() app.Commands = []*cli.Command{ ListCmd(generateMockListReader([]gethcommon.Address{}, nil)), } args := []string{ - "TestListAppointees_NoUsers", + "TestListAppointees_NoAppointees", "list", "--account-address", "0x1234567890abcdef1234567890abcdef12345678", "--target-address", "0xabcdef1234567890abcdef1234567890abcdef12", diff --git a/pkg/user/appointee/remove.go b/pkg/user/appointee/remove.go index b052019a..a3fe6cdb 100644 --- a/pkg/user/appointee/remove.go +++ b/pkg/user/appointee/remove.go @@ -18,24 +18,24 @@ import ( "github.com/urfave/cli/v2" ) -type RemoveUserPermissionWriter interface { +type RemoveAppointeePermissionWriter interface { RemovePermission( ctx context.Context, request elcontracts.RemovePermissionRequest, ) (*gethtypes.Receipt, error) } -func RemoveCmd(generator func(logging.Logger, *removeConfig) (RemoveUserPermissionWriter, error)) *cli.Command { +func RemoveCmd(generator func(logging.Logger, *removeConfig) (RemoveAppointeePermissionWriter, error)) *cli.Command { removeCmd := &cli.Command{ Name: "remove", Usage: "user appointee remove --account-address --appointee-address --target-address --selector ", - UsageText: "Remove a user's permission", + UsageText: "Remove an appointee's permission", Description: ` - Remove a user's permission'. + Remove an appointee's permission'. `, After: telemetry.AfterRunAction(), Action: func(c *cli.Context) error { - return removeUserPermission(c, generator) + return removeAppointeePermission(c, generator) }, Flags: removeCommandFlags(), } @@ -43,9 +43,9 @@ func RemoveCmd(generator func(logging.Logger, *removeConfig) (RemoveUserPermissi return removeCmd } -func removeUserPermission( +func removeAppointeePermission( cliCtx *cli.Context, - generator func(logging.Logger, *removeConfig) (RemoveUserPermissionWriter, error), + generator func(logging.Logger, *removeConfig) (RemoveAppointeePermissionWriter, error), ) error { ctx := cliCtx.Context logger := common.GetLogger(cliCtx) @@ -62,11 +62,11 @@ func removeUserPermission( receipt, err := permissionWriter.RemovePermission( ctx, elcontracts.RemovePermissionRequest{ - AccountAddress: config.AccountAddress, - UserAddress: config.UserAddress, - Target: config.Target, - Selector: config.Selector, - WaitForReceipt: true, + AccountAddress: config.AccountAddress, + AppointeeAddress: config.AppointeeAddress, + Target: config.Target, + Selector: config.Selector, + WaitForReceipt: true, }, ) if err != nil { @@ -76,13 +76,13 @@ func removeUserPermission( return nil } -func generateRemoveUserPermissionWriter( +func generateRemoveAppointeePermissionWriter( prompter utils.Prompter, ) func( logger logging.Logger, config *removeConfig, -) (RemoveUserPermissionWriter, error) { - return func(logger logging.Logger, config *removeConfig) (RemoveUserPermissionWriter, error) { +) (RemoveAppointeePermissionWriter, error) { + return func(logger logging.Logger, config *removeConfig) (RemoveAppointeePermissionWriter, error) { ethClient, err := ethclient.Dial(config.RPCUrl) if err != nil { return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) @@ -104,7 +104,7 @@ func generateRemoveUserPermissionWriter( func readAndValidateRemoveConfig(cliContext *cli.Context, logger logging.Logger) (*removeConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) - userAddress := gethcommon.HexToAddress(cliContext.String(AppointeeAddressFlag.Name)) + appointeeAddress := gethcommon.HexToAddress(cliContext.String(AppointeeAddressFlag.Name)) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -148,7 +148,7 @@ func readAndValidateRemoveConfig(cliContext *cli.Context, logger logging.Logger) Network: network, RPCUrl: ethRpcUrl, AccountAddress: accountAddress, - UserAddress: userAddress, + AppointeeAddress: appointeeAddress, Target: target, Selector: selectorBytes, SignerConfig: *signerConfig, diff --git a/pkg/user/appointee/remove_test.go b/pkg/user/appointee/remove_test.go index f08d71b4..2b386776 100644 --- a/pkg/user/appointee/remove_test.go +++ b/pkg/user/appointee/remove_test.go @@ -13,20 +13,20 @@ import ( "github.com/urfave/cli/v2" ) -type mockRemoveUserPermissionWriter struct { +type mockRemoveAppointeePermissionWriter struct { removePermissionFunc func(ctx context.Context, request elcontracts.RemovePermissionRequest) (*gethtypes.Receipt, error) } -func (m *mockRemoveUserPermissionWriter) RemovePermission( +func (m *mockRemoveAppointeePermissionWriter) RemovePermission( ctx context.Context, request elcontracts.RemovePermissionRequest, ) (*gethtypes.Receipt, error) { return m.removePermissionFunc(ctx, request) } -func generateMockRemoveWriter(err error) func(logging.Logger, *removeConfig) (RemoveUserPermissionWriter, error) { - return func(logger logging.Logger, config *removeConfig) (RemoveUserPermissionWriter, error) { - return &mockRemoveUserPermissionWriter{ +func generateMockRemoveWriter(err error) func(logging.Logger, *removeConfig) (RemoveAppointeePermissionWriter, error) { + return func(logger logging.Logger, config *removeConfig) (RemoveAppointeePermissionWriter, error) { + return &mockRemoveAppointeePermissionWriter{ removePermissionFunc: func(ctx context.Context, request elcontracts.RemovePermissionRequest) (*gethtypes.Receipt, error) { return &gethtypes.Receipt{}, err }, @@ -60,7 +60,7 @@ func TestRemoveCmd_GeneratorError(t *testing.T) { expectedError := "failed to create permission writer" app := cli.NewApp() app.Commands = []*cli.Command{ - RemoveCmd(func(logger logging.Logger, config *removeConfig) (RemoveUserPermissionWriter, error) { + RemoveCmd(func(logger logging.Logger, config *removeConfig) (RemoveAppointeePermissionWriter, error) { return nil, errors.New(expectedError) }), } diff --git a/pkg/user/appointee/set.go b/pkg/user/appointee/set.go index fd715ec0..166d1971 100644 --- a/pkg/user/appointee/set.go +++ b/pkg/user/appointee/set.go @@ -18,23 +18,23 @@ import ( "github.com/urfave/cli/v2" ) -type SetUserPermissionWriter interface { +type SetAppointeePermissionWriter interface { SetPermission( ctx context.Context, request elcontracts.SetPermissionRequest, ) (*gethtypes.Receipt, error) } -func SetCmd(generator func(logging.Logger, *setConfig) (SetUserPermissionWriter, error)) *cli.Command { +func SetCmd(generator func(logging.Logger, *setConfig) (SetAppointeePermissionWriter, error)) *cli.Command { setCmd := &cli.Command{ Name: "set", Usage: "user appointee set --account-address --appointee-address --target-address --selector ", - UsageText: "Grant a user a permission.", + UsageText: "Grant an appointee a permission.", Description: ` - Grant a user a permission.'. + Grant an appointee a permission.'. `, Action: func(c *cli.Context) error { - return setUserPermission(c, generator) + return setAppointeePermission(c, generator) }, After: telemetry.AfterRunAction(), Flags: setCommandFlags(), @@ -43,9 +43,9 @@ func SetCmd(generator func(logging.Logger, *setConfig) (SetUserPermissionWriter, return setCmd } -func setUserPermission( +func setAppointeePermission( cliCtx *cli.Context, - generator func(logging.Logger, *setConfig) (SetUserPermissionWriter, error), + generator func(logging.Logger, *setConfig) (SetAppointeePermissionWriter, error), ) error { ctx := cliCtx.Context logger := common.GetLogger(cliCtx) @@ -62,11 +62,11 @@ func setUserPermission( receipt, err := permissionWriter.SetPermission( ctx, elcontracts.SetPermissionRequest{ - AccountAddress: config.AccountAddress, - UserAddress: config.UserAddress, - Target: config.Target, - Selector: config.Selector, - WaitForReceipt: true, + AccountAddress: config.AccountAddress, + AppointeeAddress: config.AppointeeAddress, + Target: config.Target, + Selector: config.Selector, + WaitForReceipt: true, }, ) if err != nil { @@ -76,13 +76,13 @@ func setUserPermission( return nil } -func generateSetUserPermissionWriter( +func generateSetAppointeePermissionWriter( prompter utils.Prompter, ) func( logger logging.Logger, config *setConfig, -) (SetUserPermissionWriter, error) { - return func(logger logging.Logger, config *setConfig) (SetUserPermissionWriter, error) { +) (SetAppointeePermissionWriter, error) { + return func(logger logging.Logger, config *setConfig) (SetAppointeePermissionWriter, error) { ethClient, err := ethclient.Dial(config.RPCUrl) if err != nil { return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) @@ -104,7 +104,7 @@ func generateSetUserPermissionWriter( func readAndValidateSetConfig(cliContext *cli.Context, logger logging.Logger) (*setConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) - userAddress := gethcommon.HexToAddress(cliContext.String(AppointeeAddressFlag.Name)) + appointeeAddress := gethcommon.HexToAddress(cliContext.String(AppointeeAddressFlag.Name)) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -148,7 +148,7 @@ func readAndValidateSetConfig(cliContext *cli.Context, logger logging.Logger) (* Network: network, RPCUrl: ethRpcUrl, AccountAddress: accountAddress, - UserAddress: userAddress, + AppointeeAddress: appointeeAddress, Target: target, Selector: selectorBytes, SignerConfig: *signerConfig, diff --git a/pkg/user/appointee/set_test.go b/pkg/user/appointee/set_test.go index 14a0b95f..9b43d86f 100644 --- a/pkg/user/appointee/set_test.go +++ b/pkg/user/appointee/set_test.go @@ -13,20 +13,20 @@ import ( "github.com/urfave/cli/v2" ) -type mockSetUserPermissionWriter struct { +type mockSetAppointeePermissionWriter struct { setPermissionFunc func(ctx context.Context, request elcontracts.SetPermissionRequest) (*gethtypes.Receipt, error) } -func (m *mockSetUserPermissionWriter) SetPermission( +func (m *mockSetAppointeePermissionWriter) SetPermission( ctx context.Context, request elcontracts.SetPermissionRequest, ) (*gethtypes.Receipt, error) { return m.setPermissionFunc(ctx, request) } -func generateMockSetWriter(err error) func(logging.Logger, *setConfig) (SetUserPermissionWriter, error) { - return func(logger logging.Logger, config *setConfig) (SetUserPermissionWriter, error) { - return &mockSetUserPermissionWriter{ +func generateMockSetWriter(err error) func(logging.Logger, *setConfig) (SetAppointeePermissionWriter, error) { + return func(logger logging.Logger, config *setConfig) (SetAppointeePermissionWriter, error) { + return &mockSetAppointeePermissionWriter{ setPermissionFunc: func(ctx context.Context, request elcontracts.SetPermissionRequest) (*gethtypes.Receipt, error) { return &gethtypes.Receipt{}, err }, @@ -60,7 +60,7 @@ func TestSetCmd_GeneratorError(t *testing.T) { expectedError := "failed to create permission writer" app := cli.NewApp() app.Commands = []*cli.Command{ - SetCmd(func(logger logging.Logger, config *setConfig) (SetUserPermissionWriter, error) { + SetCmd(func(logger logging.Logger, config *setConfig) (SetAppointeePermissionWriter, error) { return nil, errors.New(expectedError) }), } diff --git a/pkg/user/appointee/types.go b/pkg/user/appointee/types.go index 6e17194f..d91496d0 100644 --- a/pkg/user/appointee/types.go +++ b/pkg/user/appointee/types.go @@ -10,8 +10,8 @@ import ( type canCallConfig struct { Network string RPCUrl string - UserAddress gethcommon.Address - CallerAddress gethcommon.Address + AccountAddress gethcommon.Address + AppointeeAddress gethcommon.Address Target gethcommon.Address Selector [4]byte PermissionManagerAddress gethcommon.Address @@ -19,11 +19,10 @@ type canCallConfig struct { Environment string } -type listUsersConfig struct { +type listAppointeesConfig struct { Network string RPCUrl string AccountAddress gethcommon.Address - UserAddress gethcommon.Address Target gethcommon.Address Selector [4]byte PermissionManagerAddress gethcommon.Address @@ -31,11 +30,11 @@ type listUsersConfig struct { Environment string } -type listUserPermissionsConfig struct { +type listAppointeePermissionsConfig struct { Network string RPCUrl string AccountAddress gethcommon.Address - UserAddress gethcommon.Address + AppointeeAddress gethcommon.Address PermissionManagerAddress gethcommon.Address ChainID *big.Int Environment string @@ -45,7 +44,7 @@ type removeConfig struct { Network string RPCUrl string AccountAddress gethcommon.Address - UserAddress gethcommon.Address + AppointeeAddress gethcommon.Address Target gethcommon.Address SignerConfig types.SignerConfig Selector [4]byte @@ -58,7 +57,7 @@ type setConfig struct { Network string RPCUrl string AccountAddress gethcommon.Address - UserAddress gethcommon.Address + AppointeeAddress gethcommon.Address Target gethcommon.Address SignerConfig types.SignerConfig Selector [4]byte From 45806ba79ee1d8799b03db7f8893f9b1f1be0206 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Thu, 12 Dec 2024 10:14:00 -0800 Subject: [PATCH 08/34] fix: integration test (#255) --- .github/workflows/integration-test.yml | 4 ++-- pkg/internal/common/helper.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index a2c7acd5..dd090e51 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -23,7 +23,7 @@ jobs: with: repository: layr-labs/eigensdk-go token: ${{ github.token }} - ref: dev + ref: 37139a7d8ea870ca2c36e6aca72115de12c26d5d - name: Run anvil chain run: | @@ -96,7 +96,7 @@ jobs: with: repository: layr-labs/eigensdk-go token: ${{ github.token }} - ref: dev + ref: 37139a7d8ea870ca2c36e6aca72115de12c26d5d - name: Run anvil chain run: | nohup make start-anvil-with-contracts-deployed > nohup.out 2>&1 & diff --git a/pkg/internal/common/helper.go b/pkg/internal/common/helper.go index bbd282ad..54f70ca9 100644 --- a/pkg/internal/common/helper.go +++ b/pkg/internal/common/helper.go @@ -69,7 +69,7 @@ var ChainMetadataMap = map[int64]types.ChainMetadata{ BlockExplorerUrl: "", ELDelegationManagerAddress: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", ELAVSDirectoryAddress: "0x0165878A594ca255338adfa4d48449f69242Eb8F", - ELRewardsCoordinatorAddress: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", + ELRewardsCoordinatorAddress: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", ELPermissionManagerAddress: "", WebAppUrl: "", ProofStoreBaseURL: "", From 5291137311fee3934d0f83df600b129af3c74ebf Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Thu, 12 Dec 2024 13:25:28 -0800 Subject: [PATCH 09/34] fix: allocation delay config and update tests (#257) --- pkg/operator/config/create.go | 57 ++++++++++++++----------------- pkg/user/admin/types.go | 3 +- pkg/user/appointee/remove_test.go | 3 +- pkg/user/appointee/set_test.go | 3 +- tests/keystore/operator-ci.yaml | 1 + tests/web3signer/operator-ci.yaml | 1 + 6 files changed, 34 insertions(+), 34 deletions(-) diff --git a/pkg/operator/config/create.go b/pkg/operator/config/create.go index ee12af0c..72951532 100644 --- a/pkg/operator/config/create.go +++ b/pkg/operator/config/create.go @@ -119,36 +119,31 @@ func promptOperatorInfo(config *types.OperatorConfig, p utils.Prompter) (types.O } config.Operator.Address = operatorAddress - // TODO(madhur): Disabling this for now as the feature doesn't work but - // we need to keep the code around for future // Prompt to gate stakers approval - //gateApproval, err := p.Confirm("Do you want to gate stakers approval?") - //if err != nil { - // return types.OperatorConfig{}, err - //} + gateApproval, err := p.Confirm("Do you want to gate stakers approval?") + if err != nil { + return types.OperatorConfig{}, err + } // Prompt for address if operator wants to gate approvals - //if gateApproval { - // delegationApprover, err := p.InputString("Enter your staker approver address:", "", "", - // func(s string) error { - // isValidAddress := eigenSdkUtils.IsValidEthereumAddress(s) - // - // if !isValidAddress { - // return errors.New("address is invalid") - // } - // - // return nil - // }, - // ) - // if err != nil { - // return types.OperatorConfig{}, err - // } - // config.Operator.DelegationApproverAddress = delegationApprover - //} else { - // config.Operator.DelegationApproverAddress = eigensdkTypes.ZeroAddress - //} - - // TODO(madhur): Remove this once we have the feature working and want to prompt users for this address - config.Operator.DelegationApproverAddress = eigensdkTypes.ZeroAddress + if gateApproval { + delegationApprover, err := p.InputString("Enter your staker approver address:", "", "", + func(s string) error { + isValidAddress := eigenSdkUtils.IsValidEthereumAddress(s) + + if !isValidAddress { + return errors.New("address is invalid") + } + + return nil + }, + ) + if err != nil { + return types.OperatorConfig{}, err + } + config.Operator.DelegationApproverAddress = delegationApprover + } else { + config.Operator.DelegationApproverAddress = eigensdkTypes.ZeroAddress + } // Prompt for eth node rpcUrl, err := p.InputString("Enter your ETH rpc url:", "http://localhost:8545", "", @@ -161,8 +156,8 @@ func promptOperatorInfo(config *types.OperatorConfig, p utils.Prompter) (types.O // Prompt for allocation delay allocationDelay, err := p.InputInteger( - "Enter your allocation delay (in seconds, default is 17.5 days):", - "1512000", + "Enter your allocation delay (in blocks, default is 1200):", + "1200", "", func(i int64) error { if i < 0 { @@ -180,7 +175,7 @@ func promptOperatorInfo(config *types.OperatorConfig, p utils.Prompter) (types.O "Are you sure you want to set the allocation delay to " + strconv.FormatInt( allocationDelay, 10, - ) + " seconds? This cannot be changed once set.", + ) + " blocks?", ) if err != nil { return types.OperatorConfig{}, err diff --git a/pkg/user/admin/types.go b/pkg/user/admin/types.go index a0e73378..22cb088f 100644 --- a/pkg/user/admin/types.go +++ b/pkg/user/admin/types.go @@ -1,9 +1,10 @@ package admin import ( + "math/big" + "github.com/Layr-Labs/eigenlayer-cli/pkg/types" gethcommon "github.com/ethereum/go-ethereum/common" - "math/big" ) type listPendingAdminsConfig struct { diff --git a/pkg/user/appointee/remove_test.go b/pkg/user/appointee/remove_test.go index 2b386776..108c33dc 100644 --- a/pkg/user/appointee/remove_test.go +++ b/pkg/user/appointee/remove_test.go @@ -3,9 +3,10 @@ package appointee import ( "context" "errors" - gethtypes "github.com/ethereum/go-ethereum/core/types" "testing" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" "github.com/stretchr/testify/assert" diff --git a/pkg/user/appointee/set_test.go b/pkg/user/appointee/set_test.go index 9b43d86f..b17d3be2 100644 --- a/pkg/user/appointee/set_test.go +++ b/pkg/user/appointee/set_test.go @@ -3,9 +3,10 @@ package appointee import ( "context" "errors" - gethtypes "github.com/ethereum/go-ethereum/core/types" "testing" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" "github.com/stretchr/testify/assert" diff --git a/tests/keystore/operator-ci.yaml b/tests/keystore/operator-ci.yaml index ec684748..0f93a961 100644 --- a/tests/keystore/operator-ci.yaml +++ b/tests/keystore/operator-ci.yaml @@ -3,6 +3,7 @@ operator: delegation_approver_address: 0xcaB1b44dd1f1C265405878Ac1179cd94D0dBA634 staker_opt_out_window_blocks: 0 metadata_url: https://madhur-test-public.s3.us-east-2.amazonaws.com/metadata.json + allocation_delay: 1200 el_delegation_manager_address: 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 eth_rpc_url: http://localhost:8545 private_key_store_path: /home/runner/.eigenlayer/operator_keys/opr0.ecdsa.key.json diff --git a/tests/web3signer/operator-ci.yaml b/tests/web3signer/operator-ci.yaml index c765a412..d7d56811 100644 --- a/tests/web3signer/operator-ci.yaml +++ b/tests/web3signer/operator-ci.yaml @@ -3,6 +3,7 @@ operator: delegation_approver_address: 0xcaB1b44dd1f1C265405878Ac1179cd94D0dBA634 staker_opt_out_window_blocks: 0 metadata_url: https://madhur-test-public.s3.us-east-2.amazonaws.com/metadata.json + allocation_delay: 1200 el_delegation_manager_address: 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 eth_rpc_url: http://localhost:8545 private_key_store_path: From 3633d02aef34ac01b0d3c3006b266478e2cb5456 Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Fri, 13 Dec 2024 16:26:14 -0800 Subject: [PATCH 10/34] Adding print code paths for appointee set and remove. (#259) Co-authored-by: Brandon Chatham --- go.mod | 2 +- go.sum | 4 +- pkg/user/appointee/appointee.go | 1 - pkg/user/appointee/batch_set.go | 40 --------- pkg/user/appointee/can_call.go | 4 +- pkg/user/appointee/list.go | 3 +- pkg/user/appointee/list_permissions.go | 10 +-- pkg/user/appointee/remove.go | 90 ++++++++++++++++++-- pkg/user/appointee/remove_test.go | 39 +++------ pkg/user/appointee/set.go | 109 +++++++++++++++++++++---- pkg/user/appointee/set_test.go | 18 +++- pkg/user/appointee/types.go | 6 ++ 12 files changed, 222 insertions(+), 104 deletions(-) delete mode 100644 pkg/user/appointee/batch_set.go diff --git a/go.mod b/go.mod index 563fd038..d0ca2601 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( //<<<<<<< HEAD // github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212190947-9985122d81fe //======= - github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212003044-37139a7d8ea8 + github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241213014620-7831a35a43a7 github.com/blang/semver/v4 v4.0.0 github.com/consensys/gnark-crypto v0.12.1 github.com/ethereum/go-ethereum v1.14.5 diff --git a/go.sum b/go.sum index fb06eb80..105c26cb 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 h1:G5Q1SnLmFbEjhOkky3vIHk github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12/go.mod h1:OlJd1QjqEW53wfWG/lJyPCGvrXwWVEjPQsP4TV+gttQ= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e h1:DvW0/kWHV9mZsbH2KOjEHKTSIONNPUj6X05FJvUohy4= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e/go.mod h1:T7tYN8bTdca2pkMnz9G2+ZwXYWw5gWqQUIu4KLgC/vM= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212003044-37139a7d8ea8 h1:flOwiF9Z9xburwDodNbd6VBi8rzzNSbRy2KG5kqoAak= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212003044-37139a7d8ea8/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241213014620-7831a35a43a7 h1:58JjbKZs0oaGRQv/AU+XtqWvD3+dyKM4NIbqjACANN0= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241213014620-7831a35a43a7/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= diff --git a/pkg/user/appointee/appointee.go b/pkg/user/appointee/appointee.go index d9db68b4..2c063953 100644 --- a/pkg/user/appointee/appointee.go +++ b/pkg/user/appointee/appointee.go @@ -20,7 +20,6 @@ func AppointeeCmd(prompter utils.Prompter) *cli.Command { &flags.VerboseFlag, }, Subcommands: []*cli.Command{ - BatchSetCmd(), canCallCmd(generateCanCallReader), ListCmd(generateListAppointeesReader), ListPermissionsCmd(generateListAppointeePermissionsReader), diff --git a/pkg/user/appointee/batch_set.go b/pkg/user/appointee/batch_set.go deleted file mode 100644 index 0861807e..00000000 --- a/pkg/user/appointee/batch_set.go +++ /dev/null @@ -1,40 +0,0 @@ -package appointee - -import ( - "sort" - - "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" - "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" - "github.com/urfave/cli/v2" -) - -func BatchSetCmd() *cli.Command { - batchSetCmd := &cli.Command{ - Name: "batch set", - Usage: "user appointee batch-set --batch-set-file ", - UsageText: "Appoint multiple users permissions at a time.", - Description: ` - Appoint multiple users permissions at a time. - `, - After: telemetry.AfterRunAction(), - Flags: batchSetFlags(), - } - - return batchSetCmd -} - -func batchSetFlags() []cli.Flag { - cmdFlags := []cli.Flag{ - &flags.VerboseFlag, - &BatchSetFileFlag, - // TODO: any other flags related to command inputs. - &PermissionControllerAddressFlag, - &flags.NetworkFlag, - &flags.EnvironmentFlag, - &flags.ETHRpcUrlFlag, - &flags.BroadcastFlag, - } - cmdFlags = append(cmdFlags, flags.GetSignerFlags()...) - sort.Sort(cli.FlagsByName(cmdFlags)) - return cmdFlags -} diff --git a/pkg/user/appointee/can_call.go b/pkg/user/appointee/can_call.go index e13f7be6..ff9ed223 100644 --- a/pkg/user/appointee/can_call.go +++ b/pkg/user/appointee/can_call.go @@ -59,10 +59,10 @@ func canCall(cliCtx *cli.Context, generator func(logging.Logger, *canCallConfig) return err } - result, err := elReader.CanCall(ctx, config.AppointeeAddress, config.AccountAddress, config.Target, config.Selector) + result, err := elReader.CanCall(ctx, config.AccountAddress, config.AppointeeAddress, config.Target, config.Selector) fmt.Printf("CanCall Result: %v\n", result) fmt.Printf( - "Selector, Target and Appointee: %s, %x, %s\n", + "Target, Selector and Appointee: %s, %x, %s\n", config.Target, string(config.Selector[:]), config.AppointeeAddress, diff --git a/pkg/user/appointee/list.go b/pkg/user/appointee/list.go index c65e52d8..7d244764 100644 --- a/pkg/user/appointee/list.go +++ b/pkg/user/appointee/list.go @@ -77,10 +77,11 @@ func printResults(config *listAppointeesConfig, appointees []gethcommon.Address) string(config.Selector[:]), config.AccountAddress, ) + fmt.Println() fmt.Println(strings.Repeat("=", 60)) for _, appointee := range appointees { - fmt.Printf("Appointee address: %s\n", appointee) + fmt.Printf("%s\n", appointee) } } diff --git a/pkg/user/appointee/list_permissions.go b/pkg/user/appointee/list_permissions.go index 4a9ecbcb..470f4184 100644 --- a/pkg/user/appointee/list_permissions.go +++ b/pkg/user/appointee/list_permissions.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "sort" + "strings" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" @@ -118,13 +119,10 @@ func readAndValidateListAppointeePermissionsConfig( func printPermissions(config *listAppointeePermissionsConfig, targets []gethcommon.Address, selectors [][4]byte) { fmt.Printf("Appointee address: %s\n", config.AppointeeAddress) fmt.Printf("Appointed by: %s\n", config.AccountAddress) - fmt.Println("====================================================================================") - - for _, target := range targets { - for _, selector := range selectors { - fmt.Printf("Target: %s, Selector: %x\n", target, selector) - } + fmt.Println(strings.Repeat("=", 60)) + for index := range targets { + fmt.Printf("Target: %s, Selector: %x\n", targets[index], selectors[index]) } } diff --git a/pkg/user/appointee/remove.go b/pkg/user/appointee/remove.go index a3fe6cdb..4789a2c2 100644 --- a/pkg/user/appointee/remove.go +++ b/pkg/user/appointee/remove.go @@ -2,6 +2,7 @@ package appointee import ( "context" + "fmt" "sort" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" @@ -23,6 +24,9 @@ type RemoveAppointeePermissionWriter interface { ctx context.Context, request elcontracts.RemovePermissionRequest, ) (*gethtypes.Receipt, error) + NewRemovePermissionTx( + request elcontracts.RemovePermissionRequest, + ) (*gethtypes.Transaction, error) } func RemoveCmd(generator func(logging.Logger, *removeConfig) (RemoveAppointeePermissionWriter, error)) *cli.Command { @@ -59,15 +63,79 @@ func removeAppointeePermission( if err != nil { return err } + removePermissionRequest := elcontracts.RemovePermissionRequest{ + AccountAddress: config.AccountAddress, + AppointeeAddress: config.AppointeeAddress, + Target: config.Target, + Selector: config.Selector, + WaitForReceipt: true, + } + if config.Broadcast { + return broadcastRemoveAppointeeTx(ctx, permissionWriter, config, removePermissionRequest) + } + return printRemoveAppointeeResult(logger, permissionWriter, config, removePermissionRequest) +} + +func printRemoveAppointeeResult( + logger logging.Logger, + permissionWriter RemoveAppointeePermissionWriter, + config *removeConfig, + request elcontracts.RemovePermissionRequest, +) error { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return err + } + noSendTxOpts := common.GetNoSendTxOpts(config.AccountAddress) + if common.IsSmartContractAddress(config.AccountAddress, ethClient) { + // address is a smart contract + noSendTxOpts.GasLimit = 150_000 + } + unsignedTx, err := permissionWriter.NewRemovePermissionTx(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( + "Appointee %s will be lose permission to target %s selector %s by account %s\n", + config.AppointeeAddress, + config.Target, + config.Selector, + config.AccountAddress, + ) + } + txFeeDetails := common.GetTxFeeDetails(unsignedTx) + fmt.Println() + txFeeDetails.Print() + fmt.Println("To broadcast the transaction, use the --broadcast flag") + return nil +} + +func broadcastRemoveAppointeeTx( + ctx context.Context, + permissionWriter RemoveAppointeePermissionWriter, + config *removeConfig, + request elcontracts.RemovePermissionRequest, +) error { receipt, err := permissionWriter.RemovePermission( ctx, - elcontracts.RemovePermissionRequest{ - AccountAddress: config.AccountAddress, - AppointeeAddress: config.AppointeeAddress, - Target: config.Target, - Selector: config.Selector, - WaitForReceipt: true, - }, + request, ) if err != nil { return err @@ -108,6 +176,9 @@ func readAndValidateRemoveConfig(cliContext *cli.Context, logger logging.Logger) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) + outputFile := cliContext.String(flags.OutputFileFlag.Name) + outputType := cliContext.String(flags.OutputTypeFlag.Name) + broadcast := cliContext.Bool(flags.BroadcastFlag.Name) target := gethcommon.HexToAddress(cliContext.String(TargetAddressFlag.Name)) selector := cliContext.String(SelectorFlag.Name) selectorBytes, err := common.ValidateAndConvertSelectorString(selector) @@ -155,6 +226,9 @@ func readAndValidateRemoveConfig(cliContext *cli.Context, logger logging.Logger) PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), ChainID: chainID, Environment: environment, + Broadcast: broadcast, + OutputType: outputType, + OutputFile: outputFile, }, nil } @@ -170,6 +244,8 @@ func removeCommandFlags() []cli.Flag { &flags.EnvironmentFlag, &flags.ETHRpcUrlFlag, &flags.BroadcastFlag, + &flags.OutputFileFlag, + &flags.OutputTypeFlag, } cmdFlags = append(cmdFlags, flags.GetSignerFlags()...) sort.Sort(cli.FlagsByName(cmdFlags)) diff --git a/pkg/user/appointee/remove_test.go b/pkg/user/appointee/remove_test.go index 108c33dc..cd25e379 100644 --- a/pkg/user/appointee/remove_test.go +++ b/pkg/user/appointee/remove_test.go @@ -15,7 +15,8 @@ import ( ) type mockRemoveAppointeePermissionWriter struct { - removePermissionFunc func(ctx context.Context, request elcontracts.RemovePermissionRequest) (*gethtypes.Receipt, error) + removePermissionFunc func(ctx context.Context, request elcontracts.RemovePermissionRequest) (*gethtypes.Receipt, error) + newRemovePermissionTxFunc func(request elcontracts.RemovePermissionRequest) (*gethtypes.Transaction, error) } func (m *mockRemoveAppointeePermissionWriter) RemovePermission( @@ -25,12 +26,21 @@ func (m *mockRemoveAppointeePermissionWriter) RemovePermission( return m.removePermissionFunc(ctx, request) } +func (m *mockRemoveAppointeePermissionWriter) NewRemovePermissionTx( + request elcontracts.RemovePermissionRequest, +) (*gethtypes.Transaction, error) { + return m.newRemovePermissionTxFunc(request) +} + func generateMockRemoveWriter(err error) func(logging.Logger, *removeConfig) (RemoveAppointeePermissionWriter, error) { return func(logger logging.Logger, config *removeConfig) (RemoveAppointeePermissionWriter, error) { return &mockRemoveAppointeePermissionWriter{ removePermissionFunc: func(ctx context.Context, request elcontracts.RemovePermissionRequest) (*gethtypes.Receipt, error) { return &gethtypes.Receipt{}, err }, + newRemovePermissionTxFunc: func(request elcontracts.RemovePermissionRequest) (*gethtypes.Transaction, error) { + return &gethtypes.Transaction{}, err + }, }, nil } } @@ -51,6 +61,7 @@ func TestRemoveCmd_Success(t *testing.T) { "--network", "holesky", "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "--broadcast", } err := app.Run(args) @@ -76,31 +87,7 @@ func TestRemoveCmd_GeneratorError(t *testing.T) { "--network", "holesky", "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", - } - - err := app.Run(args) - assert.Error(t, err) - assert.Contains(t, err.Error(), expectedError) -} - -func TestRemoveCmd_RemovePermissionError(t *testing.T) { - expectedError := "error removing permission" - app := cli.NewApp() - app.Commands = []*cli.Command{ - RemoveCmd(generateMockRemoveWriter(errors.New(expectedError))), - } - - args := []string{ - "TestRemoveCmd_RemovePermissionError", - "remove", - "--account-address", "0x1234567890abcdef1234567890abcdef12345678", - "--appointee-address", "0xabcdef1234567890abcdef1234567890abcdef12", - "--target-address", "0x9876543210fedcba9876543210fedcba98765432", - "--selector", "0x1A2B3C4D", - "--network", "holesky", - "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", - "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", - "--path-to-key-store", "/path/to/keystore.json", + "--broadcast", } err := app.Run(args) diff --git a/pkg/user/appointee/set.go b/pkg/user/appointee/set.go index 166d1971..4f85cc68 100644 --- a/pkg/user/appointee/set.go +++ b/pkg/user/appointee/set.go @@ -2,6 +2,7 @@ package appointee import ( "context" + "fmt" "sort" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" @@ -23,6 +24,9 @@ type SetAppointeePermissionWriter interface { ctx context.Context, request elcontracts.SetPermissionRequest, ) (*gethtypes.Receipt, error) + NewSetPermissionTx( + request elcontracts.SetPermissionRequest, + ) (*gethtypes.Transaction, error) } func SetCmd(generator func(logging.Logger, *setConfig) (SetAppointeePermissionWriter, error)) *cli.Command { @@ -31,7 +35,7 @@ func SetCmd(generator func(logging.Logger, *setConfig) (SetAppointeePermissionWr Usage: "user appointee set --account-address --appointee-address --target-address --selector ", UsageText: "Grant an appointee a permission.", Description: ` - Grant an appointee a permission.'. + Grant an appointee a permission. `, Action: func(c *cli.Context) error { return setAppointeePermission(c, generator) @@ -59,16 +63,35 @@ func setAppointeePermission( if err != nil { return err } - receipt, err := permissionWriter.SetPermission( - ctx, - elcontracts.SetPermissionRequest{ - AccountAddress: config.AccountAddress, - AppointeeAddress: config.AppointeeAddress, - Target: config.Target, - Selector: config.Selector, - WaitForReceipt: true, - }, - ) + return broadcastOrPrint(ctx, logger, permissionWriter, config) +} + +func broadcastOrPrint( + ctx context.Context, + logger logging.Logger, + permissionWriter SetAppointeePermissionWriter, + config *setConfig, +) error { + permissionRequest := elcontracts.SetPermissionRequest{ + AccountAddress: config.AccountAddress, + AppointeeAddress: config.AppointeeAddress, + Target: config.Target, + Selector: config.Selector, + WaitForReceipt: true, + } + if config.Broadcast { + return broadcastSetAppointeeTx(ctx, permissionWriter, config, permissionRequest) + } + return printSetAppointeeResults(logger, permissionWriter, config, permissionRequest) +} + +func broadcastSetAppointeeTx( + ctx context.Context, + permissionWriter SetAppointeePermissionWriter, + config *setConfig, + request elcontracts.SetPermissionRequest, +) error { + receipt, err := permissionWriter.SetPermission(ctx, request) if err != nil { return err } @@ -76,12 +99,60 @@ func setAppointeePermission( return nil } -func generateSetAppointeePermissionWriter( - prompter utils.Prompter, -) func( +func printSetAppointeeResults( logger logging.Logger, + permissionWriter SetAppointeePermissionWriter, config *setConfig, -) (SetAppointeePermissionWriter, error) { + request elcontracts.SetPermissionRequest, +) error { + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return err + } + noSendTxOpts := common.GetNoSendTxOpts(config.AccountAddress) + if common.IsSmartContractAddress(config.AccountAddress, ethClient) { + // address is a smart contract + noSendTxOpts.GasLimit = 150_000 + } + tx, err := permissionWriter.NewSetPermissionTx(request) + if err != nil { + return eigenSdkUtils.WrapError("failed to create unsigned tx", err) + } + + if config.OutputType == string(common.OutputType_Calldata) { + calldataHex := gethcommon.Bytes2Hex(tx.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( + "Appointee %s will be given permission to target %s selector %s by account %s\n", + config.AppointeeAddress, + config.Target, + config.Selector, + config.AccountAddress, + ) + } + txFeeDetails := common.GetTxFeeDetails(tx) + fmt.Println() + txFeeDetails.Print() + fmt.Println("To broadcast the transaction, use the --broadcast flag") + return nil +} + +func generateSetAppointeePermissionWriter( + prompter utils.Prompter, +) func(logger logging.Logger, config *setConfig) (SetAppointeePermissionWriter, error) { return func(logger logging.Logger, config *setConfig) (SetAppointeePermissionWriter, error) { ethClient, err := ethclient.Dial(config.RPCUrl) if err != nil { @@ -108,6 +179,9 @@ func readAndValidateSetConfig(cliContext *cli.Context, logger logging.Logger) (* 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) target := gethcommon.HexToAddress(cliContext.String(TargetAddressFlag.Name)) selector := cliContext.String(SelectorFlag.Name) selectorBytes, err := common.ValidateAndConvertSelectorString(selector) @@ -155,6 +229,9 @@ func readAndValidateSetConfig(cliContext *cli.Context, logger logging.Logger) (* PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), ChainID: chainID, Environment: environment, + OutputFile: outputFile, + OutputType: outputType, + Broadcast: broadcast, }, nil } @@ -170,6 +247,8 @@ func setCommandFlags() []cli.Flag { &flags.EnvironmentFlag, &flags.ETHRpcUrlFlag, &flags.BroadcastFlag, + &flags.OutputTypeFlag, + &flags.OutputFileFlag, } cmdFlags = append(cmdFlags, flags.GetSignerFlags()...) sort.Sort(cli.FlagsByName(cmdFlags)) diff --git a/pkg/user/appointee/set_test.go b/pkg/user/appointee/set_test.go index b17d3be2..e049ad36 100644 --- a/pkg/user/appointee/set_test.go +++ b/pkg/user/appointee/set_test.go @@ -5,17 +5,17 @@ import ( "errors" "testing" - gethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" + gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/assert" "github.com/urfave/cli/v2" ) type mockSetAppointeePermissionWriter struct { - setPermissionFunc func(ctx context.Context, request elcontracts.SetPermissionRequest) (*gethtypes.Receipt, error) + setPermissionFunc func(ctx context.Context, request elcontracts.SetPermissionRequest) (*gethtypes.Receipt, error) + newSetPermissionTxFunc func(request elcontracts.SetPermissionRequest) (*gethtypes.Transaction, error) } func (m *mockSetAppointeePermissionWriter) SetPermission( @@ -25,12 +25,21 @@ func (m *mockSetAppointeePermissionWriter) SetPermission( return m.setPermissionFunc(ctx, request) } +func (m *mockSetAppointeePermissionWriter) NewSetPermissionTx( + request elcontracts.SetPermissionRequest, +) (*gethtypes.Transaction, error) { + return m.newSetPermissionTxFunc(request) +} + func generateMockSetWriter(err error) func(logging.Logger, *setConfig) (SetAppointeePermissionWriter, error) { return func(logger logging.Logger, config *setConfig) (SetAppointeePermissionWriter, error) { return &mockSetAppointeePermissionWriter{ setPermissionFunc: func(ctx context.Context, request elcontracts.SetPermissionRequest) (*gethtypes.Receipt, error) { return &gethtypes.Receipt{}, err }, + newSetPermissionTxFunc: func(request elcontracts.SetPermissionRequest) (*gethtypes.Transaction, error) { + return &gethtypes.Transaction{}, err + }, }, nil } } @@ -51,6 +60,7 @@ func TestSetCmd_Success(t *testing.T) { "--network", "holesky", "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "--broadcast", } err := app.Run(args) @@ -76,6 +86,7 @@ func TestSetCmd_GeneratorError(t *testing.T) { "--network", "holesky", "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "--broadcast", } err := app.Run(args) @@ -100,6 +111,7 @@ func TestSetCmd_SetPermissionError(t *testing.T) { "--network", "holesky", "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "--broadcast", } err := app.Run(args) diff --git a/pkg/user/appointee/types.go b/pkg/user/appointee/types.go index d91496d0..6ac8c6b1 100644 --- a/pkg/user/appointee/types.go +++ b/pkg/user/appointee/types.go @@ -51,6 +51,9 @@ type removeConfig struct { PermissionManagerAddress gethcommon.Address ChainID *big.Int Environment string + OutputFile string + OutputType string + Broadcast bool } type setConfig struct { @@ -64,4 +67,7 @@ type setConfig struct { PermissionManagerAddress gethcommon.Address ChainID *big.Int Environment string + OutputFile string + OutputType string + Broadcast bool } From ee567d4ae8d0c4d53051a8009ae309228126e74d Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Mon, 16 Dec 2024 13:32:38 -0800 Subject: [PATCH 11/34] chote: latest eigensdk commit --- go.mod | 5 +---- go.sum | 2 ++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index d0ca2601..a6c8a2f6 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,7 @@ require ( github.com/Layr-Labs/eigenlayer-contracts v0.3.2-mainnet-rewards github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e - //<<<<<<< HEAD - // github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212190947-9985122d81fe - //======= - github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241213014620-7831a35a43a7 + github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241216183922-525ebfea245a github.com/blang/semver/v4 v4.0.0 github.com/consensys/gnark-crypto v0.12.1 github.com/ethereum/go-ethereum v1.14.5 diff --git a/go.sum b/go.sum index 105c26cb..ba7cd5eb 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248- github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e/go.mod h1:T7tYN8bTdca2pkMnz9G2+ZwXYWw5gWqQUIu4KLgC/vM= github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241213014620-7831a35a43a7 h1:58JjbKZs0oaGRQv/AU+XtqWvD3+dyKM4NIbqjACANN0= github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241213014620-7831a35a43a7/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241216183922-525ebfea245a h1:SW/dP6D6YjN3y/vXC++TBmekXo/fDGcPXoYHitARz/I= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241216183922-525ebfea245a/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= From 37a3f63166d9dae237926af3ab47fd5229d8f233 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Mon, 16 Dec 2024 13:35:18 -0800 Subject: [PATCH 12/34] latest commit for IT --- .github/workflows/integration-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index dd090e51..0afe7771 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -23,7 +23,7 @@ jobs: with: repository: layr-labs/eigensdk-go token: ${{ github.token }} - ref: 37139a7d8ea870ca2c36e6aca72115de12c26d5d + ref: 525ebfea245aadbd0497a5be2999c3b19dbbd68b - name: Run anvil chain run: | @@ -96,7 +96,7 @@ jobs: with: repository: layr-labs/eigensdk-go token: ${{ github.token }} - ref: 37139a7d8ea870ca2c36e6aca72115de12c26d5d + ref: 525ebfea245aadbd0497a5be2999c3b19dbbd68b - name: Run anvil chain run: | nohup make start-anvil-with-contracts-deployed > nohup.out 2>&1 & From 887301efe13c4c9933daced41409f188a310ef94 Mon Sep 17 00:00:00 2001 From: Brandon Chatham Date: Mon, 16 Dec 2024 14:12:00 -0800 Subject: [PATCH 13/34] Adding new line for pending admins command output. --- pkg/user/admin/list_pending.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/user/admin/list_pending.go b/pkg/user/admin/list_pending.go index 893d76de..546640ac 100644 --- a/pkg/user/admin/list_pending.go +++ b/pkg/user/admin/list_pending.go @@ -72,7 +72,7 @@ func listPendingAdmins( } func printPendingAdmins(account gethcommon.Address, admins []gethcommon.Address) { - fmt.Printf("Pending Admins\n for AccountAddress: %s", account) + fmt.Printf("Pending Admins for AccountAddress: %s \n", account) fmt.Println(strings.Repeat("=", 60)) for _, admin := range admins { fmt.Printf("%s \n", admin.String()) From 3eae006763343090e882806fb31522d563e87140 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Mon, 16 Dec 2024 19:49:00 -0800 Subject: [PATCH 14/34] fix: add caller address (#262) --- pkg/internal/common/flags/general.go | 7 +++++++ .../allocations/set_allocation_delay.go | 14 ++++++++++--- pkg/operator/allocations/show.go | 3 ++- pkg/operator/allocations/types.go | 2 ++ pkg/operator/allocations/update.go | 20 +++++++++++++------ 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/pkg/internal/common/flags/general.go b/pkg/internal/common/flags/general.go index 517bffc8..c24f55fd 100644 --- a/pkg/internal/common/flags/general.go +++ b/pkg/internal/common/flags/general.go @@ -125,4 +125,11 @@ var ( Usage: "Optional delegation manager address. This can be used if you are testing against your own deployment of eigenlayer contracts", EnvVars: []string{"DELEGATION_MANAGER_ADDRESS"}, } + + CallerAddressFlag = cli.StringFlag{ + Name: "caller-address", + Aliases: []string{"ca"}, + Usage: "This is the address of the caller who is calling the contract function. If it is not provided, the operator address will be used as the caller address", + EnvVars: []string{"CALLER_ADDRESS"}, + } ) diff --git a/pkg/operator/allocations/set_allocation_delay.go b/pkg/operator/allocations/set_allocation_delay.go index 87b64d8e..fedfc127 100644 --- a/pkg/operator/allocations/set_allocation_delay.go +++ b/pkg/operator/allocations/set_allocation_delay.go @@ -62,7 +62,7 @@ func setDelayAction(cCtx *cli.Context, p utils.Prompter) error { return nil } eLWriter, err := common.GetELWriter( - config.operatorAddress, + config.callerAddress, config.signerConfig, ethClient, elcontracts.Config{ @@ -83,7 +83,7 @@ func setDelayAction(cCtx *cli.Context, p utils.Prompter) error { } common.PrintTransactionInfo(receipt.TxHash.String(), config.chainID) } else { - noSendTxOpts := common.GetNoSendTxOpts(config.operatorAddress) + noSendTxOpts := common.GetNoSendTxOpts(config.callerAddress) _, _, contractBindings, err := elcontracts.BuildClients(elcontracts.Config{ DelegationManagerAddress: config.delegationManagerAddress, }, ethClient, nil, logger, nil) @@ -94,7 +94,7 @@ func setDelayAction(cCtx *cli.Context, p utils.Prompter) error { // since balance of contract can be 0, as it can be called by an EOA // to claim. So we hardcode the gas limit to 150_000 so that we can // create unsigned tx without gas limit estimation from contract bindings - if common.IsSmartContractAddress(config.operatorAddress, ethClient) { + if common.IsSmartContractAddress(config.callerAddress, ethClient) { // address is a smart contract noSendTxOpts.GasLimit = 150_000 } @@ -142,6 +142,7 @@ func getSetAllocationDelayFlags() []cli.Flag { &flags.VerboseFlag, &flags.OperatorAddressFlag, &flags.DelegationManagerAddressFlag, + &flags.CallerAddressFlag, } allFlags := append(baseFlags, flags.GetSignerFlags()...) sort.Sort(cli.FlagsByName(allFlags)) @@ -168,6 +169,12 @@ func readAndValidateAllocationDelayConfig(c *cli.Context, logger logging.Logger) broadcast := c.Bool(flags.BroadcastFlag.Name) operatorAddress := c.String(flags.OperatorAddressFlag.Name) + callerAddress := c.String(flags.CallerAddressFlag.Name) + if common.IsEmptyString(callerAddress) { + logger.Infof("Caller address not provided. Using operator address (%s) as caller address", operatorAddress) + callerAddress = operatorAddress + } + chainID := utils.NetworkNameToChainId(network) logger.Debugf("Using chain ID: %s", chainID.String()) @@ -204,5 +211,6 @@ func readAndValidateAllocationDelayConfig(c *cli.Context, logger logging.Logger) signerConfig: signerConfig, delegationManagerAddress: gethcommon.HexToAddress(delegationManagerAddress), allocationDelay: uint32(allocationDelayUint), + callerAddress: gethcommon.HexToAddress(callerAddress), }, nil } diff --git a/pkg/operator/allocations/show.go b/pkg/operator/allocations/show.go index 27208dbc..876398ba 100644 --- a/pkg/operator/allocations/show.go +++ b/pkg/operator/allocations/show.go @@ -150,6 +150,7 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { slashableMagnitudeHolders := make(SlashableMagnitudeHolders, 0) dergisteredOpsets := make(DeregsiteredOperatorSets, 0) for strategy, allocations := range allAllocations { + logger.Debugf("Strategy: %s, Allocations: %v", strategy, allocations) strategyShares := operatorDelegatedSharesMap[strategy] for _, alloc := range allocations { currentShares, currentSharesPercentage := getSharesFromMagnitude( @@ -193,7 +194,7 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { } for key, val := range operatorDelegatedSharesMap { - fmt.Printf("Strategy Address: %s, Shares %s\n", key, val.String()) + fmt.Printf("Strategy Address: %s, Shares %s\n", key, common.FormatNumberWithUnderscores(val.String())) } currBlockNumber, err := ethClient.BlockNumber(ctx) diff --git a/pkg/operator/allocations/types.go b/pkg/operator/allocations/types.go index 2ceb1ac0..e6bbd1fb 100644 --- a/pkg/operator/allocations/types.go +++ b/pkg/operator/allocations/types.go @@ -93,6 +93,7 @@ type updateConfig struct { avsAddress gethcommon.Address strategyAddress gethcommon.Address delegationManagerAddress gethcommon.Address + callerAddress gethcommon.Address operatorSetId uint32 bipsToAllocate uint64 signerConfig *types.SignerConfig @@ -119,6 +120,7 @@ type allocationDelayConfig struct { signerConfig *types.SignerConfig allocationDelay uint32 delegationManagerAddress gethcommon.Address + callerAddress gethcommon.Address } type showConfig struct { diff --git a/pkg/operator/allocations/update.go b/pkg/operator/allocations/update.go index 2f196c24..5585a1c9 100644 --- a/pkg/operator/allocations/update.go +++ b/pkg/operator/allocations/update.go @@ -94,7 +94,7 @@ func updateAllocations(cCtx *cli.Context, p utils.Prompter) error { } logger.Info("Broadcasting magnitude allocation update...") eLWriter, err := common.GetELWriter( - config.operatorAddress, + config.callerAddress, config.signerConfig, ethClient, elcontracts.Config{ @@ -119,7 +119,7 @@ func updateAllocations(cCtx *cli.Context, p utils.Prompter) error { } common.PrintTransactionInfo(receipt.TxHash.String(), config.chainID) } else { - noSendTxOpts := common.GetNoSendTxOpts(config.operatorAddress) + noSendTxOpts := common.GetNoSendTxOpts(config.callerAddress) _, _, contractBindings, err := elcontracts.BuildClients(elcontracts.Config{ DelegationManagerAddress: config.delegationManagerAddress, }, ethClient, nil, logger, nil) @@ -130,7 +130,7 @@ func updateAllocations(cCtx *cli.Context, p utils.Prompter) error { // since balance of contract can be 0, as it can be called by an EOA // to claim. So we hardcode the gas limit to 150_000 so that we can // create unsigned tx without gas limit estimation from contract bindings - if common.IsSmartContractAddress(config.operatorAddress, ethClient) { + if common.IsSmartContractAddress(config.callerAddress, ethClient) { // address is a smart contract noSendTxOpts.GasLimit = 150_000 } @@ -189,6 +189,7 @@ func getUpdateFlags() []cli.Flag { &flags.CSVFileFlag, &flags.DelegationManagerAddressFlag, &flags.SilentFlag, + &flags.CallerAddressFlag, &BipsToAllocateFlag, } allFlags := append(baseFlags, flags.GetSignerFlags()...) @@ -439,14 +440,20 @@ func readAndValidateUpdateFlags(cCtx *cli.Context, logger logging.Logger) (*upda broadcast := cCtx.Bool(flags.BroadcastFlag.Name) isSilent := cCtx.Bool(flags.SilentFlag.Name) - operatorAddress := gethcommon.HexToAddress(cCtx.String(flags.OperatorAddressFlag.Name)) + operatorAddress := cCtx.String(flags.OperatorAddressFlag.Name) + callerAddress := cCtx.String(flags.CallerAddressFlag.Name) + if common.IsEmptyString(callerAddress) { + logger.Infof("Caller address not provided. Using operator address (%s) as caller address", operatorAddress) + callerAddress = operatorAddress + } + avsAddress := gethcommon.HexToAddress(cCtx.String(flags.AVSAddressFlag.Name)) strategyAddress := gethcommon.HexToAddress(cCtx.String(flags.StrategyAddressFlag.Name)) operatorSetId := uint32(cCtx.Uint64(flags.OperatorSetIdFlag.Name)) bipsToAllocate := cCtx.Uint64(BipsToAllocateFlag.Name) logger.Debugf( "Operator address: %s, AVS address: %s, Strategy address: %s, Bips to allocate: %d", - operatorAddress.Hex(), + operatorAddress, avsAddress.Hex(), strategyAddress.Hex(), bipsToAllocate, @@ -478,7 +485,8 @@ func readAndValidateUpdateFlags(cCtx *cli.Context, logger logging.Logger) (*upda output: output, outputType: outputType, broadcast: broadcast, - operatorAddress: operatorAddress, + operatorAddress: gethcommon.HexToAddress(operatorAddress), + callerAddress: gethcommon.HexToAddress(callerAddress), avsAddress: avsAddress, strategyAddress: strategyAddress, bipsToAllocate: bipsToAllocate, From 400501d29327c954b4c481424d92f93c4a05334b Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Tue, 17 Dec 2024 11:39:09 -0800 Subject: [PATCH 15/34] Adding caller-address to support arbitrary CLI signing address. (#264) Co-authored-by: Brandon Chatham --- pkg/user/admin/accept.go | 11 ++++++++++- pkg/user/admin/flags.go | 7 +++++++ pkg/user/admin/types.go | 1 + pkg/user/appointee/flags.go | 13 +++++++------ pkg/user/appointee/remove.go | 15 ++++++++++++--- pkg/user/appointee/set.go | 17 +++++++++++++---- pkg/user/appointee/types.go | 2 ++ 7 files changed, 52 insertions(+), 14 deletions(-) diff --git a/pkg/user/admin/accept.go b/pkg/user/admin/accept.go index ca4a6ee5..a6d78648 100644 --- a/pkg/user/admin/accept.go +++ b/pkg/user/admin/accept.go @@ -76,6 +76,7 @@ func readAndValidateAcceptAdminConfig( logger logging.Logger, ) (*acceptAdminConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) + callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddress.Name)) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -98,6 +99,13 @@ func readAndValidateAcceptAdminConfig( return nil, err } } + if common.IsEmptyString(callerAddress.String()) { + logger.Infof( + "Caller address not provided. Using account address (%s) as caller address", + accountAddress, + ) + callerAddress = accountAddress + } logger.Debugf( "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", @@ -111,6 +119,7 @@ func readAndValidateAcceptAdminConfig( Network: network, RPCUrl: ethRpcUrl, AccountAddress: accountAddress, + CallerAddress: callerAddress, PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), SignerConfig: *signerConfig, ChainID: chainID, @@ -144,7 +153,7 @@ func generateAcceptAdminWriter( return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) } return common.GetELWriter( - config.AccountAddress, + config.CallerAddress, &config.SignerConfig, ethClient, elcontracts.Config{ diff --git a/pkg/user/admin/flags.go b/pkg/user/admin/flags.go index d7fb6a15..3a0d3b70 100644 --- a/pkg/user/admin/flags.go +++ b/pkg/user/admin/flags.go @@ -15,6 +15,13 @@ var ( Usage: "user admin ... --admin-address \"0x...\"", EnvVars: []string{"ADMIN_ADDRESS"}, } + CallerAddress = cli.StringFlag{ + Name: "caller-address", + Aliases: []string{"ca"}, + Usage: "This is the address of the caller who is calling the contract function. \n" + + "If it is not provided, the account address will be used as the caller address", + EnvVars: []string{"CALLER_ADDRESS"}, + } PendingAdminAddressFlag = cli.StringFlag{ Name: "pending-admin-address", Aliases: []string{"paa"}, diff --git a/pkg/user/admin/types.go b/pkg/user/admin/types.go index 22cb088f..e52550f2 100644 --- a/pkg/user/admin/types.go +++ b/pkg/user/admin/types.go @@ -49,6 +49,7 @@ type acceptAdminConfig struct { Network string RPCUrl string AccountAddress gethcommon.Address + CallerAddress gethcommon.Address PermissionManagerAddress gethcommon.Address SignerConfig types.SignerConfig ChainID *big.Int diff --git a/pkg/user/appointee/flags.go b/pkg/user/appointee/flags.go index 8b060bc6..9c1c7eae 100644 --- a/pkg/user/appointee/flags.go +++ b/pkg/user/appointee/flags.go @@ -15,6 +15,13 @@ var ( Usage: "The Ethereum address of the user. Example: --appointee-address \"0x...\"", EnvVars: []string{"APPOINTEE_ADDRESS"}, } + CallerAddressFlag = cli.StringFlag{ + Name: "caller-address", + Aliases: []string{"ca"}, + Usage: "This is the address of the caller who is calling the contract function. \n" + + "If it is not provided, the account address will be used as the caller address", + EnvVars: []string{"CALLER_ADDRESS"}, + } SelectorFlag = cli.StringFlag{ Name: "selector", Aliases: []string{"s"}, @@ -27,12 +34,6 @@ var ( Usage: "The contract address for managing permissions to protocol operations.", EnvVars: []string{"TARGET_ADDRESS"}, } - BatchSetFileFlag = cli.StringFlag{ - Name: "batch-set-file", - Aliases: []string{"bsf"}, - Usage: "A YAML file for executing a batch of set permission operations.", - EnvVars: []string{"BATCH_SET_FILE"}, - } PermissionControllerAddressFlag = cli.StringFlag{ Name: "permission-controller-address", Aliases: []string{"pca"}, diff --git a/pkg/user/appointee/remove.go b/pkg/user/appointee/remove.go index 4789a2c2..93d9d4b8 100644 --- a/pkg/user/appointee/remove.go +++ b/pkg/user/appointee/remove.go @@ -86,8 +86,8 @@ func printRemoveAppointeeResult( if err != nil { return err } - noSendTxOpts := common.GetNoSendTxOpts(config.AccountAddress) - if common.IsSmartContractAddress(config.AccountAddress, ethClient) { + noSendTxOpts := common.GetNoSendTxOpts(config.CallerAddress) + if common.IsSmartContractAddress(config.CallerAddress, ethClient) { // address is a smart contract noSendTxOpts.GasLimit = 150_000 } @@ -156,7 +156,7 @@ func generateRemoveAppointeePermissionWriter( return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) } elWriter, err := common.GetELWriter( - config.AccountAddress, + config.CallerAddress, &config.SignerConfig, ethClient, elcontracts.Config{ @@ -173,6 +173,7 @@ func generateRemoveAppointeePermissionWriter( func readAndValidateRemoveConfig(cliContext *cli.Context, logger logging.Logger) (*removeConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) appointeeAddress := gethcommon.HexToAddress(cliContext.String(AppointeeAddressFlag.Name)) + callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddressFlag.Name)) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -206,6 +207,13 @@ func readAndValidateRemoveConfig(cliContext *cli.Context, logger logging.Logger) return nil, err } } + if common.IsEmptyString(callerAddress.String()) { + logger.Infof( + "Caller address not provided. Using account address (%s) as caller address", + accountAddress, + ) + callerAddress = accountAddress + } logger.Debugf( "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", @@ -220,6 +228,7 @@ func readAndValidateRemoveConfig(cliContext *cli.Context, logger logging.Logger) RPCUrl: ethRpcUrl, AccountAddress: accountAddress, AppointeeAddress: appointeeAddress, + CallerAddress: callerAddress, Target: target, Selector: selectorBytes, SignerConfig: *signerConfig, diff --git a/pkg/user/appointee/set.go b/pkg/user/appointee/set.go index 4f85cc68..7c29c990 100644 --- a/pkg/user/appointee/set.go +++ b/pkg/user/appointee/set.go @@ -109,9 +109,8 @@ func printSetAppointeeResults( if err != nil { return err } - noSendTxOpts := common.GetNoSendTxOpts(config.AccountAddress) - if common.IsSmartContractAddress(config.AccountAddress, ethClient) { - // address is a smart contract + noSendTxOpts := common.GetNoSendTxOpts(config.CallerAddress) + if common.IsSmartContractAddress(config.CallerAddress, ethClient) { noSendTxOpts.GasLimit = 150_000 } tx, err := permissionWriter.NewSetPermissionTx(request) @@ -159,7 +158,7 @@ func generateSetAppointeePermissionWriter( return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) } elWriter, err := common.GetELWriter( - config.AccountAddress, + config.CallerAddress, &config.SignerConfig, ethClient, elcontracts.Config{ @@ -176,6 +175,7 @@ func generateSetAppointeePermissionWriter( func readAndValidateSetConfig(cliContext *cli.Context, logger logging.Logger) (*setConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) appointeeAddress := gethcommon.HexToAddress(cliContext.String(AppointeeAddressFlag.Name)) + callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddressFlag.Name)) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -198,6 +198,13 @@ func readAndValidateSetConfig(cliContext *cli.Context, logger logging.Logger) (* if environment == "" { environment = common.GetEnvFromNetwork(network) } + if common.IsEmptyString(callerAddress.String()) { + logger.Infof( + "Caller address not provided. Using account address (%s) as caller address", + accountAddress, + ) + callerAddress = accountAddress + } chainID := utils.NetworkNameToChainId(network) cliContext.App.Metadata["network"] = chainID.String() @@ -223,6 +230,7 @@ func readAndValidateSetConfig(cliContext *cli.Context, logger logging.Logger) (* RPCUrl: ethRpcUrl, AccountAddress: accountAddress, AppointeeAddress: appointeeAddress, + CallerAddress: callerAddress, Target: target, Selector: selectorBytes, SignerConfig: *signerConfig, @@ -240,6 +248,7 @@ func setCommandFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &AppointeeAddressFlag, + &CallerAddressFlag, &TargetAddressFlag, &SelectorFlag, &PermissionControllerAddressFlag, diff --git a/pkg/user/appointee/types.go b/pkg/user/appointee/types.go index 6ac8c6b1..e505adc3 100644 --- a/pkg/user/appointee/types.go +++ b/pkg/user/appointee/types.go @@ -45,6 +45,7 @@ type removeConfig struct { RPCUrl string AccountAddress gethcommon.Address AppointeeAddress gethcommon.Address + CallerAddress gethcommon.Address Target gethcommon.Address SignerConfig types.SignerConfig Selector [4]byte @@ -61,6 +62,7 @@ type setConfig struct { RPCUrl string AccountAddress gethcommon.Address AppointeeAddress gethcommon.Address + CallerAddress gethcommon.Address Target gethcommon.Address SignerConfig types.SignerConfig Selector [4]byte From 7a033b7108dd3b15a96fdd348ae038e6839a4de5 Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Tue, 17 Dec 2024 11:49:12 -0800 Subject: [PATCH 16/34] Fixing usage strings for UAM commands. (#266) Co-authored-by: Brandon Chatham --- pkg/user/admin/accept.go | 8 ++------ pkg/user/admin/add_pending.go | 8 ++------ pkg/user/admin/admin.go | 8 ++------ pkg/user/admin/is_admin.go | 8 ++------ pkg/user/admin/is_pending.go | 8 ++------ pkg/user/admin/list.go | 8 ++------ pkg/user/admin/list_pending.go | 8 ++------ pkg/user/admin/remove.go | 8 ++------ pkg/user/admin/remove_pending.go | 8 ++------ pkg/user/appointee/appointee.go | 8 ++------ pkg/user/appointee/can_call.go | 8 ++------ pkg/user/appointee/list.go | 8 ++------ pkg/user/appointee/list_permissions.go | 8 ++------ pkg/user/appointee/remove.go | 8 ++------ pkg/user/appointee/set.go | 8 ++------ 15 files changed, 30 insertions(+), 90 deletions(-) diff --git a/pkg/user/admin/accept.go b/pkg/user/admin/accept.go index a6d78648..2e7147a6 100644 --- a/pkg/user/admin/accept.go +++ b/pkg/user/admin/accept.go @@ -27,12 +27,8 @@ type AcceptAdminWriter interface { func AcceptCmd(generator func(logging.Logger, *acceptAdminConfig) (AcceptAdminWriter, error)) *cli.Command { acceptCmd := &cli.Command{ - Name: "accept-admin", - Usage: "user admin accept-admin --account-address ", - UsageText: "Accepts a user to become admin who is currently pending admin acceptance.", - Description: ` - Accepts a user to become admin who is currently pending admin acceptance. - `, + Name: "accept-admin", + Usage: "Accepts a user to become admin who is currently pending admin acceptance.", Action: func(c *cli.Context) error { return acceptAdmin(c, generator) }, diff --git a/pkg/user/admin/add_pending.go b/pkg/user/admin/add_pending.go index 95877759..d1cccd60 100644 --- a/pkg/user/admin/add_pending.go +++ b/pkg/user/admin/add_pending.go @@ -27,12 +27,8 @@ type AddPendingAdminWriter interface { func AddPendingCmd(generator func(logging.Logger, *addPendingAdminConfig) (AddPendingAdminWriter, error)) *cli.Command { addPendingCmd := &cli.Command{ - Name: "add-pending-admin", - Usage: "user admin add-pending-admin --account-address --admin-address ", - UsageText: "Add an admin to be pending until accepted.", - Description: ` - Add an admin to be pending until accepted. - `, + Name: "add-pending-admin", + Usage: "Add an admin to be pending until accepted.", Action: func(context *cli.Context) error { return addPendingAdmin(context, generator) }, diff --git a/pkg/user/admin/admin.go b/pkg/user/admin/admin.go index d512e7b4..1faa5e86 100644 --- a/pkg/user/admin/admin.go +++ b/pkg/user/admin/admin.go @@ -9,12 +9,8 @@ import ( func AdminCmd(prompter utils.Prompter) *cli.Command { adminCmd := &cli.Command{ - Name: "admin", - Usage: "user admin ", - UsageText: "Manage admin users.", - Description: ` - Manage admin users. - `, + Name: "admin", + Usage: "Manage admin users.", After: telemetry.AfterRunAction(), Flags: []cli.Flag{ &flags.VerboseFlag, diff --git a/pkg/user/admin/is_admin.go b/pkg/user/admin/is_admin.go index d8d736a0..2f2a8936 100644 --- a/pkg/user/admin/is_admin.go +++ b/pkg/user/admin/is_admin.go @@ -28,12 +28,8 @@ type IsAdminReader interface { func IsAdminCmd(readerGenerator func(logging.Logger, *isAdminConfig) (IsAdminReader, error)) *cli.Command { cmd := &cli.Command{ - Name: "is-admin", - Usage: "user admin is-admin --account-address --admin-address ", - UsageText: "Checks if a user is an admin.", - Description: ` - Checks if a user is an admin. - `, + Name: "is-admin", + Usage: "Checks if a user is an admin.", Action: func(c *cli.Context) error { return isAdmin(c, readerGenerator) }, diff --git a/pkg/user/admin/is_pending.go b/pkg/user/admin/is_pending.go index 511bed08..cdcdab99 100644 --- a/pkg/user/admin/is_pending.go +++ b/pkg/user/admin/is_pending.go @@ -30,12 +30,8 @@ func IsPendingCmd( readerGenerator func(logging.Logger, *isPendingAdminConfig) (IsPendingAdminReader, error), ) *cli.Command { isPendingCmd := &cli.Command{ - Name: "is-pending-admin", - Usage: "user admin is-pending-admin --account-address --pending-admin-address ", - UsageText: "Checks if a user is pending acceptance to admin.", - Description: ` - Checks if a user is pending acceptance to admin. - `, + Name: "is-pending-admin", + Usage: "Checks if a user is pending acceptance to admin.", Action: func(c *cli.Context) error { return isPendingAdmin(c, readerGenerator) }, diff --git a/pkg/user/admin/list.go b/pkg/user/admin/list.go index 5169b4f7..417f55b0 100644 --- a/pkg/user/admin/list.go +++ b/pkg/user/admin/list.go @@ -28,12 +28,8 @@ type ListAdminsReader interface { func ListCmd(readerGenerator func(logging.Logger, *listAdminsConfig) (ListAdminsReader, error)) *cli.Command { listCmd := &cli.Command{ - Name: "list-admins", - Usage: "user admin list-admins --account-address ", - UsageText: "List all users who are admins.", - Description: ` - List all users who are admins. - `, + Name: "list-admins", + Usage: "List all users who are admins.", Action: func(c *cli.Context) error { return listAdmins(c, readerGenerator) }, diff --git a/pkg/user/admin/list_pending.go b/pkg/user/admin/list_pending.go index 546640ac..d724b7ab 100644 --- a/pkg/user/admin/list_pending.go +++ b/pkg/user/admin/list_pending.go @@ -30,12 +30,8 @@ func ListPendingCmd( readerGenerator func(logging.Logger, *listPendingAdminsConfig) (ListPendingAdminsReader, error), ) *cli.Command { listPendingCmd := &cli.Command{ - Name: "list-pending-admins", - Usage: "user admin list-pending-admins --account-address ", - UsageText: "List all users who are pending admin acceptance.", - Description: ` - List all users who are pending admin acceptance. - `, + Name: "list-pending-admins", + Usage: "List all users who are pending admin acceptance.", Action: func(c *cli.Context) error { return listPendingAdmins(c, readerGenerator) }, diff --git a/pkg/user/admin/remove.go b/pkg/user/admin/remove.go index 69c063ee..69cb30ea 100644 --- a/pkg/user/admin/remove.go +++ b/pkg/user/admin/remove.go @@ -27,12 +27,8 @@ type RemoveAdminWriter interface { func RemoveCmd(generator func(logging.Logger, *removeAdminConfig) (RemoveAdminWriter, error)) *cli.Command { removeCmd := &cli.Command{ - Name: "remove-admin", - Usage: "user admin remove-admin --account-address --admin-address ", - UsageText: "The remove command allows you to remove an admin user.", - Description: ` - The remove command allows you to remove an admin user. - `, + Name: "remove-admin", + Usage: "The remove command allows you to remove an admin user.", Action: func(context *cli.Context) error { return removeAdmin(context, generator) }, diff --git a/pkg/user/admin/remove_pending.go b/pkg/user/admin/remove_pending.go index b70edd29..55a10b14 100644 --- a/pkg/user/admin/remove_pending.go +++ b/pkg/user/admin/remove_pending.go @@ -29,12 +29,8 @@ func RemovePendingCmd( generator func(logging.Logger, *removePendingAdminConfig) (RemovePendingAdminWriter, error), ) *cli.Command { removeCmd := &cli.Command{ - Name: "remove-pending-admin", - Usage: "user admin remove-pending-admin --account-address --admin-address ", - UsageText: "Remove a user who is pending admin acceptance.", - Description: ` - Remove a user who is pending admin acceptance. - `, + Name: "remove-pending-admin", + Usage: "Remove a user who is pending admin acceptance.", Action: func(context *cli.Context) error { return removePendingAdmin(context, generator) }, diff --git a/pkg/user/appointee/appointee.go b/pkg/user/appointee/appointee.go index 2c063953..6882b72d 100644 --- a/pkg/user/appointee/appointee.go +++ b/pkg/user/appointee/appointee.go @@ -9,12 +9,8 @@ import ( func AppointeeCmd(prompter utils.Prompter) *cli.Command { appointeeCmd := &cli.Command{ - Name: "appointee", - Usage: "user appointee ", - UsageText: "User permission management operations.", - Description: ` - User permission management operations. - `, + Name: "appointee", + Usage: "User permission management operations.", After: telemetry.AfterRunAction(), Flags: []cli.Flag{ &flags.VerboseFlag, diff --git a/pkg/user/appointee/can_call.go b/pkg/user/appointee/can_call.go index ff9ed223..c5b79c97 100644 --- a/pkg/user/appointee/can_call.go +++ b/pkg/user/appointee/can_call.go @@ -29,12 +29,8 @@ type CanCallReader interface { func canCallCmd(readerGenerator func(logging.Logger, *canCallConfig) (CanCallReader, error)) *cli.Command { cmd := &cli.Command{ - Name: "can-call", - Usage: "user appointee can-call --account-address --appointee-address --target-address --selector ", - UsageText: "Checks if an appointee has a specific permission.", - Description: ` - Checks if an appointee has a specific permission. - `, + Name: "can-call", + Usage: "Checks if an appointee has a specific permission.", Action: func(c *cli.Context) error { return canCall(c, readerGenerator) }, diff --git a/pkg/user/appointee/list.go b/pkg/user/appointee/list.go index 7d244764..541a4cd1 100644 --- a/pkg/user/appointee/list.go +++ b/pkg/user/appointee/list.go @@ -29,12 +29,8 @@ type ListAppointeesReader interface { func ListCmd(readerGenerator func(logging.Logger, *listAppointeesConfig) (ListAppointeesReader, error)) *cli.Command { listCmd := &cli.Command{ - Name: "list", - Usage: "user appointee list --account-address --target-address --selector ", - UsageText: "Lists all appointed addresses for an account with the provided permissions.", - Description: ` - Lists all appointed addresses for an account with the provided permissions. - `, + Name: "list", + Usage: "Lists all appointed addresses for an account with the provided permissions.", Action: func(c *cli.Context) error { return listAppointees(c, readerGenerator) }, diff --git a/pkg/user/appointee/list_permissions.go b/pkg/user/appointee/list_permissions.go index 470f4184..f3845cd1 100644 --- a/pkg/user/appointee/list_permissions.go +++ b/pkg/user/appointee/list_permissions.go @@ -30,12 +30,8 @@ func ListPermissionsCmd( readerGenerator func(logging.Logger, *listAppointeePermissionsConfig) (PermissionsReader, error), ) *cli.Command { cmd := &cli.Command{ - Name: "list-permissions", - Usage: "user appointee list-permissions --account-address --appointee-address ", - UsageText: "List all permissions for a user.", - Description: ` - List all permissions of a user. - `, + Name: "list-permissions", + Usage: "List all permissions of a user.", Action: func(c *cli.Context) error { return listPermissions(c, readerGenerator) }, diff --git a/pkg/user/appointee/remove.go b/pkg/user/appointee/remove.go index 93d9d4b8..c564acc0 100644 --- a/pkg/user/appointee/remove.go +++ b/pkg/user/appointee/remove.go @@ -31,12 +31,8 @@ type RemoveAppointeePermissionWriter interface { func RemoveCmd(generator func(logging.Logger, *removeConfig) (RemoveAppointeePermissionWriter, error)) *cli.Command { removeCmd := &cli.Command{ - Name: "remove", - Usage: "user appointee remove --account-address --appointee-address --target-address --selector ", - UsageText: "Remove an appointee's permission", - Description: ` - Remove an appointee's permission'. - `, + Name: "remove", + Usage: "Remove an appointee's permission", After: telemetry.AfterRunAction(), Action: func(c *cli.Context) error { return removeAppointeePermission(c, generator) diff --git a/pkg/user/appointee/set.go b/pkg/user/appointee/set.go index 7c29c990..407fb1a0 100644 --- a/pkg/user/appointee/set.go +++ b/pkg/user/appointee/set.go @@ -31,12 +31,8 @@ type SetAppointeePermissionWriter interface { func SetCmd(generator func(logging.Logger, *setConfig) (SetAppointeePermissionWriter, error)) *cli.Command { setCmd := &cli.Command{ - Name: "set", - Usage: "user appointee set --account-address --appointee-address --target-address --selector ", - UsageText: "Grant an appointee a permission.", - Description: ` - Grant an appointee a permission. - `, + Name: "set", + Usage: "Grant an appointee a permission.", Action: func(c *cli.Context) error { return setAppointeePermission(c, generator) }, From 41c2b441ae9cf6e5269d53dc1d209f17c9152e2b Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Tue, 17 Dec 2024 12:38:57 -0800 Subject: [PATCH 17/34] feat: get slashable stake from contract call (#265) --- .github/workflows/integration-test.yml | 4 +- go.mod | 2 +- go.sum | 6 +- pkg/operator/allocations/show.go | 95 +++++++++++++++++++------ pkg/operator/allocations/update.go | 6 ++ pkg/operator/allocations/update_test.go | 10 +++ 6 files changed, 93 insertions(+), 30 deletions(-) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 0afe7771..0bf50ecc 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -23,7 +23,7 @@ jobs: with: repository: layr-labs/eigensdk-go token: ${{ github.token }} - ref: 525ebfea245aadbd0497a5be2999c3b19dbbd68b + ref: 7d3c828d8b48fd334d1805c2ecc27cddb7b20487 - name: Run anvil chain run: | @@ -96,7 +96,7 @@ jobs: with: repository: layr-labs/eigensdk-go token: ${{ github.token }} - ref: 525ebfea245aadbd0497a5be2999c3b19dbbd68b + ref: 7d3c828d8b48fd334d1805c2ecc27cddb7b20487 - name: Run anvil chain run: | nohup make start-anvil-with-contracts-deployed > nohup.out 2>&1 & diff --git a/go.mod b/go.mod index a6c8a2f6..7be4f333 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Layr-Labs/eigenlayer-contracts v0.3.2-mainnet-rewards github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e - github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241216183922-525ebfea245a + github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217185241-7d3c828d8b48 github.com/blang/semver/v4 v4.0.0 github.com/consensys/gnark-crypto v0.12.1 github.com/ethereum/go-ethereum v1.14.5 diff --git a/go.sum b/go.sum index ba7cd5eb..285641f4 100644 --- a/go.sum +++ b/go.sum @@ -12,10 +12,8 @@ github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 h1:G5Q1SnLmFbEjhOkky3vIHk github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12/go.mod h1:OlJd1QjqEW53wfWG/lJyPCGvrXwWVEjPQsP4TV+gttQ= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e h1:DvW0/kWHV9mZsbH2KOjEHKTSIONNPUj6X05FJvUohy4= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e/go.mod h1:T7tYN8bTdca2pkMnz9G2+ZwXYWw5gWqQUIu4KLgC/vM= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241213014620-7831a35a43a7 h1:58JjbKZs0oaGRQv/AU+XtqWvD3+dyKM4NIbqjACANN0= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241213014620-7831a35a43a7/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241216183922-525ebfea245a h1:SW/dP6D6YjN3y/vXC++TBmekXo/fDGcPXoYHitARz/I= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241216183922-525ebfea245a/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217185241-7d3c828d8b48 h1:tAuh8CCvqAY9tf8WUhAMdUFx2UiSqWfwrjmNB4prRfQ= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217185241-7d3c828d8b48/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= diff --git a/pkg/operator/allocations/show.go b/pkg/operator/allocations/show.go index 876398ba..ac8f4871 100644 --- a/pkg/operator/allocations/show.go +++ b/pkg/operator/allocations/show.go @@ -1,6 +1,7 @@ package allocations import ( + "context" "fmt" "math/big" "sort" @@ -132,7 +133,7 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { } /* - 5. Get the operator scaled shares for all strategies + 5. Get the operator shares for all strategies */ operatorDelegatedSharesMap := make(map[string]*big.Int) shares, err := elReader.GetOperatorShares(ctx, config.operatorAddress, config.strategyAddresses) @@ -144,24 +145,43 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { } /* - 6. Using all of the above, calculate SlashableMagnitudeHolders object - for displaying the allocation state of the operator + 6. Using all of the above, get Slashable Shares for the operator + */ + slashableSharesMap, err := getSlashableShares( + ctx, + config.operatorAddress, + registeredOperatorSets, + config.strategyAddresses, + elReader, + ) + if err != nil { + return eigenSdkUtils.WrapError("failed to get slashable shares", err) + } + + /* + 7. Using all of the above, calculate SlashableMagnitudeHolders object + for displaying the allocation state of the operator */ slashableMagnitudeHolders := make(SlashableMagnitudeHolders, 0) dergisteredOpsets := make(DeregsiteredOperatorSets, 0) for strategy, allocations := range allAllocations { logger.Debugf("Strategy: %s, Allocations: %v", strategy, allocations) strategyShares := operatorDelegatedSharesMap[strategy] + totalMagnitude := totalMagnitudeMap[strategy] for _, alloc := range allocations { - currentShares, currentSharesPercentage := getSharesFromMagnitude( - strategyShares, - alloc.CurrentMagnitude.Uint64(), - ) + currentShares := slashableSharesMap[gethcommon.HexToAddress(strategy)][getUniqueKey(alloc.AvsAddress, alloc.OperatorSetId)] + currentSharesPercentage := getSharePercentage(currentShares, strategyShares) + newMagnitudeBigInt := big.NewInt(0) if alloc.PendingDiff.Cmp(big.NewInt(0)) != 0 { newMagnitudeBigInt = big.NewInt(0).Add(alloc.CurrentMagnitude, alloc.PendingDiff) } - newShares, newSharesPercentage := getSharesFromMagnitude(strategyShares, newMagnitudeBigInt.Uint64()) + + newShares, newSharesPercentage := getSharesFromMagnitude( + strategyShares, + newMagnitudeBigInt.Uint64(), + totalMagnitude, + ) // Check if the operator set is not registered and add it to the unregistered list // Then skip the rest of the loop @@ -235,36 +255,65 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { return nil } -func getSharesFromMagnitude(totalScaledShare *big.Int, magnitude uint64) (*big.Int, *big.Float) { +func getSharePercentage(shares *big.Int, totalShares *big.Int) *big.Float { + percentageShares := big.NewInt(1) + percentageShares = percentageShares.Mul(shares, big.NewInt(100)) + percentageSharesFloat := new( + big.Float, + ).Quo(new(big.Float).SetInt(percentageShares), new(big.Float).SetInt(totalShares)) + return percentageSharesFloat +} + +func getSlashableShares( + ctx context.Context, + operatorAddress gethcommon.Address, + opSets []allocationmanager.OperatorSet, + strategyAddresses []gethcommon.Address, + reader elChainReader, +) (map[gethcommon.Address]map[string]*big.Int, error) { + result := make(map[gethcommon.Address]map[string]*big.Int) + for _, opSet := range opSets { + slashableSharesMap, err := reader.GetSlashableShares(ctx, operatorAddress, opSet, strategyAddresses) + if err != nil { + return nil, err + } + for strat, shares := range slashableSharesMap { + if _, ok := result[strat]; !ok { + result[strat] = make(map[string]*big.Int) + } + result[strat][getUniqueKey(opSet.Avs, opSet.Id)] = shares + } + } + return result, nil +} + +func getSharesFromMagnitude(totalShare *big.Int, magnitude uint64, totalMagnitude uint64) (*big.Int, *big.Float) { /* - * shares = totalScaledShare * magnitude / PrecisionFactor - * percentageShares = (shares / totalScaledShare) * 100 + * shares = totalShare * magnitude / totalMagnitude + * percentageShares = (shares / totalShare) * 100 */ // Check for zero magnitude or totalScaledShare to avoid divide-by-zero errors - if magnitude == 0 || totalScaledShare.Cmp(big.NewInt(0)) == 0 { + if magnitude == 0 || totalShare.Cmp(big.NewInt(0)) == 0 { return big.NewInt(0), big.NewFloat(0) } - slashableMagBigInt := big.NewInt(1) - slashableMagBigInt = slashableMagBigInt.SetUint64(magnitude) - - scaledOpShares := big.NewInt(1) - scaledOpShares = scaledOpShares.Set(totalScaledShare) - scaledOpShares = scaledOpShares.Div(scaledOpShares, PrecisionFactor) - shares := scaledOpShares.Mul(scaledOpShares, slashableMagBigInt) + opShares := big.NewInt(1) + opShares = opShares.Set(totalShare) + shares := opShares.Mul(opShares, big.NewInt(int64(magnitude))) + shares = shares.Div(shares, big.NewInt(int64(totalMagnitude))) percentageShares := big.NewInt(1) - percentageShares = percentageShares.Mul(scaledOpShares, big.NewInt(100)) + percentageShares = percentageShares.Mul(opShares, big.NewInt(100)) percentageSharesFloat := new( big.Float, - ).Quo(new(big.Float).SetInt(percentageShares), new(big.Float).SetInt(totalScaledShare)) + ).Quo(new(big.Float).SetInt(percentageShares), new(big.Float).SetInt(totalShare)) return shares, percentageSharesFloat } -func getUniqueKey(strategyAddress gethcommon.Address, opSetId uint32) string { - return fmt.Sprintf("%s-%d", strategyAddress.String(), opSetId) +func getUniqueKey(avsAddress gethcommon.Address, opSetId uint32) string { + return fmt.Sprintf("%s-%d", avsAddress.String(), opSetId) } func readAndValidateShowConfig(cCtx *cli.Context, logger *logging.Logger) (*showConfig, error) { diff --git a/pkg/operator/allocations/update.go b/pkg/operator/allocations/update.go index 5585a1c9..c0a0facf 100644 --- a/pkg/operator/allocations/update.go +++ b/pkg/operator/allocations/update.go @@ -37,6 +37,12 @@ type elChainReader interface { operator gethcommon.Address, strategy gethcommon.Address, ) (uint64, error) + GetSlashableShares( + ctx context.Context, + operatorAddress gethcommon.Address, + operatorSet allocationmanager.OperatorSet, + strategies []gethcommon.Address, + ) (map[gethcommon.Address]*big.Int, error) } func UpdateCmd(p utils.Prompter) *cli.Command { diff --git a/pkg/operator/allocations/update_test.go b/pkg/operator/allocations/update_test.go index a97dd217..1158706a 100644 --- a/pkg/operator/allocations/update_test.go +++ b/pkg/operator/allocations/update_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "math" + "math/big" "os" "testing" @@ -76,6 +77,15 @@ func (f *fakeElChainReader) GetAllocatableMagnitude( return magnitude, nil } +func (f *fakeElChainReader) GetSlashableShares( + ctx context.Context, + operatorAddress gethcommon.Address, + operatorSet allocationmanager.OperatorSet, + strategies []gethcommon.Address, +) (map[gethcommon.Address]*big.Int, error) { + return nil, errors.New("not implemented") +} + func TestGenerateAllocationsParams(t *testing.T) { avsAddress := testutils.GenerateRandomEthereumAddressString() strategyAddress := testutils.GenerateRandomEthereumAddressString() From 7c616d56615707cee6400f074f2119e6626d2b00 Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Tue, 17 Dec 2024 13:21:49 -0800 Subject: [PATCH 18/34] Adding caller-address to user admin commands to specify arbitrary caller with appointed permissions (#267) Co-authored-by: Brandon Chatham --- pkg/user/admin/accept.go | 3 ++- pkg/user/admin/add_pending.go | 12 +++++++++++- pkg/user/admin/flags.go | 2 +- pkg/user/admin/remove.go | 12 +++++++++++- pkg/user/admin/remove_pending.go | 12 +++++++++++- pkg/user/admin/types.go | 3 +++ pkg/user/appointee/remove.go | 1 + 7 files changed, 40 insertions(+), 5 deletions(-) diff --git a/pkg/user/admin/accept.go b/pkg/user/admin/accept.go index 2e7147a6..531842ae 100644 --- a/pkg/user/admin/accept.go +++ b/pkg/user/admin/accept.go @@ -72,7 +72,7 @@ func readAndValidateAcceptAdminConfig( logger logging.Logger, ) (*acceptAdminConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) - callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddress.Name)) + callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddressFlag.Name)) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -127,6 +127,7 @@ func acceptFlags() []cli.Flag { cmdFlags := []cli.Flag{ &flags.VerboseFlag, &AccountAddressFlag, + &CallerAddressFlag, &PermissionControllerAddressFlag, &flags.OutputTypeFlag, &flags.OutputFileFlag, diff --git a/pkg/user/admin/add_pending.go b/pkg/user/admin/add_pending.go index d1cccd60..f32d7d07 100644 --- a/pkg/user/admin/add_pending.go +++ b/pkg/user/admin/add_pending.go @@ -77,12 +77,20 @@ func readAndValidateAddPendingAdminConfig( ) (*addPendingAdminConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) adminAddress := gethcommon.HexToAddress(cliContext.String(AdminAddressFlag.Name)) + callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddressFlag.Name)) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) if environment == "" { environment = common.GetEnvFromNetwork(network) } + if common.IsEmptyString(callerAddress.String()) { + logger.Infof( + "Caller address not provided. Using account address (%s) as caller address", + accountAddress, + ) + callerAddress = accountAddress + } signerConfig, err := common.GetSignerConfig(cliContext, logger) if err != nil { // We don't want to throw error since people can still use it to generate the claim @@ -113,6 +121,7 @@ func readAndValidateAddPendingAdminConfig( RPCUrl: ethRpcUrl, AccountAddress: accountAddress, AdminAddress: adminAddress, + CallerAddress: callerAddress, SignerConfig: *signerConfig, PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), ChainID: chainID, @@ -129,7 +138,7 @@ func generateAddPendingAdminWriter( return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) } return common.GetELWriter( - config.AccountAddress, + config.CallerAddress, &config.SignerConfig, ethClient, elcontracts.Config{ @@ -147,6 +156,7 @@ func addPendingFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &AdminAddressFlag, + &CallerAddressFlag, &PermissionControllerAddressFlag, &flags.OutputTypeFlag, &flags.OutputFileFlag, diff --git a/pkg/user/admin/flags.go b/pkg/user/admin/flags.go index 3a0d3b70..a5d7ce82 100644 --- a/pkg/user/admin/flags.go +++ b/pkg/user/admin/flags.go @@ -15,7 +15,7 @@ var ( Usage: "user admin ... --admin-address \"0x...\"", EnvVars: []string{"ADMIN_ADDRESS"}, } - CallerAddress = cli.StringFlag{ + CallerAddressFlag = cli.StringFlag{ Name: "caller-address", Aliases: []string{"ca"}, Usage: "This is the address of the caller who is calling the contract function. \n" + diff --git a/pkg/user/admin/remove.go b/pkg/user/admin/remove.go index 69cb30ea..8e16c91d 100644 --- a/pkg/user/admin/remove.go +++ b/pkg/user/admin/remove.go @@ -77,12 +77,20 @@ func readAndValidateRemoveAdminConfig( ) (*removeAdminConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) adminAddress := gethcommon.HexToAddress(cliContext.String(AdminAddressFlag.Name)) + callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddressFlag.Name)) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) if environment == "" { environment = common.GetEnvFromNetwork(network) } + if common.IsEmptyString(callerAddress.String()) { + logger.Infof( + "Caller address not provided. Using account address (%s) as caller address", + accountAddress, + ) + callerAddress = accountAddress + } signerConfig, err := common.GetSignerConfig(cliContext, logger) if err != nil { // We don't want to throw error since people can still use it to generate the claim @@ -113,6 +121,7 @@ func readAndValidateRemoveAdminConfig( RPCUrl: ethRpcUrl, AccountAddress: accountAddress, AdminAddress: adminAddress, + CallerAddress: callerAddress, PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), SignerConfig: *signerConfig, ChainID: chainID, @@ -129,7 +138,7 @@ func generateRemoveAdminWriter( return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) } return common.GetELWriter( - config.AccountAddress, + config.CallerAddress, &config.SignerConfig, ethClient, elcontracts.Config{ @@ -147,6 +156,7 @@ func removeFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &AdminAddressFlag, + &CallerAddressFlag, &PermissionControllerAddressFlag, &flags.OutputTypeFlag, &flags.OutputFileFlag, diff --git a/pkg/user/admin/remove_pending.go b/pkg/user/admin/remove_pending.go index 55a10b14..3b80ee22 100644 --- a/pkg/user/admin/remove_pending.go +++ b/pkg/user/admin/remove_pending.go @@ -79,12 +79,20 @@ func readAndValidateRemovePendingAdminConfig( ) (*removePendingAdminConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) adminAddress := gethcommon.HexToAddress(cliContext.String(AdminAddressFlag.Name)) + callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddressFlag.Name)) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) if environment == "" { environment = common.GetEnvFromNetwork(network) } + if common.IsEmptyString(callerAddress.String()) { + logger.Infof( + "Caller address not provided. Using account address (%s) as caller address", + accountAddress, + ) + callerAddress = accountAddress + } signerConfig, err := common.GetSignerConfig(cliContext, logger) if err != nil { // We don't want to throw error since people can still use it to generate the claim @@ -115,6 +123,7 @@ func readAndValidateRemovePendingAdminConfig( RPCUrl: ethRpcUrl, AccountAddress: accountAddress, AdminAddress: adminAddress, + CallerAddress: callerAddress, PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), SignerConfig: *signerConfig, ChainID: chainID, @@ -131,7 +140,7 @@ func generateRemovePendingAdminWriter( return nil, eigenSdkUtils.WrapError("failed to create new eth client", err) } return common.GetELWriter( - config.AccountAddress, + config.CallerAddress, &config.SignerConfig, ethClient, elcontracts.Config{ @@ -149,6 +158,7 @@ func removePendingAdminFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &AdminAddressFlag, + &CallerAddressFlag, &PermissionControllerAddressFlag, &flags.BroadcastFlag, &flags.OutputTypeFlag, diff --git a/pkg/user/admin/types.go b/pkg/user/admin/types.go index e52550f2..343b6299 100644 --- a/pkg/user/admin/types.go +++ b/pkg/user/admin/types.go @@ -61,6 +61,7 @@ type addPendingAdminConfig struct { RPCUrl string AccountAddress gethcommon.Address AdminAddress gethcommon.Address + CallerAddress gethcommon.Address PermissionManagerAddress gethcommon.Address SignerConfig types.SignerConfig ChainID *big.Int @@ -72,6 +73,7 @@ type removeAdminConfig struct { RPCUrl string AccountAddress gethcommon.Address AdminAddress gethcommon.Address + CallerAddress gethcommon.Address PermissionManagerAddress gethcommon.Address SignerConfig types.SignerConfig ChainID *big.Int @@ -83,6 +85,7 @@ type removePendingAdminConfig struct { RPCUrl string AccountAddress gethcommon.Address AdminAddress gethcommon.Address + CallerAddress gethcommon.Address PermissionManagerAddress gethcommon.Address SignerConfig types.SignerConfig ChainID *big.Int diff --git a/pkg/user/appointee/remove.go b/pkg/user/appointee/remove.go index c564acc0..65b1c11b 100644 --- a/pkg/user/appointee/remove.go +++ b/pkg/user/appointee/remove.go @@ -242,6 +242,7 @@ func removeCommandFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &AppointeeAddressFlag, + &CallerAddressFlag, &TargetAddressFlag, &SelectorFlag, &PermissionControllerAddressFlag, From 367cbe0cd952bed3aed4a6fccbd5fcbf7b6b51b4 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Tue, 17 Dec 2024 13:46:47 -0800 Subject: [PATCH 19/34] fix: flakey test (#268) --- pkg/operator/allocations/update_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/operator/allocations/update_test.go b/pkg/operator/allocations/update_test.go index 1158706a..e85e942b 100644 --- a/pkg/operator/allocations/update_test.go +++ b/pkg/operator/allocations/update_test.go @@ -219,7 +219,8 @@ func TestGenerateAllocationsParams(t *testing.T) { assert.Error(t, err) } else { assert.NoError(t, err) - assert.Equal(t, tt.expectedAllocations, allocations) + assert.ElementsMatch(t, tt.expectedAllocations.Allocations, allocations.Allocations) + assert.Equal(t, tt.expectedAllocations.AllocatableMagnitudes, allocations.AllocatableMagnitudes) } }) } From 22be54289e3001be1f4cda61d410bd7bed2aa86a Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Tue, 17 Dec 2024 14:27:09 -0800 Subject: [PATCH 20/34] update deps --- .github/workflows/integration-test.yml | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 0bf50ecc..3f9a4cc9 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -23,7 +23,7 @@ jobs: with: repository: layr-labs/eigensdk-go token: ${{ github.token }} - ref: 7d3c828d8b48fd334d1805c2ecc27cddb7b20487 + ref: 549e0185cee644d0a6fc9c9863f1cf76d9ef971f - name: Run anvil chain run: | @@ -96,7 +96,7 @@ jobs: with: repository: layr-labs/eigensdk-go token: ${{ github.token }} - ref: 7d3c828d8b48fd334d1805c2ecc27cddb7b20487 + ref: 549e0185cee644d0a6fc9c9863f1cf76d9ef971f - name: Run anvil chain run: | nohup make start-anvil-with-contracts-deployed > nohup.out 2>&1 & diff --git a/go.mod b/go.mod index 7be4f333..ce9cb4b5 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Layr-Labs/eigenlayer-contracts v0.3.2-mainnet-rewards github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e - github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217185241-7d3c828d8b48 + github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217222530-549e0185cee6 github.com/blang/semver/v4 v4.0.0 github.com/consensys/gnark-crypto v0.12.1 github.com/ethereum/go-ethereum v1.14.5 diff --git a/go.sum b/go.sum index 285641f4..7a6ea1d4 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 h1:G5Q1SnLmFbEjhOkky3vIHk github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12/go.mod h1:OlJd1QjqEW53wfWG/lJyPCGvrXwWVEjPQsP4TV+gttQ= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e h1:DvW0/kWHV9mZsbH2KOjEHKTSIONNPUj6X05FJvUohy4= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e/go.mod h1:T7tYN8bTdca2pkMnz9G2+ZwXYWw5gWqQUIu4KLgC/vM= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217185241-7d3c828d8b48 h1:tAuh8CCvqAY9tf8WUhAMdUFx2UiSqWfwrjmNB4prRfQ= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217185241-7d3c828d8b48/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217222530-549e0185cee6 h1:v2SQn+Yq/HMAkv0a11NHnZXJS0K+2F4JWU0ogOV6+jg= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217222530-549e0185cee6/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= From b624bdecc447843950a1a255b4115b3f67a5d619 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Tue, 17 Dec 2024 14:29:41 -0800 Subject: [PATCH 21/34] fix test --- pkg/operator/status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/operator/status.go b/pkg/operator/status.go index 16a9fffc..e6a1ab00 100644 --- a/pkg/operator/status.go +++ b/pkg/operator/status.go @@ -129,7 +129,7 @@ func printOperatorDetails(operator eigensdkTypes.Operator) { fmt.Println("--------------------------- Operator Details ---------------------------") fmt.Printf("Address: %s\n", operator.Address) fmt.Printf("Delegation Approver Address: %s\n", operator.DelegationApproverAddress) - fmt.Printf("Staker Opt Out Window Blocks: %d\n", operator.StakerOptOutWindowBlocks) + fmt.Printf("Allocation Delay: %d\n", operator.AllocationDelay) fmt.Println("------------------------------------------------------------------------") fmt.Println() } From 1f2c807f2a00b9ec4f62c94389ff11b7d9b6a123 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Tue, 17 Dec 2024 14:35:42 -0800 Subject: [PATCH 22/34] update docs --- pkg/operator/config/operator-config-example.yaml | 7 ++++--- tests/keystore/operator-ci.yaml | 1 - tests/web3signer/operator-ci.yaml | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/operator/config/operator-config-example.yaml b/pkg/operator/config/operator-config-example.yaml index 0fe85a8b..78e036be 100644 --- a/pkg/operator/config/operator-config-example.yaml +++ b/pkg/operator/config/operator-config-example.yaml @@ -12,9 +12,10 @@ operator: # For now, you can leave it with the default value for un-gated delegation requests # Once we enable gated delegation requests, you can update this field with the address of the approver delegation_approver_address: 0x0000000000000000000000000000000000000000 - # Please refer to this link for more details on this field https://github.com/Layr-Labs/eigenlayer-contracts/blob/92ccacc868785350973afc15e90a18dcd39fbc0b/src/contracts/interfaces/IDelegationManager.sol#L33: - # Please keep this field to 0, and it can be updated later using EigenLayer CLI - staker_opt_out_window_blocks: 0 + # This is the delay in blocks after which your allocations will take into effect. This doesn't applies + # to deallocation which is set by the protocol. + allocation_delay: 1200 + # This is the URL where the metadata of the operator is hosted. metadata_url: https://raw.githubusercontent.com/Layr-Labs/eigenlayer-cli/master/pkg/operator/config/metadata-example.json # EigenLayer Delegation manager contract address diff --git a/tests/keystore/operator-ci.yaml b/tests/keystore/operator-ci.yaml index 0f93a961..f718a863 100644 --- a/tests/keystore/operator-ci.yaml +++ b/tests/keystore/operator-ci.yaml @@ -1,7 +1,6 @@ operator: address: 0xcaB1b44dd1f1C265405878Ac1179cd94D0dBA634 delegation_approver_address: 0xcaB1b44dd1f1C265405878Ac1179cd94D0dBA634 - staker_opt_out_window_blocks: 0 metadata_url: https://madhur-test-public.s3.us-east-2.amazonaws.com/metadata.json allocation_delay: 1200 el_delegation_manager_address: 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 diff --git a/tests/web3signer/operator-ci.yaml b/tests/web3signer/operator-ci.yaml index d7d56811..451a0c34 100644 --- a/tests/web3signer/operator-ci.yaml +++ b/tests/web3signer/operator-ci.yaml @@ -1,7 +1,6 @@ operator: address: 0x7dbc809c1ec153d45ffb0c75fb4fded68e34699e delegation_approver_address: 0xcaB1b44dd1f1C265405878Ac1179cd94D0dBA634 - staker_opt_out_window_blocks: 0 metadata_url: https://madhur-test-public.s3.us-east-2.amazonaws.com/metadata.json allocation_delay: 1200 el_delegation_manager_address: 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 From 6b6f34c200980c7d078d86d843c3c7ad87fc04ab Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Tue, 17 Dec 2024 15:14:49 -0800 Subject: [PATCH 23/34] fix: add caller address to opset reg/dereg (#269) --- pkg/operator/deregister_operator_sets.go | 17 ++++++++++++----- pkg/operator/register_operator_sets.go | 17 ++++++++++++----- pkg/operator/types.go | 2 ++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/pkg/operator/deregister_operator_sets.go b/pkg/operator/deregister_operator_sets.go index 7e908112..0cdbea86 100644 --- a/pkg/operator/deregister_operator_sets.go +++ b/pkg/operator/deregister_operator_sets.go @@ -66,7 +66,7 @@ func deregisterAction(cCtx *cli.Context, p utils.Prompter) error { } logger.Info("Signing and broadcasting deregistration transaction") eLWriter, err := common.GetELWriter( - config.operatorAddress, + config.callerAddress, config.signerConfig, ethClient, elcontracts.Config{ @@ -92,7 +92,7 @@ func deregisterAction(cCtx *cli.Context, p utils.Prompter) error { } common.PrintTransactionInfo(receipt.TxHash.String(), config.chainID) } else { - noSendTxOpts := common.GetNoSendTxOpts(config.operatorAddress) + noSendTxOpts := common.GetNoSendTxOpts(config.callerAddress) _, _, contractBindings, err := elcontracts.BuildClients(elcontracts.Config{ DelegationManagerAddress: config.delegationManagerAddress, }, ethClient, nil, logger, nil) @@ -103,7 +103,7 @@ func deregisterAction(cCtx *cli.Context, p utils.Prompter) error { // since balance of contract can be 0, as it can be called by an EOA // to claim. So we hardcode the gas limit to 150_000 so that we can // create unsigned tx without gas limit estimation from contract bindings - if common.IsSmartContractAddress(config.operatorAddress, ethClient) { + if common.IsSmartContractAddress(config.callerAddress, ethClient) { // address is a smart contract noSendTxOpts.GasLimit = 150_000 } @@ -169,7 +169,12 @@ func readAndValidateDeregisterConfig(cCtx *cli.Context, logger logging.Logger) ( broadcast := cCtx.Bool(flags.BroadcastFlag.Name) isSilent := cCtx.Bool(flags.SilentFlag.Name) - operatorAddress := gethcommon.HexToAddress(cCtx.String(flags.OperatorAddressFlag.Name)) + operatorAddress := cCtx.String(flags.OperatorAddressFlag.Name) + callerAddress := cCtx.String(flags.CallerAddressFlag.Name) + if common.IsEmptyString(callerAddress) { + logger.Infof("Caller address not provided. Using operator address (%s) as caller address", operatorAddress) + callerAddress = operatorAddress + } avsAddress := gethcommon.HexToAddress(cCtx.String(flags.AVSAddressFlag.Name)) // Get signerConfig @@ -199,7 +204,8 @@ func readAndValidateDeregisterConfig(cCtx *cli.Context, logger logging.Logger) ( config := &DeregisterConfig{ avsAddress: avsAddress, operatorSetIds: operatorSetIds, - operatorAddress: operatorAddress, + operatorAddress: gethcommon.HexToAddress(operatorAddress), + callerAddress: gethcommon.HexToAddress(callerAddress), network: network, environment: environment, broadcast: broadcast, @@ -229,6 +235,7 @@ func getDeregistrationFlags() []cli.Flag { &flags.OperatorSetIdsFlag, &flags.DelegationManagerAddressFlag, &flags.SilentFlag, + &flags.CallerAddressFlag, } allFlags := append(baseFlags, flags.GetSignerFlags()...) diff --git a/pkg/operator/register_operator_sets.go b/pkg/operator/register_operator_sets.go index a4b76e28..07b4e573 100644 --- a/pkg/operator/register_operator_sets.go +++ b/pkg/operator/register_operator_sets.go @@ -62,7 +62,7 @@ func registerOperatorSetsAction(cCtx *cli.Context, p utils.Prompter) error { } logger.Info("Signing and broadcasting registration transaction") eLWriter, err := common.GetELWriter( - config.operatorAddress, + config.callerAddress, config.signerConfig, ethClient, elcontracts.Config{ @@ -87,7 +87,7 @@ func registerOperatorSetsAction(cCtx *cli.Context, p utils.Prompter) error { } common.PrintTransactionInfo(receipt.TxHash.String(), config.chainID) } else { - noSendTxOpts := common.GetNoSendTxOpts(config.operatorAddress) + noSendTxOpts := common.GetNoSendTxOpts(config.callerAddress) _, _, contractBindings, err := elcontracts.BuildClients(elcontracts.Config{ DelegationManagerAddress: config.delegationManagerAddress, }, ethClient, nil, logger, nil) @@ -98,7 +98,7 @@ func registerOperatorSetsAction(cCtx *cli.Context, p utils.Prompter) error { // since balance of contract can be 0, as it can be called by an EOA // to claim. So we hardcode the gas limit to 150_000 so that we can // create unsigned tx without gas limit estimation from contract bindings - if common.IsSmartContractAddress(config.operatorAddress, ethClient) { + if common.IsSmartContractAddress(config.callerAddress, ethClient) { // address is a smart contract noSendTxOpts.GasLimit = 150_000 } @@ -155,7 +155,12 @@ func readAndValidateRegisterOperatorSetsConfig(cCtx *cli.Context, logger logging broadcast := cCtx.Bool(flags.BroadcastFlag.Name) isSilent := cCtx.Bool(flags.SilentFlag.Name) - operatorAddress := gethcommon.HexToAddress(cCtx.String(flags.OperatorAddressFlag.Name)) + operatorAddress := cCtx.String(flags.OperatorAddressFlag.Name) + callerAddress := cCtx.String(flags.CallerAddressFlag.Name) + if common.IsEmptyString(callerAddress) { + logger.Infof("Caller address not provided. Using operator address (%s) as caller address", operatorAddress) + callerAddress = operatorAddress + } avsAddress := gethcommon.HexToAddress(cCtx.String(flags.AVSAddressFlag.Name)) // Get signerConfig @@ -185,7 +190,8 @@ func readAndValidateRegisterOperatorSetsConfig(cCtx *cli.Context, logger logging config := &RegisterConfig{ avsAddress: avsAddress, operatorSetIds: operatorSetIds, - operatorAddress: operatorAddress, + operatorAddress: gethcommon.HexToAddress(operatorAddress), + callerAddress: gethcommon.HexToAddress(callerAddress), network: network, environment: environment, broadcast: broadcast, @@ -215,6 +221,7 @@ func getRegistrationFlags() []cli.Flag { &flags.OperatorSetIdsFlag, &flags.DelegationManagerAddressFlag, &flags.SilentFlag, + &flags.CallerAddressFlag, } allFlags := append(baseFlags, flags.GetSignerFlags()...) diff --git a/pkg/operator/types.go b/pkg/operator/types.go index 2f4b4fdb..87f5b44c 100644 --- a/pkg/operator/types.go +++ b/pkg/operator/types.go @@ -11,6 +11,7 @@ type DeregisterConfig struct { avsAddress common.Address operatorSetIds []uint32 operatorAddress common.Address + callerAddress common.Address network string environment string broadcast bool @@ -27,6 +28,7 @@ type RegisterConfig struct { avsAddress common.Address operatorSetIds []uint32 operatorAddress common.Address + callerAddress common.Address network string environment string broadcast bool From 1739cef453eea5706655725d38c875b21f514550 Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Tue, 17 Dec 2024 15:21:39 -0800 Subject: [PATCH 24/34] Adding output support to file or terminal without broadcast for user admin commands. (#270) Co-authored-by: Brandon Chatham --- pkg/user/admin/accept.go | 88 +++++++++++++++++++++++-- pkg/user/admin/accept_test.go | 26 +++++++- pkg/user/admin/add_pending.go | 86 +++++++++++++++++++++++-- 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 ++++++- pkg/user/admin/types.go | 12 ++++ 9 files changed, 426 insertions(+), 41 deletions(-) diff --git a/pkg/user/admin/accept.go b/pkg/user/admin/accept.go index 531842ae..d9551997 100644 --- a/pkg/user/admin/accept.go +++ b/pkg/user/admin/accept.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 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..1347d8c8 100644 --- a/pkg/user/admin/accept_test.go +++ b/pkg/user/admin/accept_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" "github.com/stretchr/testify/assert" @@ -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.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/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..a27c6d3a 100644 --- a/pkg/user/admin/remove.go +++ b/pkg/user/admin/remove.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 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..256ba389 100644 --- a/pkg/user/admin/remove_pending.go +++ b/pkg/user/admin/remove_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 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) 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 81618ef9486f8cb2afa3118ff0cc3b993dd7cc03 Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Tue, 17 Dec 2024 15:59:30 -0800 Subject: [PATCH 25/34] Updating appointee set & remove to support output parameters (#271) Co-authored-by: Brandon Chatham --- go.mod | 2 +- go.sum | 2 ++ pkg/user/appointee/remove.go | 45 ++++++++++++++++--------------- pkg/user/appointee/remove_test.go | 33 ++++++++++++++++++++--- pkg/user/appointee/set.go | 6 +++-- pkg/user/appointee/set_test.go | 10 ++++--- 6 files changed, 66 insertions(+), 32 deletions(-) diff --git a/go.mod b/go.mod index ce9cb4b5..f3eb014e 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Layr-Labs/eigenlayer-contracts v0.3.2-mainnet-rewards github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e - github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217222530-549e0185cee6 + github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217234459-1dd4a5c5b30a github.com/blang/semver/v4 v4.0.0 github.com/consensys/gnark-crypto v0.12.1 github.com/ethereum/go-ethereum v1.14.5 diff --git a/go.sum b/go.sum index 7a6ea1d4..42d4a602 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248- github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e/go.mod h1:T7tYN8bTdca2pkMnz9G2+ZwXYWw5gWqQUIu4KLgC/vM= github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217222530-549e0185cee6 h1:v2SQn+Yq/HMAkv0a11NHnZXJS0K+2F4JWU0ogOV6+jg= github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217222530-549e0185cee6/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217234459-1dd4a5c5b30a h1:spyS+Tp1PgVIPmAesVVRuOkC3jAZRyKXhttAieTBxmg= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217234459-1dd4a5c5b30a/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= diff --git a/pkg/user/appointee/remove.go b/pkg/user/appointee/remove.go index 65b1c11b..d3c62509 100644 --- a/pkg/user/appointee/remove.go +++ b/pkg/user/appointee/remove.go @@ -12,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" @@ -25,6 +26,7 @@ type RemoveAppointeePermissionWriter interface { request elcontracts.RemovePermissionRequest, ) (*gethtypes.Receipt, error) NewRemovePermissionTx( + txOpts *bind.TransactOpts, request elcontracts.RemovePermissionRequest, ) (*gethtypes.Transaction, error) } @@ -69,10 +71,27 @@ func removeAppointeePermission( if config.Broadcast { return broadcastRemoveAppointeeTx(ctx, permissionWriter, config, removePermissionRequest) } - return printRemoveAppointeeResult(logger, permissionWriter, config, removePermissionRequest) + return printRemoveAppointeeTx(logger, permissionWriter, config, removePermissionRequest) } -func printRemoveAppointeeResult( +func broadcastRemoveAppointeeTx( + ctx context.Context, + permissionWriter RemoveAppointeePermissionWriter, + config *removeConfig, + request elcontracts.RemovePermissionRequest, +) error { + receipt, err := permissionWriter.RemovePermission( + ctx, + request, + ) + if err != nil { + return err + } + common.PrintTransactionInfo(receipt.TxHash.String(), config.ChainID) + return nil +} + +func printRemoveAppointeeTx( logger logging.Logger, permissionWriter RemoveAppointeePermissionWriter, config *removeConfig, @@ -84,10 +103,9 @@ func printRemoveAppointeeResult( } noSendTxOpts := common.GetNoSendTxOpts(config.CallerAddress) if common.IsSmartContractAddress(config.CallerAddress, ethClient) { - // address is a smart contract noSendTxOpts.GasLimit = 150_000 } - unsignedTx, err := permissionWriter.NewRemovePermissionTx(request) + unsignedTx, err := permissionWriter.NewRemovePermissionTx(noSendTxOpts, request) if err != nil { return eigenSdkUtils.WrapError("failed to create unsigned tx", err) } @@ -109,7 +127,7 @@ func printRemoveAppointeeResult( fmt.Println() } fmt.Printf( - "Appointee %s will be lose permission to target %s selector %s by account %s\n", + "Appointee %s will lose permission to target %s selector %s for account %s\n", config.AppointeeAddress, config.Target, config.Selector, @@ -123,23 +141,6 @@ func printRemoveAppointeeResult( return nil } -func broadcastRemoveAppointeeTx( - ctx context.Context, - permissionWriter RemoveAppointeePermissionWriter, - config *removeConfig, - request elcontracts.RemovePermissionRequest, -) error { - receipt, err := permissionWriter.RemovePermission( - ctx, - request, - ) - if err != nil { - return err - } - common.PrintTransactionInfo(receipt.TxHash.String(), config.ChainID) - return nil -} - func generateRemoveAppointeePermissionWriter( prompter utils.Prompter, ) func( diff --git a/pkg/user/appointee/remove_test.go b/pkg/user/appointee/remove_test.go index cd25e379..61fee75e 100644 --- a/pkg/user/appointee/remove_test.go +++ b/pkg/user/appointee/remove_test.go @@ -5,6 +5,7 @@ import ( "errors" "testing" + "github.com/ethereum/go-ethereum/accounts/abi/bind" gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" @@ -16,7 +17,7 @@ import ( type mockRemoveAppointeePermissionWriter struct { removePermissionFunc func(ctx context.Context, request elcontracts.RemovePermissionRequest) (*gethtypes.Receipt, error) - newRemovePermissionTxFunc func(request elcontracts.RemovePermissionRequest) (*gethtypes.Transaction, error) + newRemovePermissionTxFunc func(txOpts *bind.TransactOpts, request elcontracts.RemovePermissionRequest) (*gethtypes.Transaction, error) } func (m *mockRemoveAppointeePermissionWriter) RemovePermission( @@ -27,9 +28,10 @@ func (m *mockRemoveAppointeePermissionWriter) RemovePermission( } func (m *mockRemoveAppointeePermissionWriter) NewRemovePermissionTx( + txOpts *bind.TransactOpts, request elcontracts.RemovePermissionRequest, ) (*gethtypes.Transaction, error) { - return m.newRemovePermissionTxFunc(request) + return m.newRemovePermissionTxFunc(txOpts, request) } func generateMockRemoveWriter(err error) func(logging.Logger, *removeConfig) (RemoveAppointeePermissionWriter, error) { @@ -38,7 +40,7 @@ func generateMockRemoveWriter(err error) func(logging.Logger, *removeConfig) (Re removePermissionFunc: func(ctx context.Context, request elcontracts.RemovePermissionRequest) (*gethtypes.Receipt, error) { return &gethtypes.Receipt{}, err }, - newRemovePermissionTxFunc: func(request elcontracts.RemovePermissionRequest) (*gethtypes.Transaction, error) { + newRemovePermissionTxFunc: func(txOpts *bind.TransactOpts, request elcontracts.RemovePermissionRequest) (*gethtypes.Transaction, error) { return &gethtypes.Transaction{}, err }, }, nil @@ -94,3 +96,28 @@ func TestRemoveCmd_GeneratorError(t *testing.T) { assert.Error(t, err) assert.Contains(t, err.Error(), expectedError) } + +func TestRemoveCmd_RemovePermissionError(t *testing.T) { + expectedError := "error removing appointee permission" + app := cli.NewApp() + app.Commands = []*cli.Command{ + RemoveCmd(generateMockRemoveWriter(errors.New(expectedError))), + } + + args := []string{ + "TestRemoveCmd_RemovePermissionError", + "remove", + "--account-address", "0x1234567890abcdef1234567890abcdef12345678", + "--appointee-address", "0xabcdef1234567890abcdef1234567890abcdef12", + "--target-address", "0x9876543210fedcba9876543210fedcba98765432", + "--selector", "0x1A2B3C4D", + "--network", "holesky", + "--eth-rpc-url", "https://ethereum-holesky.publicnode.com/", + "--ecdsa-private-key", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "--broadcast", + } + + err := app.Run(args) + assert.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} diff --git a/pkg/user/appointee/set.go b/pkg/user/appointee/set.go index 407fb1a0..1d540a73 100644 --- a/pkg/user/appointee/set.go +++ b/pkg/user/appointee/set.go @@ -12,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" @@ -25,6 +26,7 @@ type SetAppointeePermissionWriter interface { request elcontracts.SetPermissionRequest, ) (*gethtypes.Receipt, error) NewSetPermissionTx( + txOpts *bind.TransactOpts, request elcontracts.SetPermissionRequest, ) (*gethtypes.Transaction, error) } @@ -109,11 +111,11 @@ func printSetAppointeeResults( if common.IsSmartContractAddress(config.CallerAddress, ethClient) { noSendTxOpts.GasLimit = 150_000 } - tx, err := permissionWriter.NewSetPermissionTx(request) + + tx, err := permissionWriter.NewSetPermissionTx(noSendTxOpts, request) if err != nil { return eigenSdkUtils.WrapError("failed to create unsigned tx", err) } - if config.OutputType == string(common.OutputType_Calldata) { calldataHex := gethcommon.Bytes2Hex(tx.Data()) if !common.IsEmptyString(config.OutputFile) { diff --git a/pkg/user/appointee/set_test.go b/pkg/user/appointee/set_test.go index e049ad36..5c5e39d1 100644 --- a/pkg/user/appointee/set_test.go +++ b/pkg/user/appointee/set_test.go @@ -7,15 +7,16 @@ 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" gethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" "github.com/urfave/cli/v2" ) type mockSetAppointeePermissionWriter struct { setPermissionFunc func(ctx context.Context, request elcontracts.SetPermissionRequest) (*gethtypes.Receipt, error) - newSetPermissionTxFunc func(request elcontracts.SetPermissionRequest) (*gethtypes.Transaction, error) + newSetPermissionTxFunc func(txOpts *bind.TransactOpts, request elcontracts.SetPermissionRequest) (*gethtypes.Transaction, error) } func (m *mockSetAppointeePermissionWriter) SetPermission( @@ -26,9 +27,10 @@ func (m *mockSetAppointeePermissionWriter) SetPermission( } func (m *mockSetAppointeePermissionWriter) NewSetPermissionTx( + txOpts *bind.TransactOpts, request elcontracts.SetPermissionRequest, ) (*gethtypes.Transaction, error) { - return m.newSetPermissionTxFunc(request) + return m.newSetPermissionTxFunc(txOpts, request) } func generateMockSetWriter(err error) func(logging.Logger, *setConfig) (SetAppointeePermissionWriter, error) { @@ -37,7 +39,7 @@ func generateMockSetWriter(err error) func(logging.Logger, *setConfig) (SetAppoi setPermissionFunc: func(ctx context.Context, request elcontracts.SetPermissionRequest) (*gethtypes.Receipt, error) { return &gethtypes.Receipt{}, err }, - newSetPermissionTxFunc: func(request elcontracts.SetPermissionRequest) (*gethtypes.Transaction, error) { + newSetPermissionTxFunc: func(txOpts *bind.TransactOpts, request elcontracts.SetPermissionRequest) (*gethtypes.Transaction, error) { return &gethtypes.Transaction{}, err }, }, nil From a1d46bea2725f44b7dc27832b63b1fa45683f854 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Tue, 17 Dec 2024 16:56:40 -0800 Subject: [PATCH 26/34] fix: showing allocations for registered ops (#272) --- go.sum | 2 -- pkg/operator/allocations/show.go | 42 ++++++++++++++++++-------- pkg/operator/register_operator_sets.go | 9 +++--- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/go.sum b/go.sum index 42d4a602..31a66dd5 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,6 @@ github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 h1:G5Q1SnLmFbEjhOkky3vIHk github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12/go.mod h1:OlJd1QjqEW53wfWG/lJyPCGvrXwWVEjPQsP4TV+gttQ= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e h1:DvW0/kWHV9mZsbH2KOjEHKTSIONNPUj6X05FJvUohy4= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e/go.mod h1:T7tYN8bTdca2pkMnz9G2+ZwXYWw5gWqQUIu4KLgC/vM= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217222530-549e0185cee6 h1:v2SQn+Yq/HMAkv0a11NHnZXJS0K+2F4JWU0ogOV6+jg= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217222530-549e0185cee6/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217234459-1dd4a5c5b30a h1:spyS+Tp1PgVIPmAesVVRuOkC3jAZRyKXhttAieTBxmg= github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241217234459-1dd4a5c5b30a/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= diff --git a/pkg/operator/allocations/show.go b/pkg/operator/allocations/show.go index ac8f4871..55b1235f 100644 --- a/pkg/operator/allocations/show.go +++ b/pkg/operator/allocations/show.go @@ -169,23 +169,25 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { strategyShares := operatorDelegatedSharesMap[strategy] totalMagnitude := totalMagnitudeMap[strategy] for _, alloc := range allocations { - currentShares := slashableSharesMap[gethcommon.HexToAddress(strategy)][getUniqueKey(alloc.AvsAddress, alloc.OperatorSetId)] - currentSharesPercentage := getSharePercentage(currentShares, strategyShares) - - newMagnitudeBigInt := big.NewInt(0) - if alloc.PendingDiff.Cmp(big.NewInt(0)) != 0 { - newMagnitudeBigInt = big.NewInt(0).Add(alloc.CurrentMagnitude, alloc.PendingDiff) - } - - newShares, newSharesPercentage := getSharesFromMagnitude( - strategyShares, - newMagnitudeBigInt.Uint64(), - totalMagnitude, - ) // Check if the operator set is not registered and add it to the unregistered list // Then skip the rest of the loop if _, ok := registeredOperatorSetsMap[getUniqueKey(alloc.AvsAddress, alloc.OperatorSetId)]; !ok { + currentShares, currentSharesPercentage := getSharesFromMagnitude( + strategyShares, + alloc.CurrentMagnitude.Uint64(), + totalMagnitude, + ) + + // If the operator set is not registered and has no shares, skip it + // This comes as valid scenario since we iterate first over + // strategy addresses and then over allocations. + // This can be fixed by first going over allocations and then over strategy addresses + // We will fix this in a subsequent PR and improve (TODO: shrimalmadhur) + if currentShares == nil || currentShares.Cmp(big.NewInt(0)) == 0 { + continue + } + dergisteredOpsets = append(dergisteredOpsets, DeregisteredOperatorSet{ StrategyAddress: gethcommon.HexToAddress(strategy), AVSAddress: alloc.AvsAddress, @@ -197,6 +199,20 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { continue } + currentShares := slashableSharesMap[gethcommon.HexToAddress(strategy)][getUniqueKey(alloc.AvsAddress, alloc.OperatorSetId)] + currentSharesPercentage := getSharePercentage(currentShares, strategyShares) + + newMagnitudeBigInt := big.NewInt(0) + if alloc.PendingDiff.Cmp(big.NewInt(0)) != 0 { + newMagnitudeBigInt = big.NewInt(0).Add(alloc.CurrentMagnitude, alloc.PendingDiff) + } + + newShares, newSharesPercentage := getSharesFromMagnitude( + strategyShares, + newMagnitudeBigInt.Uint64(), + totalMagnitude, + ) + // Add the operator set to the registered list slashableMagnitudeHolders = append(slashableMagnitudeHolders, SlashableMagnitudesHolder{ StrategyAddress: gethcommon.HexToAddress(strategy), diff --git a/pkg/operator/register_operator_sets.go b/pkg/operator/register_operator_sets.go index 07b4e573..3589b697 100644 --- a/pkg/operator/register_operator_sets.go +++ b/pkg/operator/register_operator_sets.go @@ -78,12 +78,13 @@ func registerOperatorSetsAction(cCtx *cli.Context, p utils.Prompter) error { receipt, err := eLWriter.RegisterForOperatorSets( ctx, elcontracts.RegistrationRequest{ - AVSAddress: config.avsAddress, - OperatorSetIds: config.operatorSetIds, - WaitForReceipt: true, + OperatorAddress: config.operatorAddress, + AVSAddress: config.avsAddress, + OperatorSetIds: config.operatorSetIds, + WaitForReceipt: true, }) if err != nil { - return eigenSdkUtils.WrapError("failed to deregister from operator sets", err) + return eigenSdkUtils.WrapError("failed to register for operator sets", err) } common.PrintTransactionInfo(receipt.TxHash.String(), config.chainID) } else { From c4e36579a5c006bf8129ea91442958843194b98d Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Wed, 18 Dec 2024 10:43:34 -0800 Subject: [PATCH 27/34] fix: remove 0 total strategy --- pkg/operator/allocations/show.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/operator/allocations/show.go b/pkg/operator/allocations/show.go index 55b1235f..3795fd92 100644 --- a/pkg/operator/allocations/show.go +++ b/pkg/operator/allocations/show.go @@ -166,7 +166,7 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { dergisteredOpsets := make(DeregsiteredOperatorSets, 0) for strategy, allocations := range allAllocations { logger.Debugf("Strategy: %s, Allocations: %v", strategy, allocations) - strategyShares := operatorDelegatedSharesMap[strategy] + totalStrategyShares := operatorDelegatedSharesMap[strategy] totalMagnitude := totalMagnitudeMap[strategy] for _, alloc := range allocations { @@ -174,7 +174,7 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { // Then skip the rest of the loop if _, ok := registeredOperatorSetsMap[getUniqueKey(alloc.AvsAddress, alloc.OperatorSetId)]; !ok { currentShares, currentSharesPercentage := getSharesFromMagnitude( - strategyShares, + totalStrategyShares, alloc.CurrentMagnitude.Uint64(), totalMagnitude, ) @@ -199,8 +199,12 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { continue } + // If the total shares in that strategy are zero, skip the operator set + if totalStrategyShares == nil || totalStrategyShares.Cmp(big.NewInt(0)) == 0 { + continue + } currentShares := slashableSharesMap[gethcommon.HexToAddress(strategy)][getUniqueKey(alloc.AvsAddress, alloc.OperatorSetId)] - currentSharesPercentage := getSharePercentage(currentShares, strategyShares) + currentSharesPercentage := getSharePercentage(currentShares, totalStrategyShares) newMagnitudeBigInt := big.NewInt(0) if alloc.PendingDiff.Cmp(big.NewInt(0)) != 0 { @@ -208,7 +212,7 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { } newShares, newSharesPercentage := getSharesFromMagnitude( - strategyShares, + totalStrategyShares, newMagnitudeBigInt.Uint64(), totalMagnitude, ) From dcd4cecbd3ad9ea697ca50563d6728bb1d72f8dc Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Wed, 18 Dec 2024 15:51:31 -0800 Subject: [PATCH 28/34] chore: refactor and add tests (#273) --- go.mod | 30 ++++ go.sum | 42 +++++- pkg/operator/allocations/show.go | 106 ++++++++------ pkg/operator/allocations/show_test.go | 194 ++++++++++++++++++++++++++ 4 files changed, 329 insertions(+), 43 deletions(-) create mode 100644 pkg/operator/allocations/show_test.go diff --git a/go.mod b/go.mod index f3eb014e..77690e53 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,10 @@ require ( ) require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/attestantio/go-eth2-client v0.19.9 // indirect github.com/aws/aws-sdk-go-v2 v1.26.1 // indirect @@ -53,21 +56,32 @@ require ( github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect github.com/buger/jsonparser v1.1.1 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/consensys/bavard v0.1.13 // indirect + github.com/containerd/containerd v1.7.12 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/distribution/reference v0.5.0 // indirect + github.com/docker/docker v25.0.6+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/ethereum/c-kzg-4844 v1.0.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/ferranbt/fastssz v0.1.3 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/goccy/go-yaml v1.9.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect @@ -77,6 +91,8 @@ require ( github.com/klauspost/compress v1.17.1 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/lmittmann/tint v1.0.4 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -85,8 +101,16 @@ require ( github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/client_golang v1.19.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.48.0 // indirect @@ -97,11 +121,17 @@ require ( github.com/rs/zerolog v1.29.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect + github.com/shirou/gopsutil/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/supranational/blst v0.3.11 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect + github.com/testcontainers/testcontainers-go v0.30.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect diff --git a/go.sum b/go.sum index 31a66dd5..daf197ad 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= @@ -119,8 +121,9 @@ github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLR github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= -github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -170,6 +173,7 @@ github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -211,6 +215,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -221,6 +227,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -250,6 +258,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g= github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= @@ -367,16 +377,23 @@ github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11 github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= @@ -403,6 +420,7 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= @@ -411,10 +429,18 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= @@ -433,12 +459,14 @@ golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -450,6 +478,7 @@ golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= @@ -468,14 +497,17 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -483,6 +515,7 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -501,7 +534,9 @@ golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -510,6 +545,9 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= @@ -542,5 +580,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/pkg/operator/allocations/show.go b/pkg/operator/allocations/show.go index 3795fd92..11d6d5b1 100644 --- a/pkg/operator/allocations/show.go +++ b/pkg/operator/allocations/show.go @@ -162,6 +162,68 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { 7. Using all of the above, calculate SlashableMagnitudeHolders object for displaying the allocation state of the operator */ + slashableMagnitudeHolders, dergisteredOpsets, err := prepareAllocationsData( + allAllocations, + registeredOperatorSetsMap, + operatorDelegatedSharesMap, + totalMagnitudeMap, + slashableSharesMap, + logger, + ) + if err != nil { + return err + } + + for key, val := range operatorDelegatedSharesMap { + fmt.Printf("Strategy Address: %s, Shares %s\n", key, common.FormatNumberWithUnderscores(val.String())) + } + + currBlockNumber, err := ethClient.BlockNumber(ctx) + if err != nil { + return eigenSdkUtils.WrapError("failed to get current block number", err) + } + delay, err := elReader.GetAllocationDelay(ctx, config.operatorAddress) + if err != nil { + return err + } + fmt.Println() + fmt.Printf("Current allocation delay: %d blocks\n", delay) + fmt.Println() + fmt.Printf( + "------------------ Allocation State for %s (Block: %d) ---------------------\n", + config.operatorAddress.String(), + currBlockNumber, + ) + if config.outputType == string(common.OutputType_Json) { + slashableMagnitudeHolders.PrintJSON() + } else { + slashableMagnitudeHolders.PrintPretty() + } + + if len(dergisteredOpsets) > 0 { + fmt.Println() + fmt.Printf( + "NOTE: You have %d deregistered operator sets which have nonzero allocations as listed below. Please deallocate to use those funds.\n", + len(dergisteredOpsets), + ) + if config.outputType == string(common.OutputType_Json) { + dergisteredOpsets.PrintJSON() + } else { + dergisteredOpsets.PrintPretty() + } + } + + return nil +} + +func prepareAllocationsData( + allAllocations map[string][]elcontracts.AllocationInfo, + registeredOperatorSetsMap map[string]allocationmanager.OperatorSet, + operatorDelegatedSharesMap map[string]*big.Int, + totalMagnitudeMap map[string]uint64, + slashableSharesMap map[gethcommon.Address]map[string]*big.Int, + logger logging.Logger, +) (SlashableMagnitudeHolders, DeregsiteredOperatorSets, error) { slashableMagnitudeHolders := make(SlashableMagnitudeHolders, 0) dergisteredOpsets := make(DeregsiteredOperatorSets, 0) for strategy, allocations := range allAllocations { @@ -207,7 +269,7 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { currentSharesPercentage := getSharePercentage(currentShares, totalStrategyShares) newMagnitudeBigInt := big.NewInt(0) - if alloc.PendingDiff.Cmp(big.NewInt(0)) != 0 { + if alloc.PendingDiff != nil && alloc.PendingDiff.Cmp(big.NewInt(0)) != 0 { newMagnitudeBigInt = big.NewInt(0).Add(alloc.CurrentMagnitude, alloc.PendingDiff) } @@ -232,47 +294,7 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { }) } } - - for key, val := range operatorDelegatedSharesMap { - fmt.Printf("Strategy Address: %s, Shares %s\n", key, common.FormatNumberWithUnderscores(val.String())) - } - - currBlockNumber, err := ethClient.BlockNumber(ctx) - if err != nil { - return eigenSdkUtils.WrapError("failed to get current block number", err) - } - delay, err := elReader.GetAllocationDelay(ctx, config.operatorAddress) - if err != nil { - return err - } - fmt.Println() - fmt.Printf("Current allocation delay: %d blocks\n", delay) - fmt.Println() - fmt.Printf( - "------------------ Allocation State for %s (Block: %d) ---------------------\n", - config.operatorAddress.String(), - currBlockNumber, - ) - if config.outputType == string(common.OutputType_Json) { - slashableMagnitudeHolders.PrintJSON() - } else { - slashableMagnitudeHolders.PrintPretty() - } - - if len(dergisteredOpsets) > 0 { - fmt.Println() - fmt.Printf( - "NOTE: You have %d deregistered operator sets which have nonzero allocations as listed below. Please deallocate to use those funds.\n", - len(dergisteredOpsets), - ) - if config.outputType == string(common.OutputType_Json) { - dergisteredOpsets.PrintJSON() - } else { - dergisteredOpsets.PrintPretty() - } - } - - return nil + return slashableMagnitudeHolders, dergisteredOpsets, nil } func getSharePercentage(shares *big.Int, totalShares *big.Int) *big.Float { diff --git a/pkg/operator/allocations/show_test.go b/pkg/operator/allocations/show_test.go new file mode 100644 index 00000000..698d07cf --- /dev/null +++ b/pkg/operator/allocations/show_test.go @@ -0,0 +1,194 @@ +package allocations + +import ( + "math/big" + "testing" + + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + allocationmanager "github.com/Layr-Labs/eigensdk-go/contracts/bindings/AllocationManager" + "github.com/Layr-Labs/eigensdk-go/testutils" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" +) + +func TestPrepareAllocationsData(t *testing.T) { + avsAddress := gethcommon.HexToAddress("0xa1") + + tests := []struct { + name string + allAllocations map[string][]elcontracts.AllocationInfo + registeredOperatorSets map[string]allocationmanager.OperatorSet + operatorDelegatedShares map[string]*big.Int + totalMagnitude map[string]uint64 + slashableShares map[gethcommon.Address]map[string]*big.Int + expectedSlashable SlashableMagnitudeHolders + expectedDeregistered DeregsiteredOperatorSets + expectedErr error + }{ + { + name: "Happy path - registered operator set with pending changes", + allAllocations: map[string][]elcontracts.AllocationInfo{ + "0x1": { + { + CurrentMagnitude: big.NewInt(100), + PendingDiff: big.NewInt(50), + EffectBlock: 1000, + OperatorSetId: 1, + AvsAddress: avsAddress, + }, + }, + }, + registeredOperatorSets: map[string]allocationmanager.OperatorSet{ + getUniqueKey(avsAddress, 1): { + Avs: gethcommon.HexToAddress("0xa1"), + Id: 1, + }, + }, + operatorDelegatedShares: map[string]*big.Int{ + "0x1": big.NewInt(1000), + }, + totalMagnitude: map[string]uint64{ + "0x1": 200, + }, + slashableShares: map[gethcommon.Address]map[string]*big.Int{ + gethcommon.HexToAddress("0x1"): { + getUniqueKey(avsAddress, 1): big.NewInt(500), + }, + }, + expectedSlashable: SlashableMagnitudeHolders{ + { + StrategyAddress: gethcommon.HexToAddress("0x1"), + AVSAddress: gethcommon.HexToAddress("0xa1"), + OperatorSetId: 1, + SlashableMagnitude: 100, + NewMagnitude: 150, + UpdateBlock: 1000, + Shares: big.NewInt(500), + SharesPercentage: "50", + NewAllocationShares: big.NewInt(750), + UpcomingSharesPercentage: "75", + }, + }, + expectedDeregistered: DeregsiteredOperatorSets{}, + expectedErr: nil, + }, + { + name: "Deregistered operator set", + allAllocations: map[string][]elcontracts.AllocationInfo{ + "0x1": { + { + CurrentMagnitude: big.NewInt(100), + PendingDiff: big.NewInt(0), + EffectBlock: 1000, + OperatorSetId: 1, + AvsAddress: gethcommon.HexToAddress("0xa1"), + }, + }, + }, + registeredOperatorSets: map[string]allocationmanager.OperatorSet{}, // Empty map means operator set is not registered + operatorDelegatedShares: map[string]*big.Int{ + "0x1": big.NewInt(1000), + }, + totalMagnitude: map[string]uint64{ + "0x1": 200, + }, + slashableShares: map[gethcommon.Address]map[string]*big.Int{ + gethcommon.HexToAddress("0x1"): { + getUniqueKey(avsAddress, 1): big.NewInt(500), + }, + }, + expectedSlashable: SlashableMagnitudeHolders{}, + expectedDeregistered: DeregsiteredOperatorSets{ + { + StrategyAddress: gethcommon.HexToAddress("0x1"), + AVSAddress: gethcommon.HexToAddress("0xa1"), + OperatorSetId: 1, + SlashableMagnitude: 100, + Shares: big.NewInt(500), + SharesPercentage: "50", + }, + }, + expectedErr: nil, + }, + { + name: "Zero total shares - should skip", + allAllocations: map[string][]elcontracts.AllocationInfo{ + "0x1": { + { + CurrentMagnitude: big.NewInt(100), + PendingDiff: big.NewInt(50), + EffectBlock: 1000, + OperatorSetId: 1, + AvsAddress: gethcommon.HexToAddress("0xa1"), + }, + }, + }, + registeredOperatorSets: map[string]allocationmanager.OperatorSet{ + getUniqueKey(avsAddress, 1): { + Avs: gethcommon.HexToAddress("0xa1"), + Id: 1, + }, + }, + operatorDelegatedShares: map[string]*big.Int{ + "0x1": big.NewInt(0), // Zero total shares + }, + totalMagnitude: map[string]uint64{ + "0x1": 200, + }, + slashableShares: map[gethcommon.Address]map[string]*big.Int{ + gethcommon.HexToAddress("0x1"): { + getUniqueKey(avsAddress, 1): big.NewInt(0), + }, + }, + expectedSlashable: SlashableMagnitudeHolders{}, + expectedDeregistered: DeregsiteredOperatorSets{}, + expectedErr: nil, + }, + { + name: "Empty allocations", + allAllocations: map[string][]elcontracts.AllocationInfo{}, + registeredOperatorSets: map[string]allocationmanager.OperatorSet{ + getUniqueKey(avsAddress, 1): { + Avs: gethcommon.HexToAddress("0xa1"), + Id: 1, + }, + }, + operatorDelegatedShares: map[string]*big.Int{ + "0x1": big.NewInt(1000), + }, + totalMagnitude: map[string]uint64{ + "0x1": 200, + }, + slashableShares: map[gethcommon.Address]map[string]*big.Int{ + gethcommon.HexToAddress("0x1"): { + getUniqueKey(avsAddress, 1): big.NewInt(500), + }, + }, + expectedSlashable: SlashableMagnitudeHolders{}, + expectedDeregistered: DeregsiteredOperatorSets{}, + expectedErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + slashable, deregistered, err := prepareAllocationsData( + tt.allAllocations, + tt.registeredOperatorSets, + tt.operatorDelegatedShares, + tt.totalMagnitude, + tt.slashableShares, + testutils.GetTestLogger(), + ) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.Equal(t, tt.expectedErr, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedSlashable, slashable) + assert.Equal(t, tt.expectedDeregistered, deregistered) + } + }) + } +} From 1d6138843fcbd0751466a9cb5a68dc52ec7cd57c Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Wed, 18 Dec 2024 16:44:16 -0800 Subject: [PATCH 29/34] Updating output code path to correctly detect empty string input for caller-address (#274) Co-authored-by: Brandon Chatham --- pkg/user/admin/accept.go | 13 +++---------- pkg/user/admin/add_pending.go | 12 +++--------- pkg/user/admin/flags.go | 7 ------- pkg/user/admin/remove.go | 12 +++--------- pkg/user/admin/remove_pending.go | 12 +++--------- pkg/user/appointee/flags.go | 7 ------- pkg/user/appointee/remove.go | 13 +++---------- pkg/user/appointee/set.go | 12 +++--------- pkg/user/flags.go | 13 +++++++++++++ pkg/user/helper.go | 26 ++++++++++++++++++++++++++ 10 files changed, 57 insertions(+), 70 deletions(-) create mode 100644 pkg/user/flags.go create mode 100644 pkg/user/helper.go diff --git a/pkg/user/admin/accept.go b/pkg/user/admin/accept.go index d9551997..045f4d67 100644 --- a/pkg/user/admin/accept.go +++ b/pkg/user/admin/accept.go @@ -8,6 +8,7 @@ import ( "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/user" "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" @@ -144,7 +145,7 @@ func readAndValidateAcceptAdminConfig( logger logging.Logger, ) (*acceptAdminConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) - callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddressFlag.Name)) + callerAddress := user.PopulateCallerAddress(cliContext, logger, accountAddress) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -170,14 +171,6 @@ func readAndValidateAcceptAdminConfig( return nil, err } } - if common.IsEmptyString(callerAddress.String()) { - logger.Infof( - "Caller address not provided. Using account address (%s) as caller address", - accountAddress, - ) - callerAddress = accountAddress - } - logger.Debugf( "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", environment, @@ -205,7 +198,7 @@ func acceptFlags() []cli.Flag { cmdFlags := []cli.Flag{ &flags.VerboseFlag, &AccountAddressFlag, - &CallerAddressFlag, + &user.CallerAddressFlag, &PermissionControllerAddressFlag, &flags.OutputTypeFlag, &flags.OutputFileFlag, diff --git a/pkg/user/admin/add_pending.go b/pkg/user/admin/add_pending.go index e4f23ba2..4784a264 100644 --- a/pkg/user/admin/add_pending.go +++ b/pkg/user/admin/add_pending.go @@ -8,6 +8,7 @@ import ( "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/user" "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" @@ -147,7 +148,7 @@ func readAndValidateAddPendingAdminConfig( ) (*addPendingAdminConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) adminAddress := gethcommon.HexToAddress(cliContext.String(AdminAddressFlag.Name)) - callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddressFlag.Name)) + callerAddress := user.PopulateCallerAddress(cliContext, logger, accountAddress) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -157,13 +158,6 @@ func readAndValidateAddPendingAdminConfig( if environment == "" { environment = common.GetEnvFromNetwork(network) } - if common.IsEmptyString(callerAddress.String()) { - logger.Infof( - "Caller address not provided. Using account address (%s) as caller address", - accountAddress, - ) - callerAddress = accountAddress - } signerConfig, err := common.GetSignerConfig(cliContext, logger) if err != nil { // We don't want to throw error since people can still use it to generate the claim @@ -232,7 +226,7 @@ func addPendingFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &AdminAddressFlag, - &CallerAddressFlag, + &user.CallerAddressFlag, &PermissionControllerAddressFlag, &flags.OutputTypeFlag, &flags.OutputFileFlag, diff --git a/pkg/user/admin/flags.go b/pkg/user/admin/flags.go index a5d7ce82..d7fb6a15 100644 --- a/pkg/user/admin/flags.go +++ b/pkg/user/admin/flags.go @@ -15,13 +15,6 @@ var ( Usage: "user admin ... --admin-address \"0x...\"", EnvVars: []string{"ADMIN_ADDRESS"}, } - CallerAddressFlag = cli.StringFlag{ - Name: "caller-address", - Aliases: []string{"ca"}, - Usage: "This is the address of the caller who is calling the contract function. \n" + - "If it is not provided, the account address will be used as the caller address", - EnvVars: []string{"CALLER_ADDRESS"}, - } PendingAdminAddressFlag = cli.StringFlag{ Name: "pending-admin-address", Aliases: []string{"paa"}, diff --git a/pkg/user/admin/remove.go b/pkg/user/admin/remove.go index a27c6d3a..18e13210 100644 --- a/pkg/user/admin/remove.go +++ b/pkg/user/admin/remove.go @@ -8,6 +8,7 @@ import ( "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/user" "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" @@ -145,7 +146,7 @@ func readAndValidateRemoveAdminConfig( ) (*removeAdminConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) adminAddress := gethcommon.HexToAddress(cliContext.String(AdminAddressFlag.Name)) - callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddressFlag.Name)) + callerAddress := user.PopulateCallerAddress(cliContext, logger, accountAddress) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -155,13 +156,6 @@ func readAndValidateRemoveAdminConfig( if environment == "" { environment = common.GetEnvFromNetwork(network) } - if common.IsEmptyString(callerAddress.String()) { - logger.Infof( - "Caller address not provided. Using account address (%s) as caller address", - accountAddress, - ) - callerAddress = accountAddress - } signerConfig, err := common.GetSignerConfig(cliContext, logger) if err != nil { // We don't want to throw error since people can still use it to generate the claim @@ -230,7 +224,7 @@ func removeFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &AdminAddressFlag, - &CallerAddressFlag, + &user.CallerAddressFlag, &PermissionControllerAddressFlag, &flags.OutputTypeFlag, &flags.OutputFileFlag, diff --git a/pkg/user/admin/remove_pending.go b/pkg/user/admin/remove_pending.go index 256ba389..8110ad20 100644 --- a/pkg/user/admin/remove_pending.go +++ b/pkg/user/admin/remove_pending.go @@ -8,6 +8,7 @@ import ( "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/user" "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" @@ -145,7 +146,7 @@ func readAndValidateRemovePendingAdminConfig( ) (*removePendingAdminConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) adminAddress := gethcommon.HexToAddress(cliContext.String(AdminAddressFlag.Name)) - callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddressFlag.Name)) + callerAddress := user.PopulateCallerAddress(cliContext, logger, accountAddress) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -155,13 +156,6 @@ func readAndValidateRemovePendingAdminConfig( if environment == "" { environment = common.GetEnvFromNetwork(network) } - if common.IsEmptyString(callerAddress.String()) { - logger.Infof( - "Caller address not provided. Using account address (%s) as caller address", - accountAddress, - ) - callerAddress = accountAddress - } signerConfig, err := common.GetSignerConfig(cliContext, logger) if err != nil { // We don't want to throw error since people can still use it to generate the claim @@ -230,7 +224,7 @@ func removePendingAdminFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &AdminAddressFlag, - &CallerAddressFlag, + &user.CallerAddressFlag, &PermissionControllerAddressFlag, &flags.BroadcastFlag, &flags.OutputTypeFlag, diff --git a/pkg/user/appointee/flags.go b/pkg/user/appointee/flags.go index 9c1c7eae..44bacc3b 100644 --- a/pkg/user/appointee/flags.go +++ b/pkg/user/appointee/flags.go @@ -15,13 +15,6 @@ var ( Usage: "The Ethereum address of the user. Example: --appointee-address \"0x...\"", EnvVars: []string{"APPOINTEE_ADDRESS"}, } - CallerAddressFlag = cli.StringFlag{ - Name: "caller-address", - Aliases: []string{"ca"}, - Usage: "This is the address of the caller who is calling the contract function. \n" + - "If it is not provided, the account address will be used as the caller address", - EnvVars: []string{"CALLER_ADDRESS"}, - } SelectorFlag = cli.StringFlag{ Name: "selector", Aliases: []string{"s"}, diff --git a/pkg/user/appointee/remove.go b/pkg/user/appointee/remove.go index d3c62509..276ccadf 100644 --- a/pkg/user/appointee/remove.go +++ b/pkg/user/appointee/remove.go @@ -8,6 +8,7 @@ import ( "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/user" "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" @@ -170,7 +171,7 @@ func generateRemoveAppointeePermissionWriter( func readAndValidateRemoveConfig(cliContext *cli.Context, logger logging.Logger) (*removeConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) appointeeAddress := gethcommon.HexToAddress(cliContext.String(AppointeeAddressFlag.Name)) - callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddressFlag.Name)) + callerAddress := user.PopulateCallerAddress(cliContext, logger, accountAddress) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -204,14 +205,6 @@ func readAndValidateRemoveConfig(cliContext *cli.Context, logger logging.Logger) return nil, err } } - if common.IsEmptyString(callerAddress.String()) { - logger.Infof( - "Caller address not provided. Using account address (%s) as caller address", - accountAddress, - ) - callerAddress = accountAddress - } - logger.Debugf( "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", environment, @@ -243,7 +236,7 @@ func removeCommandFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &AppointeeAddressFlag, - &CallerAddressFlag, + &user.CallerAddressFlag, &TargetAddressFlag, &SelectorFlag, &PermissionControllerAddressFlag, diff --git a/pkg/user/appointee/set.go b/pkg/user/appointee/set.go index 1d540a73..9227d9b3 100644 --- a/pkg/user/appointee/set.go +++ b/pkg/user/appointee/set.go @@ -8,6 +8,7 @@ import ( "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/user" "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/logging" @@ -173,7 +174,7 @@ func generateSetAppointeePermissionWriter( func readAndValidateSetConfig(cliContext *cli.Context, logger logging.Logger) (*setConfig, error) { accountAddress := gethcommon.HexToAddress(cliContext.String(AccountAddressFlag.Name)) appointeeAddress := gethcommon.HexToAddress(cliContext.String(AppointeeAddressFlag.Name)) - callerAddress := gethcommon.HexToAddress(cliContext.String(CallerAddressFlag.Name)) + callerAddress := user.PopulateCallerAddress(cliContext, logger, accountAddress) ethRpcUrl := cliContext.String(flags.ETHRpcUrlFlag.Name) network := cliContext.String(flags.NetworkFlag.Name) environment := cliContext.String(flags.EnvironmentFlag.Name) @@ -196,13 +197,6 @@ func readAndValidateSetConfig(cliContext *cli.Context, logger logging.Logger) (* if environment == "" { environment = common.GetEnvFromNetwork(network) } - if common.IsEmptyString(callerAddress.String()) { - logger.Infof( - "Caller address not provided. Using account address (%s) as caller address", - accountAddress, - ) - callerAddress = accountAddress - } chainID := utils.NetworkNameToChainId(network) cliContext.App.Metadata["network"] = chainID.String() @@ -246,7 +240,7 @@ func setCommandFlags() []cli.Flag { &flags.VerboseFlag, &AccountAddressFlag, &AppointeeAddressFlag, - &CallerAddressFlag, + &user.CallerAddressFlag, &TargetAddressFlag, &SelectorFlag, &PermissionControllerAddressFlag, diff --git a/pkg/user/flags.go b/pkg/user/flags.go new file mode 100644 index 00000000..a59875bb --- /dev/null +++ b/pkg/user/flags.go @@ -0,0 +1,13 @@ +package user + +import "github.com/urfave/cli/v2" + +var ( + CallerAddressFlag = cli.StringFlag{ + Name: "caller-address", + Aliases: []string{"ca"}, + Usage: "This is the address of the caller who is calling the contract function. \n" + + "If it is not provided, the account address will be used as the caller address", + EnvVars: []string{"CALLER_ADDRESS"}, + } +) diff --git a/pkg/user/helper.go b/pkg/user/helper.go new file mode 100644 index 00000000..7d24bd6d --- /dev/null +++ b/pkg/user/helper.go @@ -0,0 +1,26 @@ +package user + +import ( + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/urfave/cli/v2" +) + +func PopulateCallerAddress( + cliContext *cli.Context, + logger logging.Logger, + accountAddress gethcommon.Address, +) gethcommon.Address { + // TODO: these are copied across both callers of this method. Will clean this up in the CLI refactor of flags. + callerAddress := cliContext.String(CallerAddressFlag.Name) + if common.IsEmptyString(callerAddress) { + logger.Infof( + "Caller address not provided. Using account address (%s) as caller address", + accountAddress, + ) + + return accountAddress + } + return gethcommon.HexToAddress(callerAddress) +} From f89eea987103f91ac7726c81f66af4ab4f894dfb Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Wed, 18 Dec 2024 19:15:45 -0800 Subject: [PATCH 30/34] feat: support csv out for show command (#275) --- pkg/operator/allocations/show.go | 20 +++++--- pkg/operator/allocations/types.go | 80 +++++++++++++++++++++++++++---- 2 files changed, 84 insertions(+), 16 deletions(-) diff --git a/pkg/operator/allocations/show.go b/pkg/operator/allocations/show.go index 11d6d5b1..15184bca 100644 --- a/pkg/operator/allocations/show.go +++ b/pkg/operator/allocations/show.go @@ -2,9 +2,11 @@ package allocations import ( "context" + "errors" "fmt" "math/big" "sort" + "strings" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" @@ -22,11 +24,6 @@ import ( "github.com/urfave/cli/v2" ) -var ( - // PrecisionFactor comes from the allocation manager contract - PrecisionFactor = big.NewInt(1e18) -) - func ShowCmd(p utils.Prompter) *cli.Command { showCmd := &cli.Command{ Name: "show", @@ -197,7 +194,18 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { if config.outputType == string(common.OutputType_Json) { slashableMagnitudeHolders.PrintJSON() } else { - slashableMagnitudeHolders.PrintPretty() + if !common.IsEmptyString(config.output) { + if !strings.HasSuffix(config.output, ".csv") { + return errors.New("output file must be a .csv file") + } + err = slashableMagnitudeHolders.WriteToCSV(config.output) + if err != nil { + return err + } + logger.Infof("Allocation state written to file: %s", config.output) + } else { + slashableMagnitudeHolders.PrintPretty() + } } if len(dergisteredOpsets) > 0 { diff --git a/pkg/operator/allocations/types.go b/pkg/operator/allocations/types.go index e6bbd1fb..b5a9104f 100644 --- a/pkg/operator/allocations/types.go +++ b/pkg/operator/allocations/types.go @@ -1,9 +1,12 @@ package allocations import ( + "encoding/csv" "encoding/json" "fmt" "math/big" + "os" + "reflect" "strings" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" @@ -139,16 +142,73 @@ type showConfig struct { type SlashableMagnitudeHolders []SlashableMagnitudesHolder type SlashableMagnitudesHolder struct { - StrategyAddress gethcommon.Address - AVSAddress gethcommon.Address - OperatorSetId uint32 - SlashableMagnitude uint64 - NewMagnitude uint64 - NewAllocationShares *big.Int - UpdateBlock uint32 - Shares *big.Int - SharesPercentage string - UpcomingSharesPercentage string + StrategyAddress gethcommon.Address `csv:"strategy_address"` + AVSAddress gethcommon.Address `csv:"avs_address"` + OperatorSetId uint32 `csv:"operator_set_id"` + SlashableMagnitude uint64 `csv:"-"` + NewMagnitude uint64 `csv:"-"` + Shares *big.Int `csv:"shares"` + SharesPercentage string `csv:"shares_percentage"` + NewAllocationShares *big.Int `csv:"new_allocation_shares"` + UpcomingSharesPercentage string `csv:"upcoming_shares_percentage"` + UpdateBlock uint32 `csv:"update_block"` +} + +func (s SlashableMagnitudeHolders) WriteToCSV(filePath string) error { + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("failed to create file: %v", err) + } + defer file.Close() + + writer := csv.NewWriter(file) + defer writer.Flush() + + // Get fields and their CSV names, excluding skipped fields + var headers []string + var fieldIndices []int + val := reflect.ValueOf(SlashableMagnitudesHolder{}) + typ := val.Type() + + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + csvTag := field.Tag.Get("csv") + + // Skip if tag is "-" + if csvTag == "-" { + continue + } + + // Use tag value if present, otherwise use field name + if csvTag != "" { + headers = append(headers, csvTag) + } else { + headers = append(headers, field.Name) + } + fieldIndices = append(fieldIndices, i) + } + + // Write headers + if err := writer.Write(headers); err != nil { + return fmt.Errorf("failed to write headers: %v", err) + } + + // Write data rows + for _, eachRow := range s { + val := reflect.ValueOf(eachRow) + row := make([]string, len(fieldIndices)) + // Only include non-skipped fields + for i, fieldIndex := range fieldIndices { + field := val.Field(fieldIndex) + row[i] = fmt.Sprintf("%v", field.Interface()) + } + + if err := writer.Write(row); err != nil { + return fmt.Errorf("failed to write row: %v", err) + } + } + + return nil } func (s SlashableMagnitudeHolders) PrintPretty() { From 026cfdbef0e437df6279cf4c6b3b2d36e5789734 Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Thu, 19 Dec 2024 11:49:58 -0800 Subject: [PATCH 31/34] Adding PermissionController address for Holesky (#279) Co-authored-by: Brandon Chatham --- pkg/internal/common/helper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/internal/common/helper.go b/pkg/internal/common/helper.go index 54f70ca9..13098a4b 100644 --- a/pkg/internal/common/helper.go +++ b/pkg/internal/common/helper.go @@ -61,7 +61,7 @@ var ChainMetadataMap = map[int64]types.ChainMetadata{ ELDelegationManagerAddress: "0xA44151489861Fe9e3055d95adC98FbD462B948e7", ELAVSDirectoryAddress: "0x055733000064333CaDDbC92763c58BF0192fFeBf", ELRewardsCoordinatorAddress: "0xAcc1fb458a1317E886dB376Fc8141540537E68fE", - ELPermissionManagerAddress: "", + ELPermissionManagerAddress: "0x598cb226B591155F767dA17AfE7A2241a68C5C10", WebAppUrl: "https://holesky.eigenlayer.xyz/operator", ProofStoreBaseURL: "https://eigenlabs-rewards-testnet-holesky.s3.amazonaws.com", }, From c2daa7a3d5b7f1116a8d79c1a08c5808bfc7d4bc Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Fri, 20 Dec 2024 12:33:10 -0800 Subject: [PATCH 32/34] Rename permission manager (#280) Co-authored-by: Brandon Chatham --- pkg/internal/common/helper.go | 46 ++++---- pkg/types/chain_metadata.go | 14 +-- pkg/user/admin/accept.go | 34 +++--- pkg/user/admin/add_pending.go | 36 +++--- pkg/user/admin/is_admin.go | 26 ++--- pkg/user/admin/is_pending.go | 26 ++--- pkg/user/admin/list.go | 24 ++-- pkg/user/admin/list_pending.go | 24 ++-- pkg/user/admin/remove.go | 36 +++--- pkg/user/admin/remove_pending.go | 36 +++--- pkg/user/admin/types.go | 146 ++++++++++++------------- pkg/user/appointee/can_call.go | 30 ++--- pkg/user/appointee/list.go | 28 ++--- pkg/user/appointee/list_permissions.go | 26 ++--- pkg/user/appointee/remove.go | 40 +++---- pkg/user/appointee/set.go | 40 +++---- pkg/user/appointee/types.go | 104 +++++++++--------- 17 files changed, 358 insertions(+), 358 deletions(-) diff --git a/pkg/internal/common/helper.go b/pkg/internal/common/helper.go index 13098a4b..8b61a4da 100644 --- a/pkg/internal/common/helper.go +++ b/pkg/internal/common/helper.go @@ -48,31 +48,31 @@ const ( var ChainMetadataMap = map[int64]types.ChainMetadata{ MainnetChainId: { - BlockExplorerUrl: "https://etherscan.io/tx", - ELDelegationManagerAddress: "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", - ELAVSDirectoryAddress: "0x135dda560e946695d6f155dacafc6f1f25c1f5af", - ELRewardsCoordinatorAddress: "0x7750d328b314EfFa365A0402CcfD489B80B0adda", - ELPermissionManagerAddress: "", - WebAppUrl: "https://app.eigenlayer.xyz/operator", - ProofStoreBaseURL: "https://eigenlabs-rewards-mainnet-ethereum.s3.amazonaws.com", + BlockExplorerUrl: "https://etherscan.io/tx", + ELDelegationManagerAddress: "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", + ELAVSDirectoryAddress: "0x135dda560e946695d6f155dacafc6f1f25c1f5af", + ELRewardsCoordinatorAddress: "0x7750d328b314EfFa365A0402CcfD489B80B0adda", + ELPermissionControllerAddress: "", + WebAppUrl: "https://app.eigenlayer.xyz/operator", + ProofStoreBaseURL: "https://eigenlabs-rewards-mainnet-ethereum.s3.amazonaws.com", }, HoleskyChainId: { - BlockExplorerUrl: "https://holesky.etherscan.io/tx", - ELDelegationManagerAddress: "0xA44151489861Fe9e3055d95adC98FbD462B948e7", - ELAVSDirectoryAddress: "0x055733000064333CaDDbC92763c58BF0192fFeBf", - ELRewardsCoordinatorAddress: "0xAcc1fb458a1317E886dB376Fc8141540537E68fE", - ELPermissionManagerAddress: "0x598cb226B591155F767dA17AfE7A2241a68C5C10", - WebAppUrl: "https://holesky.eigenlayer.xyz/operator", - ProofStoreBaseURL: "https://eigenlabs-rewards-testnet-holesky.s3.amazonaws.com", + BlockExplorerUrl: "https://holesky.etherscan.io/tx", + ELDelegationManagerAddress: "0xA44151489861Fe9e3055d95adC98FbD462B948e7", + ELAVSDirectoryAddress: "0x055733000064333CaDDbC92763c58BF0192fFeBf", + ELRewardsCoordinatorAddress: "0xAcc1fb458a1317E886dB376Fc8141540537E68fE", + ELPermissionControllerAddress: "0x598cb226B591155F767dA17AfE7A2241a68C5C10", + WebAppUrl: "https://holesky.eigenlayer.xyz/operator", + ProofStoreBaseURL: "https://eigenlabs-rewards-testnet-holesky.s3.amazonaws.com", }, AnvilChainId: { - BlockExplorerUrl: "", - ELDelegationManagerAddress: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", - ELAVSDirectoryAddress: "0x0165878A594ca255338adfa4d48449f69242Eb8F", - ELRewardsCoordinatorAddress: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", - ELPermissionManagerAddress: "", - WebAppUrl: "", - ProofStoreBaseURL: "", + BlockExplorerUrl: "", + ELDelegationManagerAddress: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", + ELAVSDirectoryAddress: "0x0165878A594ca255338adfa4d48449f69242Eb8F", + ELRewardsCoordinatorAddress: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", + ELPermissionControllerAddress: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c", + WebAppUrl: "", + ProofStoreBaseURL: "", }, } @@ -342,13 +342,13 @@ func GetDelegationManagerAddress(chainID *big.Int) (string, error) { } } -func GetPermissionManagerAddress(chainID *big.Int) (string, error) { +func GetPermissionControllerAddress(chainID *big.Int) (string, error) { chainIDInt := chainID.Int64() chainMetadata, ok := ChainMetadataMap[chainIDInt] if !ok { return "", fmt.Errorf("chain ID %d not supported", chainIDInt) } else { - return chainMetadata.ELDelegationManagerAddress, nil + return chainMetadata.ELPermissionControllerAddress, nil } } diff --git a/pkg/types/chain_metadata.go b/pkg/types/chain_metadata.go index f59f3538..742ef8c5 100644 --- a/pkg/types/chain_metadata.go +++ b/pkg/types/chain_metadata.go @@ -1,11 +1,11 @@ package types type ChainMetadata struct { - BlockExplorerUrl string - ELDelegationManagerAddress string - ELAVSDirectoryAddress string - ELRewardsCoordinatorAddress string - ELPermissionManagerAddress string - WebAppUrl string - ProofStoreBaseURL string + BlockExplorerUrl string + ELDelegationManagerAddress string + ELAVSDirectoryAddress string + ELRewardsCoordinatorAddress string + ELPermissionControllerAddress string + WebAppUrl string + ProofStoreBaseURL string } diff --git a/pkg/user/admin/accept.go b/pkg/user/admin/accept.go index 045f4d67..19e9f1c8 100644 --- a/pkg/user/admin/accept.go +++ b/pkg/user/admin/accept.go @@ -163,34 +163,34 @@ func readAndValidateAcceptAdminConfig( } chainID := utils.NetworkNameToChainId(network) - permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(permissionManagerAddress) { - permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(PermissionControllerAddress) { + PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } } logger.Debugf( - "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + "Env: %s, network: %s, chain ID: %s, PermissionController address: %s", environment, network, chainID, - permissionManagerAddress, + PermissionControllerAddress, ) return &acceptAdminConfig{ - Network: network, - RPCUrl: ethRpcUrl, - AccountAddress: accountAddress, - CallerAddress: callerAddress, - PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), - SignerConfig: *signerConfig, - ChainID: chainID, - Environment: environment, - OutputFile: outputFile, - OutputType: outputType, - Broadcast: broadcast, + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + CallerAddress: callerAddress, + PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + SignerConfig: *signerConfig, + ChainID: chainID, + Environment: environment, + OutputFile: outputFile, + OutputType: outputType, + Broadcast: broadcast, }, nil } @@ -225,7 +225,7 @@ func generateAcceptAdminWriter( &config.SignerConfig, ethClient, elcontracts.Config{ - PermissionsControllerAddress: config.PermissionManagerAddress, + PermissionsControllerAddress: config.PermissionControllerAddress, }, prompter, config.ChainID, diff --git a/pkg/user/admin/add_pending.go b/pkg/user/admin/add_pending.go index 4784a264..623fa9a1 100644 --- a/pkg/user/admin/add_pending.go +++ b/pkg/user/admin/add_pending.go @@ -166,36 +166,36 @@ func readAndValidateAddPendingAdminConfig( } chainID := utils.NetworkNameToChainId(network) - permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(permissionManagerAddress) { - permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(PermissionControllerAddress) { + PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } } logger.Debugf( - "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + "Env: %s, network: %s, chain ID: %s, PermissionController address: %s", environment, network, chainID, - permissionManagerAddress, + PermissionControllerAddress, ) return &addPendingAdminConfig{ - Network: network, - RPCUrl: ethRpcUrl, - AccountAddress: accountAddress, - AdminAddress: adminAddress, - CallerAddress: callerAddress, - SignerConfig: *signerConfig, - PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), - ChainID: chainID, - Environment: environment, - OutputFile: outputFile, - OutputType: outputType, - Broadcast: broadcast, + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + AdminAddress: adminAddress, + CallerAddress: callerAddress, + SignerConfig: *signerConfig, + PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + ChainID: chainID, + Environment: environment, + OutputFile: outputFile, + OutputType: outputType, + Broadcast: broadcast, }, nil } @@ -212,7 +212,7 @@ func generateAddPendingAdminWriter( &config.SignerConfig, ethClient, elcontracts.Config{ - PermissionsControllerAddress: config.PermissionManagerAddress, + PermissionsControllerAddress: config.PermissionControllerAddress, }, prompter, config.ChainID, diff --git a/pkg/user/admin/is_admin.go b/pkg/user/admin/is_admin.go index 2f2a8936..6064c898 100644 --- a/pkg/user/admin/is_admin.go +++ b/pkg/user/admin/is_admin.go @@ -73,32 +73,32 @@ func readAndValidateIsAdminConfig(cliContext *cli.Context, logger logging.Logger } chainID := utils.NetworkNameToChainId(network) - permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) var err error - if common.IsEmptyString(permissionManagerAddress) { - permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(PermissionControllerAddress) { + PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } } logger.Debugf( - "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + "Env: %s, network: %s, chain ID: %s, PermissionController address: %s", environment, network, chainID, - permissionManagerAddress, + PermissionControllerAddress, ) return &isAdminConfig{ - Network: network, - RPCUrl: ethRpcUrl, - AccountAddress: accountAddress, - AdminAddress: adminAddress, - PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), - ChainID: chainID, - Environment: environment, + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + AdminAddress: adminAddress, + PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + ChainID: chainID, + Environment: environment, }, nil } @@ -117,7 +117,7 @@ func generateIsAdminReader(logger logging.Logger, config *isAdminConfig) (IsAdmi } elReader, err := elcontracts.NewReaderFromConfig( elcontracts.Config{ - PermissionsControllerAddress: config.PermissionManagerAddress, + PermissionsControllerAddress: config.PermissionControllerAddress, }, ethClient, logger, diff --git a/pkg/user/admin/is_pending.go b/pkg/user/admin/is_pending.go index cdcdab99..371bc15a 100644 --- a/pkg/user/admin/is_pending.go +++ b/pkg/user/admin/is_pending.go @@ -81,32 +81,32 @@ func readAndValidateIsPendingAdminConfig( } chainID := utils.NetworkNameToChainId(network) - permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) var err error - if common.IsEmptyString(permissionManagerAddress) { - permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(PermissionControllerAddress) { + PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } } logger.Debugf( - "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + "Env: %s, network: %s, chain ID: %s, PermissionController address: %s", environment, network, chainID, - permissionManagerAddress, + PermissionControllerAddress, ) return &isPendingAdminConfig{ - Network: network, - RPCUrl: ethRpcUrl, - AccountAddress: accountAddress, - PendingAdminAddress: pendingAdminAddress, - PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), - ChainID: chainID, - Environment: environment, + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + PendingAdminAddress: pendingAdminAddress, + PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + ChainID: chainID, + Environment: environment, }, nil } @@ -125,7 +125,7 @@ func generateIsPendingAdminReader(logger logging.Logger, config *isPendingAdminC } elReader, err := elcontracts.NewReaderFromConfig( elcontracts.Config{ - PermissionsControllerAddress: config.PermissionManagerAddress, + PermissionsControllerAddress: config.PermissionControllerAddress, }, ethClient, logger, diff --git a/pkg/user/admin/list.go b/pkg/user/admin/list.go index 417f55b0..e04a7549 100644 --- a/pkg/user/admin/list.go +++ b/pkg/user/admin/list.go @@ -83,31 +83,31 @@ func readAndValidateListAdminsConfig(cliContext *cli.Context, logger logging.Log } chainID := utils.NetworkNameToChainId(network) - permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + permissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) var err error - if common.IsEmptyString(permissionManagerAddress) { - permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(permissionControllerAddress) { + permissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } } logger.Debugf( - "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + "Env: %s, network: %s, chain ID: %s, PermissionController address: %s", environment, network, chainID, - permissionManagerAddress, + permissionControllerAddress, ) return &listAdminsConfig{ - Network: network, - RPCUrl: ethRpcUrl, - AccountAddress: accountAddress, - PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), - ChainID: chainID, - Environment: environment, + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + PermissionControllerAddress: gethcommon.HexToAddress(permissionControllerAddress), + ChainID: chainID, + Environment: environment, }, nil } @@ -118,7 +118,7 @@ func generateListAdminsReader(logger logging.Logger, config *listAdminsConfig) ( } elReader, err := elcontracts.NewReaderFromConfig( elcontracts.Config{ - PermissionsControllerAddress: config.PermissionManagerAddress, + PermissionsControllerAddress: config.PermissionControllerAddress, }, ethClient, logger, diff --git a/pkg/user/admin/list_pending.go b/pkg/user/admin/list_pending.go index d724b7ab..7d97cb4b 100644 --- a/pkg/user/admin/list_pending.go +++ b/pkg/user/admin/list_pending.go @@ -88,31 +88,31 @@ func readAndValidateListPendingAdminsConfig( } chainID := utils.NetworkNameToChainId(network) - permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + permissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) var err error - if common.IsEmptyString(permissionManagerAddress) { - permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(permissionControllerAddress) { + permissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } } logger.Debugf( - "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + "Env: %s, network: %s, chain ID: %s, PermissionController address: %s", environment, network, chainID, - permissionManagerAddress, + permissionControllerAddress, ) return &listPendingAdminsConfig{ - Network: network, - RPCUrl: ethRpcUrl, - AccountAddress: accountAddress, - PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), - ChainID: chainID, - Environment: environment, + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + PermissionControllerAddress: gethcommon.HexToAddress(permissionControllerAddress), + ChainID: chainID, + Environment: environment, }, nil } @@ -126,7 +126,7 @@ func generateListPendingAdminsReader( } elReader, err := elcontracts.NewReaderFromConfig( elcontracts.Config{ - PermissionsControllerAddress: config.PermissionManagerAddress, + PermissionsControllerAddress: config.PermissionControllerAddress, }, ethClient, logger, diff --git a/pkg/user/admin/remove.go b/pkg/user/admin/remove.go index 18e13210..e6aa71d3 100644 --- a/pkg/user/admin/remove.go +++ b/pkg/user/admin/remove.go @@ -164,36 +164,36 @@ func readAndValidateRemoveAdminConfig( } chainID := utils.NetworkNameToChainId(network) - permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(permissionManagerAddress) { - permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(PermissionControllerAddress) { + PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } } logger.Debugf( - "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + "Env: %s, network: %s, chain ID: %s, PermissionController address: %s", environment, network, chainID, - permissionManagerAddress, + PermissionControllerAddress, ) return &removeAdminConfig{ - Network: network, - RPCUrl: ethRpcUrl, - AccountAddress: accountAddress, - AdminAddress: adminAddress, - CallerAddress: callerAddress, - PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), - SignerConfig: *signerConfig, - ChainID: chainID, - Environment: environment, - OutputFile: outputFile, - OutputType: outputType, - Broadcast: broadcast, + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + AdminAddress: adminAddress, + CallerAddress: callerAddress, + PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + SignerConfig: *signerConfig, + ChainID: chainID, + Environment: environment, + OutputFile: outputFile, + OutputType: outputType, + Broadcast: broadcast, }, nil } @@ -210,7 +210,7 @@ func generateRemoveAdminWriter( &config.SignerConfig, ethClient, elcontracts.Config{ - PermissionsControllerAddress: config.PermissionManagerAddress, + PermissionsControllerAddress: config.PermissionControllerAddress, }, prompter, config.ChainID, diff --git a/pkg/user/admin/remove_pending.go b/pkg/user/admin/remove_pending.go index 8110ad20..408bfec4 100644 --- a/pkg/user/admin/remove_pending.go +++ b/pkg/user/admin/remove_pending.go @@ -164,36 +164,36 @@ func readAndValidateRemovePendingAdminConfig( } chainID := utils.NetworkNameToChainId(network) - permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(permissionManagerAddress) { - permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(PermissionControllerAddress) { + PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } } logger.Debugf( - "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + "Env: %s, network: %s, chain ID: %s, PermissionController address: %s", environment, network, chainID, - permissionManagerAddress, + PermissionControllerAddress, ) return &removePendingAdminConfig{ - Network: network, - RPCUrl: ethRpcUrl, - AccountAddress: accountAddress, - AdminAddress: adminAddress, - CallerAddress: callerAddress, - PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), - SignerConfig: *signerConfig, - ChainID: chainID, - Environment: environment, - OutputFile: outputFile, - OutputType: outputType, - Broadcast: broadcast, + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + AdminAddress: adminAddress, + CallerAddress: callerAddress, + PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + SignerConfig: *signerConfig, + ChainID: chainID, + Environment: environment, + OutputFile: outputFile, + OutputType: outputType, + Broadcast: broadcast, }, nil } @@ -210,7 +210,7 @@ func generateRemovePendingAdminWriter( &config.SignerConfig, ethClient, elcontracts.Config{ - PermissionsControllerAddress: config.PermissionManagerAddress, + PermissionsControllerAddress: config.PermissionControllerAddress, }, prompter, config.ChainID, diff --git a/pkg/user/admin/types.go b/pkg/user/admin/types.go index 8a95d56f..a4635f76 100644 --- a/pkg/user/admin/types.go +++ b/pkg/user/admin/types.go @@ -8,98 +8,98 @@ import ( ) type listPendingAdminsConfig struct { - Network string - RPCUrl string - AccountAddress gethcommon.Address - PermissionManagerAddress gethcommon.Address - ChainID *big.Int - Environment string + Network string + RPCUrl string + AccountAddress gethcommon.Address + PermissionControllerAddress gethcommon.Address + ChainID *big.Int + Environment string } type listAdminsConfig struct { - Network string - RPCUrl string - AccountAddress gethcommon.Address - PermissionManagerAddress gethcommon.Address - ChainID *big.Int - Environment string + Network string + RPCUrl string + AccountAddress gethcommon.Address + PermissionControllerAddress gethcommon.Address + ChainID *big.Int + Environment string } type isPendingAdminConfig struct { - Network string - RPCUrl string - AccountAddress gethcommon.Address - PendingAdminAddress gethcommon.Address - PermissionManagerAddress gethcommon.Address - ChainID *big.Int - Environment string + Network string + RPCUrl string + AccountAddress gethcommon.Address + PendingAdminAddress gethcommon.Address + PermissionControllerAddress gethcommon.Address + ChainID *big.Int + Environment string } type isAdminConfig struct { - Network string - RPCUrl string - AccountAddress gethcommon.Address - AdminAddress gethcommon.Address - PermissionManagerAddress gethcommon.Address - ChainID *big.Int - Environment string + Network string + RPCUrl string + AccountAddress gethcommon.Address + AdminAddress gethcommon.Address + PermissionControllerAddress gethcommon.Address + ChainID *big.Int + Environment string } type acceptAdminConfig struct { - Network string - RPCUrl string - AccountAddress gethcommon.Address - CallerAddress gethcommon.Address - PermissionManagerAddress gethcommon.Address - SignerConfig types.SignerConfig - ChainID *big.Int - Environment string - OutputFile string - OutputType string - Broadcast bool + Network string + RPCUrl string + AccountAddress gethcommon.Address + CallerAddress gethcommon.Address + PermissionControllerAddress gethcommon.Address + SignerConfig types.SignerConfig + ChainID *big.Int + Environment string + OutputFile string + OutputType string + Broadcast bool } type addPendingAdminConfig struct { - Network string - RPCUrl string - AccountAddress gethcommon.Address - AdminAddress gethcommon.Address - CallerAddress gethcommon.Address - PermissionManagerAddress gethcommon.Address - SignerConfig types.SignerConfig - ChainID *big.Int - Environment string - OutputFile string - OutputType string - Broadcast bool + Network string + RPCUrl string + AccountAddress gethcommon.Address + AdminAddress gethcommon.Address + CallerAddress gethcommon.Address + PermissionControllerAddress gethcommon.Address + SignerConfig types.SignerConfig + ChainID *big.Int + Environment string + OutputFile string + OutputType string + Broadcast bool } type removeAdminConfig struct { - Network string - RPCUrl string - AccountAddress gethcommon.Address - AdminAddress gethcommon.Address - CallerAddress gethcommon.Address - PermissionManagerAddress gethcommon.Address - SignerConfig types.SignerConfig - ChainID *big.Int - Environment string - OutputFile string - OutputType string - Broadcast bool + Network string + RPCUrl string + AccountAddress gethcommon.Address + AdminAddress gethcommon.Address + CallerAddress gethcommon.Address + PermissionControllerAddress gethcommon.Address + SignerConfig types.SignerConfig + ChainID *big.Int + Environment string + OutputFile string + OutputType string + Broadcast bool } type removePendingAdminConfig struct { - Network string - RPCUrl string - AccountAddress gethcommon.Address - AdminAddress gethcommon.Address - CallerAddress gethcommon.Address - PermissionManagerAddress gethcommon.Address - SignerConfig types.SignerConfig - ChainID *big.Int - Environment string - OutputFile string - OutputType string - Broadcast bool + Network string + RPCUrl string + AccountAddress gethcommon.Address + AdminAddress gethcommon.Address + CallerAddress gethcommon.Address + PermissionControllerAddress gethcommon.Address + SignerConfig types.SignerConfig + ChainID *big.Int + Environment string + OutputFile string + OutputType string + Broadcast bool } diff --git a/pkg/user/appointee/can_call.go b/pkg/user/appointee/can_call.go index c5b79c97..fe95e32e 100644 --- a/pkg/user/appointee/can_call.go +++ b/pkg/user/appointee/can_call.go @@ -84,33 +84,33 @@ func readAndValidateCanCallConfig(cliContext *cli.Context, logger logging.Logger } chainID := utils.NetworkNameToChainId(network) - permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(permissionManagerAddress) { - permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(PermissionControllerAddress) { + PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } } logger.Debugf( - "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + "Env: %s, network: %s, chain ID: %s, PermissionController address: %s", environment, network, chainID, - permissionManagerAddress, + PermissionControllerAddress, ) return &canCallConfig{ - Network: network, - RPCUrl: ethRpcUrl, - AccountAddress: accountAddress, - AppointeeAddress: appointeeAddress, - Target: target, - Selector: selectorBytes, - PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), - ChainID: chainID, - Environment: environment, + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + AppointeeAddress: appointeeAddress, + Target: target, + Selector: selectorBytes, + PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + ChainID: chainID, + Environment: environment, }, nil } @@ -124,7 +124,7 @@ func generateCanCallReader( } elReader, err := elcontracts.NewReaderFromConfig( elcontracts.Config{ - PermissionsControllerAddress: config.PermissionManagerAddress, + PermissionsControllerAddress: config.PermissionControllerAddress, }, ethClient, logger, diff --git a/pkg/user/appointee/list.go b/pkg/user/appointee/list.go index 541a4cd1..32fc7308 100644 --- a/pkg/user/appointee/list.go +++ b/pkg/user/appointee/list.go @@ -102,32 +102,32 @@ func readAndValidateListAppointeesConfig( chainID := utils.NetworkNameToChainId(network) cliContext.App.Metadata["network"] = chainID.String() - permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(permissionManagerAddress) { - permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(PermissionControllerAddress) { + PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } } logger.Debugf( - "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + "Env: %s, network: %s, chain ID: %s, PermissionController address: %s", environment, network, chainID, - permissionManagerAddress, + PermissionControllerAddress, ) return &listAppointeesConfig{ - Network: network, - RPCUrl: ethRpcUrl, - AccountAddress: accountAddress, - Target: target, - Selector: selectorBytes, - PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), - ChainID: chainID, - Environment: environment, + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + Target: target, + Selector: selectorBytes, + PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + ChainID: chainID, + Environment: environment, }, nil } @@ -138,7 +138,7 @@ func generateListAppointeesReader(logger logging.Logger, config *listAppointeesC } elReader, err := elcontracts.NewReaderFromConfig( elcontracts.Config{ - PermissionsControllerAddress: config.PermissionManagerAddress, + PermissionsControllerAddress: config.PermissionControllerAddress, }, ethClient, logger, diff --git a/pkg/user/appointee/list_permissions.go b/pkg/user/appointee/list_permissions.go index f3845cd1..1bb63046 100644 --- a/pkg/user/appointee/list_permissions.go +++ b/pkg/user/appointee/list_permissions.go @@ -83,32 +83,32 @@ func readAndValidateListAppointeePermissionsConfig( } chainID := utils.NetworkNameToChainId(network) - permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) var err error - if common.IsEmptyString(permissionManagerAddress) { - permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(PermissionControllerAddress) { + PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } } logger.Debugf( - "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + "Env: %s, network: %s, chain ID: %s, PermissionController address: %s", environment, network, chainID, - permissionManagerAddress, + PermissionControllerAddress, ) return &listAppointeePermissionsConfig{ - Network: network, - RPCUrl: ethRpcUrl, - AccountAddress: accountAddress, - AppointeeAddress: appointeeAddress, - PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), - ChainID: chainID, - Environment: environment, + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + AppointeeAddress: appointeeAddress, + PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + ChainID: chainID, + Environment: environment, }, nil } @@ -132,7 +132,7 @@ func generateListAppointeePermissionsReader( } elReader, err := elcontracts.NewReaderFromConfig( elcontracts.Config{ - PermissionsControllerAddress: config.PermissionManagerAddress, + PermissionsControllerAddress: config.PermissionControllerAddress, }, ethClient, logger, diff --git a/pkg/user/appointee/remove.go b/pkg/user/appointee/remove.go index 276ccadf..6149f937 100644 --- a/pkg/user/appointee/remove.go +++ b/pkg/user/appointee/remove.go @@ -158,7 +158,7 @@ func generateRemoveAppointeePermissionWriter( &config.SignerConfig, ethClient, elcontracts.Config{ - PermissionsControllerAddress: config.PermissionManagerAddress, + PermissionsControllerAddress: config.PermissionControllerAddress, }, prompter, config.ChainID, @@ -197,37 +197,37 @@ func readAndValidateRemoveConfig(cliContext *cli.Context, logger logging.Logger) chainID := utils.NetworkNameToChainId(network) cliContext.App.Metadata["network"] = chainID.String() - permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(permissionManagerAddress) { - permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(PermissionControllerAddress) { + PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } } logger.Debugf( - "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + "Env: %s, network: %s, chain ID: %s, PermissionController address: %s", environment, network, chainID, - permissionManagerAddress, + PermissionControllerAddress, ) return &removeConfig{ - Network: network, - RPCUrl: ethRpcUrl, - AccountAddress: accountAddress, - AppointeeAddress: appointeeAddress, - CallerAddress: callerAddress, - Target: target, - Selector: selectorBytes, - SignerConfig: *signerConfig, - PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), - ChainID: chainID, - Environment: environment, - Broadcast: broadcast, - OutputType: outputType, - OutputFile: outputFile, + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + AppointeeAddress: appointeeAddress, + CallerAddress: callerAddress, + Target: target, + Selector: selectorBytes, + SignerConfig: *signerConfig, + PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + ChainID: chainID, + Environment: environment, + Broadcast: broadcast, + OutputType: outputType, + OutputFile: outputFile, }, nil } diff --git a/pkg/user/appointee/set.go b/pkg/user/appointee/set.go index 9227d9b3..5ce47870 100644 --- a/pkg/user/appointee/set.go +++ b/pkg/user/appointee/set.go @@ -161,7 +161,7 @@ func generateSetAppointeePermissionWriter( &config.SignerConfig, ethClient, elcontracts.Config{ - PermissionsControllerAddress: config.PermissionManagerAddress, + PermissionsControllerAddress: config.PermissionControllerAddress, }, prompter, config.ChainID, @@ -200,38 +200,38 @@ func readAndValidateSetConfig(cliContext *cli.Context, logger logging.Logger) (* chainID := utils.NetworkNameToChainId(network) cliContext.App.Metadata["network"] = chainID.String() - permissionManagerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(permissionManagerAddress) { - permissionManagerAddress, err = common.GetPermissionManagerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(PermissionControllerAddress) { + PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } } logger.Debugf( - "Env: %s, network: %s, chain ID: %s, PermissionManager address: %s", + "Env: %s, network: %s, chain ID: %s, PermissionController address: %s", environment, network, chainID, - permissionManagerAddress, + PermissionControllerAddress, ) return &setConfig{ - Network: network, - RPCUrl: ethRpcUrl, - AccountAddress: accountAddress, - AppointeeAddress: appointeeAddress, - CallerAddress: callerAddress, - Target: target, - Selector: selectorBytes, - SignerConfig: *signerConfig, - PermissionManagerAddress: gethcommon.HexToAddress(permissionManagerAddress), - ChainID: chainID, - Environment: environment, - OutputFile: outputFile, - OutputType: outputType, - Broadcast: broadcast, + Network: network, + RPCUrl: ethRpcUrl, + AccountAddress: accountAddress, + AppointeeAddress: appointeeAddress, + CallerAddress: callerAddress, + Target: target, + Selector: selectorBytes, + SignerConfig: *signerConfig, + PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + ChainID: chainID, + Environment: environment, + OutputFile: outputFile, + OutputType: outputType, + Broadcast: broadcast, }, nil } diff --git a/pkg/user/appointee/types.go b/pkg/user/appointee/types.go index e505adc3..7d6ff1ac 100644 --- a/pkg/user/appointee/types.go +++ b/pkg/user/appointee/types.go @@ -8,68 +8,68 @@ import ( ) type canCallConfig struct { - Network string - RPCUrl string - AccountAddress gethcommon.Address - AppointeeAddress gethcommon.Address - Target gethcommon.Address - Selector [4]byte - PermissionManagerAddress gethcommon.Address - ChainID *big.Int - Environment string + Network string + RPCUrl string + AccountAddress gethcommon.Address + AppointeeAddress gethcommon.Address + Target gethcommon.Address + Selector [4]byte + PermissionControllerAddress gethcommon.Address + ChainID *big.Int + Environment string } type listAppointeesConfig struct { - Network string - RPCUrl string - AccountAddress gethcommon.Address - Target gethcommon.Address - Selector [4]byte - PermissionManagerAddress gethcommon.Address - ChainID *big.Int - Environment string + Network string + RPCUrl string + AccountAddress gethcommon.Address + Target gethcommon.Address + Selector [4]byte + PermissionControllerAddress gethcommon.Address + ChainID *big.Int + Environment string } type listAppointeePermissionsConfig struct { - Network string - RPCUrl string - AccountAddress gethcommon.Address - AppointeeAddress gethcommon.Address - PermissionManagerAddress gethcommon.Address - ChainID *big.Int - Environment string + Network string + RPCUrl string + AccountAddress gethcommon.Address + AppointeeAddress gethcommon.Address + PermissionControllerAddress gethcommon.Address + ChainID *big.Int + Environment string } type removeConfig struct { - Network string - RPCUrl string - AccountAddress gethcommon.Address - AppointeeAddress gethcommon.Address - CallerAddress gethcommon.Address - Target gethcommon.Address - SignerConfig types.SignerConfig - Selector [4]byte - PermissionManagerAddress gethcommon.Address - ChainID *big.Int - Environment string - OutputFile string - OutputType string - Broadcast bool + Network string + RPCUrl string + AccountAddress gethcommon.Address + AppointeeAddress gethcommon.Address + CallerAddress gethcommon.Address + Target gethcommon.Address + SignerConfig types.SignerConfig + Selector [4]byte + PermissionControllerAddress gethcommon.Address + ChainID *big.Int + Environment string + OutputFile string + OutputType string + Broadcast bool } type setConfig struct { - Network string - RPCUrl string - AccountAddress gethcommon.Address - AppointeeAddress gethcommon.Address - CallerAddress gethcommon.Address - Target gethcommon.Address - SignerConfig types.SignerConfig - Selector [4]byte - PermissionManagerAddress gethcommon.Address - ChainID *big.Int - Environment string - OutputFile string - OutputType string - Broadcast bool + Network string + RPCUrl string + AccountAddress gethcommon.Address + AppointeeAddress gethcommon.Address + CallerAddress gethcommon.Address + Target gethcommon.Address + SignerConfig types.SignerConfig + Selector [4]byte + PermissionControllerAddress gethcommon.Address + ChainID *big.Int + Environment string + OutputFile string + OutputType string + Broadcast bool } From 2107746211c2ff0be4ae4e5e2ae548e16e40861a Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Fri, 20 Dec 2024 13:10:30 -0800 Subject: [PATCH 33/34] Fixing name casing for PermissionController. (#281) Co-authored-by: Brandon Chatham --- pkg/user/admin/accept.go | 10 +++++----- pkg/user/admin/add_pending.go | 10 +++++----- pkg/user/admin/is_admin.go | 10 +++++----- pkg/user/admin/is_pending.go | 10 +++++----- pkg/user/admin/remove.go | 10 +++++----- pkg/user/admin/remove_pending.go | 10 +++++----- pkg/user/appointee/can_call.go | 10 +++++----- pkg/user/appointee/list.go | 10 +++++----- pkg/user/appointee/list_permissions.go | 10 +++++----- pkg/user/appointee/remove.go | 10 +++++----- pkg/user/appointee/set.go | 10 +++++----- 11 files changed, 55 insertions(+), 55 deletions(-) diff --git a/pkg/user/admin/accept.go b/pkg/user/admin/accept.go index 19e9f1c8..3b75833c 100644 --- a/pkg/user/admin/accept.go +++ b/pkg/user/admin/accept.go @@ -163,10 +163,10 @@ func readAndValidateAcceptAdminConfig( } chainID := utils.NetworkNameToChainId(network) - PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + permissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(PermissionControllerAddress) { - PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(permissionControllerAddress) { + permissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } @@ -176,7 +176,7 @@ func readAndValidateAcceptAdminConfig( environment, network, chainID, - PermissionControllerAddress, + permissionControllerAddress, ) return &acceptAdminConfig{ @@ -184,7 +184,7 @@ func readAndValidateAcceptAdminConfig( RPCUrl: ethRpcUrl, AccountAddress: accountAddress, CallerAddress: callerAddress, - PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + PermissionControllerAddress: gethcommon.HexToAddress(permissionControllerAddress), SignerConfig: *signerConfig, ChainID: chainID, Environment: environment, diff --git a/pkg/user/admin/add_pending.go b/pkg/user/admin/add_pending.go index 623fa9a1..7a596987 100644 --- a/pkg/user/admin/add_pending.go +++ b/pkg/user/admin/add_pending.go @@ -166,10 +166,10 @@ func readAndValidateAddPendingAdminConfig( } chainID := utils.NetworkNameToChainId(network) - PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + permissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(PermissionControllerAddress) { - PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(permissionControllerAddress) { + permissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } @@ -180,7 +180,7 @@ func readAndValidateAddPendingAdminConfig( environment, network, chainID, - PermissionControllerAddress, + permissionControllerAddress, ) return &addPendingAdminConfig{ @@ -190,7 +190,7 @@ func readAndValidateAddPendingAdminConfig( AdminAddress: adminAddress, CallerAddress: callerAddress, SignerConfig: *signerConfig, - PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + PermissionControllerAddress: gethcommon.HexToAddress(permissionControllerAddress), ChainID: chainID, Environment: environment, OutputFile: outputFile, diff --git a/pkg/user/admin/is_admin.go b/pkg/user/admin/is_admin.go index 6064c898..6739fba7 100644 --- a/pkg/user/admin/is_admin.go +++ b/pkg/user/admin/is_admin.go @@ -73,11 +73,11 @@ func readAndValidateIsAdminConfig(cliContext *cli.Context, logger logging.Logger } chainID := utils.NetworkNameToChainId(network) - PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + permissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) var err error - if common.IsEmptyString(PermissionControllerAddress) { - PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(permissionControllerAddress) { + permissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } @@ -88,7 +88,7 @@ func readAndValidateIsAdminConfig(cliContext *cli.Context, logger logging.Logger environment, network, chainID, - PermissionControllerAddress, + permissionControllerAddress, ) return &isAdminConfig{ @@ -96,7 +96,7 @@ func readAndValidateIsAdminConfig(cliContext *cli.Context, logger logging.Logger RPCUrl: ethRpcUrl, AccountAddress: accountAddress, AdminAddress: adminAddress, - PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + PermissionControllerAddress: gethcommon.HexToAddress(permissionControllerAddress), ChainID: chainID, Environment: environment, }, nil diff --git a/pkg/user/admin/is_pending.go b/pkg/user/admin/is_pending.go index 371bc15a..1c7bda9a 100644 --- a/pkg/user/admin/is_pending.go +++ b/pkg/user/admin/is_pending.go @@ -81,11 +81,11 @@ func readAndValidateIsPendingAdminConfig( } chainID := utils.NetworkNameToChainId(network) - PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + permissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) var err error - if common.IsEmptyString(PermissionControllerAddress) { - PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(permissionControllerAddress) { + permissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } @@ -96,7 +96,7 @@ func readAndValidateIsPendingAdminConfig( environment, network, chainID, - PermissionControllerAddress, + permissionControllerAddress, ) return &isPendingAdminConfig{ @@ -104,7 +104,7 @@ func readAndValidateIsPendingAdminConfig( RPCUrl: ethRpcUrl, AccountAddress: accountAddress, PendingAdminAddress: pendingAdminAddress, - PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + PermissionControllerAddress: gethcommon.HexToAddress(permissionControllerAddress), ChainID: chainID, Environment: environment, }, nil diff --git a/pkg/user/admin/remove.go b/pkg/user/admin/remove.go index e6aa71d3..6f38e159 100644 --- a/pkg/user/admin/remove.go +++ b/pkg/user/admin/remove.go @@ -164,10 +164,10 @@ func readAndValidateRemoveAdminConfig( } chainID := utils.NetworkNameToChainId(network) - PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + permissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(PermissionControllerAddress) { - PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(permissionControllerAddress) { + permissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } @@ -178,7 +178,7 @@ func readAndValidateRemoveAdminConfig( environment, network, chainID, - PermissionControllerAddress, + permissionControllerAddress, ) return &removeAdminConfig{ @@ -187,7 +187,7 @@ func readAndValidateRemoveAdminConfig( AccountAddress: accountAddress, AdminAddress: adminAddress, CallerAddress: callerAddress, - PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + PermissionControllerAddress: gethcommon.HexToAddress(permissionControllerAddress), SignerConfig: *signerConfig, ChainID: chainID, Environment: environment, diff --git a/pkg/user/admin/remove_pending.go b/pkg/user/admin/remove_pending.go index 408bfec4..fb2d7ff9 100644 --- a/pkg/user/admin/remove_pending.go +++ b/pkg/user/admin/remove_pending.go @@ -164,10 +164,10 @@ func readAndValidateRemovePendingAdminConfig( } chainID := utils.NetworkNameToChainId(network) - PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + permissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(PermissionControllerAddress) { - PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(permissionControllerAddress) { + permissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } @@ -178,7 +178,7 @@ func readAndValidateRemovePendingAdminConfig( environment, network, chainID, - PermissionControllerAddress, + permissionControllerAddress, ) return &removePendingAdminConfig{ @@ -187,7 +187,7 @@ func readAndValidateRemovePendingAdminConfig( AccountAddress: accountAddress, AdminAddress: adminAddress, CallerAddress: callerAddress, - PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + PermissionControllerAddress: gethcommon.HexToAddress(permissionControllerAddress), SignerConfig: *signerConfig, ChainID: chainID, Environment: environment, diff --git a/pkg/user/appointee/can_call.go b/pkg/user/appointee/can_call.go index fe95e32e..59677787 100644 --- a/pkg/user/appointee/can_call.go +++ b/pkg/user/appointee/can_call.go @@ -84,10 +84,10 @@ func readAndValidateCanCallConfig(cliContext *cli.Context, logger logging.Logger } chainID := utils.NetworkNameToChainId(network) - PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + permissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(PermissionControllerAddress) { - PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(permissionControllerAddress) { + permissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } @@ -98,7 +98,7 @@ func readAndValidateCanCallConfig(cliContext *cli.Context, logger logging.Logger environment, network, chainID, - PermissionControllerAddress, + permissionControllerAddress, ) return &canCallConfig{ @@ -108,7 +108,7 @@ func readAndValidateCanCallConfig(cliContext *cli.Context, logger logging.Logger AppointeeAddress: appointeeAddress, Target: target, Selector: selectorBytes, - PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + PermissionControllerAddress: gethcommon.HexToAddress(permissionControllerAddress), ChainID: chainID, Environment: environment, }, nil diff --git a/pkg/user/appointee/list.go b/pkg/user/appointee/list.go index 32fc7308..adb10e3c 100644 --- a/pkg/user/appointee/list.go +++ b/pkg/user/appointee/list.go @@ -102,10 +102,10 @@ func readAndValidateListAppointeesConfig( chainID := utils.NetworkNameToChainId(network) cliContext.App.Metadata["network"] = chainID.String() - PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + permissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(PermissionControllerAddress) { - PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(permissionControllerAddress) { + permissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } @@ -116,7 +116,7 @@ func readAndValidateListAppointeesConfig( environment, network, chainID, - PermissionControllerAddress, + permissionControllerAddress, ) return &listAppointeesConfig{ @@ -125,7 +125,7 @@ func readAndValidateListAppointeesConfig( AccountAddress: accountAddress, Target: target, Selector: selectorBytes, - PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + PermissionControllerAddress: gethcommon.HexToAddress(permissionControllerAddress), ChainID: chainID, Environment: environment, }, nil diff --git a/pkg/user/appointee/list_permissions.go b/pkg/user/appointee/list_permissions.go index 1bb63046..3d8403cc 100644 --- a/pkg/user/appointee/list_permissions.go +++ b/pkg/user/appointee/list_permissions.go @@ -83,11 +83,11 @@ func readAndValidateListAppointeePermissionsConfig( } chainID := utils.NetworkNameToChainId(network) - PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + permissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) var err error - if common.IsEmptyString(PermissionControllerAddress) { - PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(permissionControllerAddress) { + permissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } @@ -98,7 +98,7 @@ func readAndValidateListAppointeePermissionsConfig( environment, network, chainID, - PermissionControllerAddress, + permissionControllerAddress, ) return &listAppointeePermissionsConfig{ @@ -106,7 +106,7 @@ func readAndValidateListAppointeePermissionsConfig( RPCUrl: ethRpcUrl, AccountAddress: accountAddress, AppointeeAddress: appointeeAddress, - PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + PermissionControllerAddress: gethcommon.HexToAddress(permissionControllerAddress), ChainID: chainID, Environment: environment, }, nil diff --git a/pkg/user/appointee/remove.go b/pkg/user/appointee/remove.go index 6149f937..02290af4 100644 --- a/pkg/user/appointee/remove.go +++ b/pkg/user/appointee/remove.go @@ -197,10 +197,10 @@ func readAndValidateRemoveConfig(cliContext *cli.Context, logger logging.Logger) chainID := utils.NetworkNameToChainId(network) cliContext.App.Metadata["network"] = chainID.String() - PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + permissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(PermissionControllerAddress) { - PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(permissionControllerAddress) { + permissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } @@ -210,7 +210,7 @@ func readAndValidateRemoveConfig(cliContext *cli.Context, logger logging.Logger) environment, network, chainID, - PermissionControllerAddress, + permissionControllerAddress, ) return &removeConfig{ @@ -222,7 +222,7 @@ func readAndValidateRemoveConfig(cliContext *cli.Context, logger logging.Logger) Target: target, Selector: selectorBytes, SignerConfig: *signerConfig, - PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + PermissionControllerAddress: gethcommon.HexToAddress(permissionControllerAddress), ChainID: chainID, Environment: environment, Broadcast: broadcast, diff --git a/pkg/user/appointee/set.go b/pkg/user/appointee/set.go index 5ce47870..0c80dd83 100644 --- a/pkg/user/appointee/set.go +++ b/pkg/user/appointee/set.go @@ -200,10 +200,10 @@ func readAndValidateSetConfig(cliContext *cli.Context, logger logging.Logger) (* chainID := utils.NetworkNameToChainId(network) cliContext.App.Metadata["network"] = chainID.String() - PermissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) + permissionControllerAddress := cliContext.String(PermissionControllerAddressFlag.Name) - if common.IsEmptyString(PermissionControllerAddress) { - PermissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) + if common.IsEmptyString(permissionControllerAddress) { + permissionControllerAddress, err = common.GetPermissionControllerAddress(utils.NetworkNameToChainId(network)) if err != nil { return nil, err } @@ -214,7 +214,7 @@ func readAndValidateSetConfig(cliContext *cli.Context, logger logging.Logger) (* environment, network, chainID, - PermissionControllerAddress, + permissionControllerAddress, ) return &setConfig{ @@ -226,7 +226,7 @@ func readAndValidateSetConfig(cliContext *cli.Context, logger logging.Logger) (* Target: target, Selector: selectorBytes, SignerConfig: *signerConfig, - PermissionControllerAddress: gethcommon.HexToAddress(PermissionControllerAddress), + PermissionControllerAddress: gethcommon.HexToAddress(permissionControllerAddress), ChainID: chainID, Environment: environment, OutputFile: outputFile, From 435756e61901bd5008560f44bfdce740d40650fb Mon Sep 17 00:00:00 2001 From: FromTheRain Date: Fri, 20 Dec 2024 17:21:04 -0800 Subject: [PATCH 34/34] Adding user command integration tests to github CI workflow. (#282) Co-authored-by: Brandon Chatham --- .github/workflows/integration-test.yml | 51 ++- tests/user/.env | 23 ++ tests/user/admin/admin_lifecycle_tests.sh | 310 ++++++++++++++++ tests/user/admin/admin_utils.sh | 66 ++++ tests/user/admin/rotate_admin_tests.sh | 66 ++++ .../appointee/appointee_lifecycle_tests.sh | 332 ++++++++++++++++++ .../user/appointee/appointee_output_tests.sh | 115 ++++++ tests/user/user-integration-tests-runner.sh | 30 ++ 8 files changed, 992 insertions(+), 1 deletion(-) create mode 100755 tests/user/.env create mode 100755 tests/user/admin/admin_lifecycle_tests.sh create mode 100644 tests/user/admin/admin_utils.sh create mode 100644 tests/user/admin/rotate_admin_tests.sh create mode 100755 tests/user/appointee/appointee_lifecycle_tests.sh create mode 100755 tests/user/appointee/appointee_output_tests.sh create mode 100755 tests/user/user-integration-tests-runner.sh diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 3f9a4cc9..0c36c569 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -157,4 +157,53 @@ jobs: --claimer-address 0x2222AAC0C980Cc029624b7ff55B88Bc6F63C538f \ --web3signer-url http://127.0.0.1:9001 \ --verbose \ - --broadcast \ No newline at end of file + --broadcast + + UserIntegrationTests: + name: Integration Test - User Commands + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly-c4a984fbf2c48b793c8cd53af84f56009dd1070c + + - name: Checkout eigensdk-go + uses: actions/checkout@v4 + with: + repository: layr-labs/eigensdk-go + token: ${{ github.token }} + ref: 549e0185cee644d0a6fc9c9863f1cf76d9ef971f + + - name: Run anvil chain + run: | + nohup make start-anvil-with-contracts-deployed > nohup.out 2>&1 & + + - name: Install EigenLayer CLI + uses: actions/checkout@v4 + with: + path: eigenlayer-cli + + - name: Setup BATS + uses: mig4/setup-bats@v1 + + - name: Install EigenLayer CLI + run: | + cd eigenlayer-cli + make build + ./bin/eigenlayer --version + + - name: Prepare Integration Test Environment + run: | + echo "Preparing environment for integration tests." + cd eigenlayer-cli + echo "CLI_PATH=${GITHUB_WORKSPACE}/bin/eigenlayer" >> .env + + - name: Run User Command Integration Tests + run: | + cd eigenlayer-cli/tests/user + ./user-integration-tests-runner.sh \ No newline at end of file diff --git a/tests/user/.env b/tests/user/.env new file mode 100755 index 00000000..d14f6e06 --- /dev/null +++ b/tests/user/.env @@ -0,0 +1,23 @@ +CLI_PATH=../../bin/eigenlayer +NETWORK=anvil +RPC_URL=http://localhost:8545 +OUTPUT_FILE_FOLDER=output + +ACCOUNT_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +FIRST_ADMIN_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8 +SECOND_ADMIN_ADDRESS=0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC + +ACCOUNT_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +FIRST_ADMIN_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d +SECOND_ADMIN_PRIVATE_KEY=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a + +APPOINTEE_1=0x70997970C51812dc3A010C7d01b50e0d17dc79C8 +APPOINTEE_2=0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC +APPOINTEE_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8 + +TARGET_ADDRESS=0x3Aa5ebB10DC797CAC828524e59A333d0A371443c + +SELECTOR_1=0x4f906cf9 +SELECTOR_2=0x268959e5 + +PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 diff --git a/tests/user/admin/admin_lifecycle_tests.sh b/tests/user/admin/admin_lifecycle_tests.sh new file mode 100755 index 00000000..675b0244 --- /dev/null +++ b/tests/user/admin/admin_lifecycle_tests.sh @@ -0,0 +1,310 @@ +#!/usr/bin/env bats + +load './admin_utils.sh' + +setup() { + echo "Setting up test environment..." + rm -f "$OUTPUT_FILE_FOLDER/output_*.txt" + + echo "Listing admins for account $ACCOUNT_ADDRESS..." + output_list_admins=$($CLI_PATH user admin list-admins \ + --account-address "$ACCOUNT_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK") + echo "$output_list_admins" + + echo "Running conditional_add_admin..." + output_conditional_add=$(conditional_add_admin \ + "$ACCOUNT_ADDRESS" \ + "$ACCOUNT_ADDRESS" \ + "$ACCOUNT_PRIVATE_KEY" \ + "$PERMISSION_CONTROLLER_ADDRESS" \ + "$RPC_URL" \ + "$NETWORK" \ + "$OUTPUT_ADD_FILE" \ + "$OUTPUT_ACCEPT_FILE") + echo "$output_conditional_add" +} + +teardown() { + echo "Cleaning up test environment..." + rm -f "$OUTPUT_FILE_FOLDER/output_*.txt" +} + +@test "Add $FIRST_ADMIN_ADDRESS as admin (calldata)" { + run $CLI_PATH user admin add-pending-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --admin-address "$FIRST_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$ACCOUNT_PRIVATE_KEY" \ + --output-type "calldata" \ + --output-file "$OUTPUT_FILE_FOLDER/output_add_first_admin.txt" + + [ "$status" -eq 0 ] + [[ -s "$OUTPUT_FILE_FOLDER/output_add_first_admin.txt" ]] +} + +@test "Add $FIRST_ADMIN_ADDRESS as admin (broadcast)" { + run $CLI_PATH user admin add-pending-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --admin-address "$FIRST_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$ACCOUNT_PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Verify $FIRST_ADMIN_ADDRESS is a pending admin" { + run $CLI_PATH user admin is-pending-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --pending-admin-address "$FIRST_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + echo "$output" + + [ "$status" -eq 0 ] + [[ "$output" == *"Address provided is a pending admin"* ]] +} + +@test "Remove $FIRST_ADMIN_ADDRESS as pending admin (calldata)" { + run $CLI_PATH user admin remove-pending-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --admin-address "$FIRST_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$ACCOUNT_PRIVATE_KEY" \ + --output-type "calldata" \ + --output-file "$OUTPUT_FILE_FOLDER/output_remove_pending_first_admin.txt" + + echo "$output" + + [ "$status" -eq 0 ] + [[ -s "$OUTPUT_FILE_FOLDER/output_remove_pending_first_admin.txt" ]] +} + +@test "Remove $FIRST_ADMIN_ADDRESS as pending admin (broadcast)" { + run $CLI_PATH user admin remove-pending-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --admin-address "$FIRST_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$ACCOUNT_PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Add $FIRST_ADMIN_ADDRESS as admin after removal (broadcast)" { + run $CLI_PATH user admin add-pending-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --admin-address "$FIRST_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$ACCOUNT_PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Accept $FIRST_ADMIN_ADDRESS as admin (calldata)" { + run $CLI_PATH user admin accept-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --caller-address "$FIRST_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$FIRST_ADMIN_PRIVATE_KEY" \ + --output-type "calldata" \ + --output-file "$OUTPUT_FILE_FOLDER/output_accept_first_admin.txt" + + [ "$status" -eq 0 ] + [[ -s "$OUTPUT_FILE_FOLDER/output_accept_first_admin.txt" ]] +} + +@test "Accept $FIRST_ADMIN_ADDRESS as admin (broadcast)" { + run $CLI_PATH user admin accept-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$FIRST_ADMIN_PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Add $SECOND_ADMIN_ADDRESS as admin (calldata)" { + run $CLI_PATH user admin add-pending-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --admin-address "$SECOND_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$FIRST_ADMIN_PRIVATE_KEY" \ + --output-type "calldata" \ + --output-file "$OUTPUT_FILE_FOLDER/output_add_second_admin.txt" + + [ "$status" -eq 0 ] + [[ -s "$OUTPUT_FILE_FOLDER/output_add_second_admin.txt" ]] +} + +@test "Add $SECOND_ADMIN_ADDRESS as admin (broadcast)" { + run $CLI_PATH user admin add-pending-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --admin-address "$SECOND_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$FIRST_ADMIN_PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Verify $SECOND_ADMIN_ADDRESS is listed as a pending admin" { + run $CLI_PATH user admin list-pending-admins \ + --account-address "$ACCOUNT_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + + [ "$status" -eq 0 ] + [[ "$output" != *"$FIRST_ADMIN_ADDRESS"* ]] + [[ "$output" == *"$SECOND_ADMIN_ADDRESS"* ]] +} + +@test "Verify $SECOND_ADMIN_ADDRESS is a pending admin" { + run $CLI_PATH user admin is-pending-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --pending-admin-address "$SECOND_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + echo "$output" + + [ "$status" -eq 0 ] + [[ "$output" == *"Address provided is a pending admin"* ]] +} + +@test "Accept $SECOND_ADMIN_ADDRESS as admin (calldata)" { + run $CLI_PATH user admin accept-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --caller-address "$SECOND_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$SECOND_ADMIN_PRIVATE_KEY" \ + --output-type "calldata" \ + --output-file "$OUTPUT_FILE_FOLDER/output_accept_second_admin.txt" + + [ "$status" -eq 0 ] + [[ -s "$OUTPUT_FILE_FOLDER/output_accept_second_admin.txt" ]] +} + +@test "Accept $SECOND_ADMIN_ADDRESS as admin (broadcast)" { + run $CLI_PATH user admin accept-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$SECOND_ADMIN_PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Verify all three admins are listed after acceptance" { + run $CLI_PATH user admin list-admins \ + --account-address "$ACCOUNT_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + echo "$output" + + [ "$status" -eq 0 ] + [[ "$output" == *"$ACCOUNT_ADDRESS"* ]] + [[ "$output" == *"$FIRST_ADMIN_ADDRESS"* ]] + [[ "$output" == *"$SECOND_ADMIN_ADDRESS"* ]] +} + +@test "Remove $SECOND_ADMIN_ADDRESS as admin (calldata)" { + run $CLI_PATH user admin remove-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --admin-address "$SECOND_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$FIRST_ADMIN_PRIVATE_KEY" \ + --output-type "calldata" \ + --output-file "$OUTPUT_FILE_FOLDER/output_remove_second_admin.txt" + + [ "$status" -eq 0 ] + [[ -s "$OUTPUT_FILE_FOLDER/output_remove_second_admin.txt" ]] +} + +@test "Remove $SECOND_ADMIN_ADDRESS as admin (broadcast)" { + run $CLI_PATH user admin remove-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --admin-address "$SECOND_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$FIRST_ADMIN_PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Remove $FIRST_ADMIN_ADDRESS as admin (calldata)" { + run $CLI_PATH user admin remove-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --admin-address "$FIRST_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$ACCOUNT_PRIVATE_KEY" \ + --output-type "calldata" \ + --output-file "$OUTPUT_FILE_FOLDER/output_remove_first_admin.txt" + + [ "$status" -eq 0 ] + [[ -s "$OUTPUT_FILE_FOLDER/output_remove_first_admin.txt" ]] +} + +@test "Remove $FIRST_ADMIN_ADDRESS as admin (broadcast)" { + run $CLI_PATH user admin remove-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --admin-address "$FIRST_ADMIN_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$ACCOUNT_PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Verify only root admins remains" { + run $CLI_PATH user admin list-admins \ + --account-address "$ACCOUNT_ADDRESS" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" == *"$ACCOUNT_ADDRESS"* ]] + [[ "$output" != *"$FIRST_ADMIN_ADDRESS"* ]] + [[ "$output" != *"$SECOND_ADMIN_ADDRESS"* ]] +} diff --git a/tests/user/admin/admin_utils.sh b/tests/user/admin/admin_utils.sh new file mode 100644 index 00000000..4a7b2ca1 --- /dev/null +++ b/tests/user/admin/admin_utils.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +broadcast_add_pending_admin() { + local account="$1" + local admin="$2" + local private_key="$3" + local permission_controller_address="$4" + local rpc_url="$5" + local network="$6" + + echo "Broadcasting add-pending-admin for $admin to $account..." + $CLI_PATH user admin add-pending-admin \ + --account-address "$account" \ + --admin-address "$admin" \ + --permission-controller-address "$permission_controller_address" \ + --eth-rpc-url "$rpc_url" \ + --network "$network" \ + --ecdsa-private-key "$private_key" \ + --broadcast +} + +broadcast_accept_admin() { + local account="$1" + local private_key="$2" + local permission_controller_address="$3" + local rpc_url="$4" + local network="$5" + + echo "Broadcasting accept-admin for $account..." + $CLI_PATH user admin accept-admin \ + --account-address "$account" \ + --permission-controller-address "$permission_controller_address" \ + --eth-rpc-url "$rpc_url" \ + --network "$network" \ + --ecdsa-private-key "$private_key" \ + --broadcast +} + +conditional_add_admin() { + local account="$1" + local admin="$2" + local private_key="$3" + local permission_controller_address="$4" + local rpc_url="$5" + local network="$6" + local output_add_file="$7" + local output_accept_file="$8" + local is_admin_string="is an admin" + + echo "Checking if $admin is an admin for $account..." + local is_admin_output=$($CLI_PATH user admin is-admin \ + --account-address "$account" \ + --admin-address "$admin" \ + --permission-controller-address "$permission_controller_address" \ + --eth-rpc-url "$rpc_url" \ + --network "$network" 2>&1) + + if echo "$is_admin_output" | grep -q "$is_admin_string"; then + echo "$admin is already an admin for $account." + else + echo "$admin is not an admin for $account. Adding as an admin..." + broadcast_add_pending_admin "$account" "$admin" "$private_key" "$permission_controller_address" "$rpc_url" "$network" "$output_add_file" + broadcast_accept_admin "$account" "$admin" "$private_key" "$permission_controller_address" "$rpc_url" "$network" "$output_accept_file" + fi +} + diff --git a/tests/user/admin/rotate_admin_tests.sh b/tests/user/admin/rotate_admin_tests.sh new file mode 100644 index 00000000..edf0fef3 --- /dev/null +++ b/tests/user/admin/rotate_admin_tests.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bats + +@test "Rotate admins so first admin address is the only admin" { + run $CLI_PATH user admin add-pending-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --admin-address "$FIRST_ADMIN_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$ACCOUNT_PRIVATE_KEY" \ + --broadcast + [ "$status" -eq 0 ] + + run $CLI_PATH user admin accept-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$FIRST_ADMIN_PRIVATE_KEY" \ + --broadcast + [ "$status" -eq 0 ] + + run $CLI_PATH user admin list-admins \ + --account-address "$ACCOUNT_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + [ "$status" -eq 0 ] + + [[ "$output" == *"$FIRST_ADMIN_ADDRESS"* ]] +} + +@test "Rotate admins again so root account is the only admin" { + run $CLI_PATH user admin add-pending-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --admin-address "$ACCOUNT_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$FIRST_ADMIN_PRIVATE_KEY" \ + --broadcast + [ "$status" -eq 0 ] + + run $CLI_PATH user admin accept-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$ACCOUNT_PRIVATE_KEY" \ + --broadcast + [ "$status" -eq 0 ] + + run $CLI_PATH user admin remove-admin \ + --account-address "$ACCOUNT_ADDRESS" \ + --admin-address "$FIRST_ADMIN_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$ACCOUNT_PRIVATE_KEY" \ + --broadcast + [ "$status" -eq 0 ] + + run $CLI_PATH user admin list-admins \ + --account-address "$ACCOUNT_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + [ "$status" -eq 0 ] + + [[ "$output" == *"$ACCOUNT_ADDRESS"* ]] + [[ "$output" != *"$FIRST_ADMIN_ADDRESS"* ]] +} + diff --git a/tests/user/appointee/appointee_lifecycle_tests.sh b/tests/user/appointee/appointee_lifecycle_tests.sh new file mode 100755 index 00000000..76057002 --- /dev/null +++ b/tests/user/appointee/appointee_lifecycle_tests.sh @@ -0,0 +1,332 @@ +#!/usr/bin/env bats + +verify_listed_permissions() { + local selector_1=$1 + local selector_2=$2 + local appointee=$3 + local account=$4 + local target=$5 + local output=$6 + + local selector_1_no_prefix=${selector_1#0x} + local selector_2_no_prefix=${selector_2#0x} + + [ "$status" -eq 0 ] + [[ "$output" == *"Appointee address: $appointee"* ]] + [[ "$output" == *"Appointed by: $account"* ]] + [[ "$output" == *"Target: $target, Selector: $selector_1_no_prefix"* ]] + [[ "$output" == *"Target: $target, Selector: $selector_2_no_prefix"* ]] +} + +@test "Add first appointee for selector 1" { + run $CLI_PATH user appointee set \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$APPOINTEE_1" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Add second appointee for selector 1" { + run $CLI_PATH user appointee set \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$APPOINTEE_2" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Add first appointee for selector 2" { + run $CLI_PATH user appointee set \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$APPOINTEE_1" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_2" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Add second appointee for selector 2" { + run $CLI_PATH user appointee set \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$APPOINTEE_2" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_2" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Verify multiple appointees for selector 1" { + run $CLI_PATH user appointee list \ + --account-address "$ACCOUNT_ADDRESS" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" == *"$APPOINTEE_1"* ]] + [[ "$output" == *"$APPOINTEE_2"* ]] +} + +@test "Verify multiple appointees for selector 2" { + run $CLI_PATH user appointee list \ + --account-address "$ACCOUNT_ADDRESS" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_2" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" == *"$APPOINTEE_1"* ]] + [[ "$output" == *"$APPOINTEE_2"* ]] +} + +@test "Verify appointee1's listed permissions" { + run $CLI_PATH user appointee list-permissions \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$APPOINTEE_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + SELECTOR_1_NO_PREFIX=${SELECTOR_1#0x} + SELECTOR_2_NO_PREFIX=${SELECTOR_2#0x} + verify_listed_permissions \ + "$SELECTOR_1_NO_PREFIX" \ + "$SELECTOR_2_NO_PREFIX" \ + "$APPOINTEE_1" \ + "$ACCOUNT_ADDRESS" \ + "$TARGET_ADDRESS" \ + "$output" +} + +@test "Verify appointee2's listed permissions" { + run $CLI_PATH user appointee list-permissions \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$APPOINTEE_2" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + SELECTOR_1_NO_PREFIX=${SELECTOR_1#0x} + SELECTOR_2_NO_PREFIX=${SELECTOR_2#0x} + verify_listed_permissions \ + "$SELECTOR_1_NO_PREFIX" \ + "$SELECTOR_2_NO_PREFIX" \ + "$APPOINTEE_2" \ + "$ACCOUNT_ADDRESS" \ + "$TARGET_ADDRESS" \ + "$output" +} + +@test "Test canCall permissions for selectors is true" { + run $CLI_PATH user appointee can-call \ + --appointee-address "$APPOINTEE_1" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" == *"CanCall Result: true"* ]] + + run $CLI_PATH user appointee can-call \ + --appointee-address "$APPOINTEE_1" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" == *"CanCall Result: true"* ]] + + run $CLI_PATH user appointee can-call \ + --appointee-address "$APPOINTEE_2" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" == *"CanCall Result: true"* ]] + + run $CLI_PATH user appointee can-call \ + --appointee-address "$APPOINTEE_2" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_2" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" == *"CanCall Result: true"* ]] +} + +@test "Remove first appointee for selector 1" { + run $CLI_PATH user appointee remove \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$APPOINTEE_1" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Verify only second appointee remains for selector 1" { + run $CLI_PATH user appointee list \ + --account-address "$ACCOUNT_ADDRESS" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" != *"$APPOINTEE_1"* ]] + [[ "$output" == *"$APPOINTEE_2"* ]] +} + +@test "Remove second appointee for selector 1" { + run $CLI_PATH user appointee remove \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$APPOINTEE_2" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Verify list is empty after selector 1 removals" { + run $CLI_PATH user appointee list \ + --account-address "$ACCOUNT_ADDRESS" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" != *"$APPOINTEE_1"* ]] + [[ "$output" != *"$APPOINTEE_2"* ]] +} + +@test "Remove first appointee for selector 2" { + run $CLI_PATH user appointee remove \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$APPOINTEE_1" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_2" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Verify only second appointee remains for selector 2" { + run $CLI_PATH user appointee list \ + --account-address "$ACCOUNT_ADDRESS" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_2" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" != *"$APPOINTEE_1"* ]] + [[ "$output" == *"$APPOINTEE_2"* ]] +} + +@test "Remove second appointee for selector 2" { + run $CLI_PATH user appointee remove \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$APPOINTEE_2" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_2" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Verify list is empty after selector 2 removals" { + run $CLI_PATH user appointee list \ + --account-address "$ACCOUNT_ADDRESS" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_2" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" != *"$APPOINTEE_1"* ]] + [[ "$output" != *"$APPOINTEE_2"* ]] +} + +@test "Test canCall permissions for selectors" { + run $CLI_PATH user appointee can-call \ + --appointee-address "$APPOINTEE_1" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" == *"CanCall Result: false"* ]] + + run $CLI_PATH user appointee can-call \ + --appointee-address "$APPOINTEE_2" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_2" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" == *"CanCall Result: false"* ]] +} + diff --git a/tests/user/appointee/appointee_output_tests.sh b/tests/user/appointee/appointee_output_tests.sh new file mode 100755 index 00000000..d77d431f --- /dev/null +++ b/tests/user/appointee/appointee_output_tests.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bats + +setup() { + echo "Setting up test environment..." + rm -f "$OUTPUT_FILE_FOLDER/output_*.txt" +} + +teardown() { + echo "Cleaning up test environment..." + rm -f "$OUTPUT_FILE_FOLDER/output_*.txt" +} + +@test "Verify canCall permissions for account" { + run $CLI_PATH user appointee can-call \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$ACCOUNT_ADDRESS" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" == *"CanCall Result: true"* ]] +} + +@test "Set appointee and verify calldata output (calldata)" { + run $CLI_PATH user appointee set \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$APPOINTEE_ADDRESS" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$PRIVATE_KEY" \ + --output-type "calldata" \ + --output-file "$OUTPUT_FILE_FOLDER/output_set.txt" + + [ "$status" -eq 0 ] + [[ -s "$OUTPUT_FILE_FOLDER/output_set.txt" ]] +} + +@test "Broadcast set appointee command (broadcast)" { + run $CLI_PATH user appointee set \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$APPOINTEE_ADDRESS" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Remove appointee and verify calldata output (calldata)" { + run $CLI_PATH user appointee remove \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$APPOINTEE_ADDRESS" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$PRIVATE_KEY" \ + --output-type "calldata" \ + --output-file "$OUTPUT_FILE_FOLDER/output_remove.txt" + + [ "$status" -eq 0 ] + [[ -s "$OUTPUT_FILE_FOLDER/output_remove.txt" ]] +} + +@test "Broadcast remove appointee command (broadcast)" { + run $CLI_PATH user appointee remove \ + --account-address "$ACCOUNT_ADDRESS" \ + --appointee-address "$APPOINTEE_ADDRESS" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" \ + --ecdsa-private-key "$PRIVATE_KEY" \ + --broadcast + + [ "$status" -eq 0 ] +} + +@test "Verify appointee removal" { + run $CLI_PATH user appointee list \ + --account-address "$ACCOUNT_ADDRESS" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" != *"$APPOINTEE_ADDRESS"* ]] +} + +@test "Verify canCall permissions for removed appointee" { + run $CLI_PATH user appointee can-call \ + --appointee-address "$APPOINTEE_ADDRESS" \ + --target-address "$TARGET_ADDRESS" \ + --selector "$SELECTOR_1" \ + --permission-controller-address "$PERMISSION_CONTROLLER_ADDRESS" \ + --eth-rpc-url "$RPC_URL" \ + --network "$NETWORK" + + [ "$status" -eq 0 ] + [[ "$output" == *"CanCall Result: false"* ]] +} diff --git a/tests/user/user-integration-tests-runner.sh b/tests/user/user-integration-tests-runner.sh new file mode 100755 index 00000000..941ed043 --- /dev/null +++ b/tests/user/user-integration-tests-runner.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +echo "Starting User command integration tests." + +export $(cat .env | xargs) + +any_test_failures=0 + +run_bats_test() { + local test_file=$1 + bats "$test_file" + if [ $? -ne 0 ]; then + any_test_failures=1 + fi +} + +run_bats_test "admin/rotate_admin_tests.sh" +run_bats_test "admin/admin_lifecycle_tests.sh" +run_bats_test "appointee/appointee_lifecycle_tests.sh" +run_bats_test "appointee/appointee_output_tests.sh" + +rm -rf output/ + +if [ $any_test_failures -eq 0 ]; then + echo "All tests passed." +else + echo "Some tests failed." +fi + +exit $any_test_failures \ No newline at end of file