Skip to content

Commit 4af04b1

Browse files
authored
Add operator delegate-to command (#233)
1 parent 39cf782 commit 4af04b1

File tree

4 files changed

+230
-0
lines changed

4 files changed

+230
-0
lines changed

pkg/internal/common/flags/general.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,12 @@ var (
8282
Usage: "Suppress unnecessary output",
8383
EnvVars: []string{"SILENT"},
8484
}
85+
86+
ExpiryFlag = cli.Int64Flag{
87+
Name: "expiry",
88+
Aliases: []string{"e"},
89+
Usage: "expiry in seconds",
90+
EnvVars: []string{"EXPIRY"},
91+
Value: 3600,
92+
}
8593
)

pkg/internal/common/helper.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package common
22

33
import (
44
"context"
5+
"crypto/ecdsa"
56
"encoding/json"
67
"errors"
78
"fmt"
@@ -28,6 +29,7 @@ import (
2829
eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils"
2930

3031
"github.com/ethereum/go-ethereum/accounts/abi/bind"
32+
"github.com/ethereum/go-ethereum/accounts/keystore"
3133
"github.com/ethereum/go-ethereum/common"
3234
gethtypes "github.com/ethereum/go-ethereum/core/types"
3335
"github.com/ethereum/go-ethereum/crypto"
@@ -124,6 +126,7 @@ func getWallet(
124126
if err != nil {
125127
return nil, common.Address{}, err
126128
}
129+
127130
return keyWallet, sender, nil
128131
} else if cfg.SignerType == types.FireBlocksSigner {
129132
var secretKey string
@@ -479,3 +482,54 @@ func GetNoSendTxOpts(from common.Address) *bind.TransactOpts {
479482
func Trim0x(s string) string {
480483
return strings.TrimPrefix(s, "0x")
481484
}
485+
486+
func Sign(digest []byte, cfg types.SignerConfig, p utils.Prompter) ([]byte, error) {
487+
var privateKey *ecdsa.PrivateKey
488+
489+
if cfg.SignerType == types.LocalKeystoreSigner {
490+
ecdsaPassword, readFromPipe := utils.GetStdInPassword()
491+
var err error
492+
if !readFromPipe {
493+
ecdsaPassword, err = p.InputHiddenString("Enter password to decrypt the ecdsa private key:", "",
494+
func(password string) error {
495+
return nil
496+
},
497+
)
498+
if err != nil {
499+
fmt.Println("Error while reading ecdsa key password")
500+
return nil, err
501+
}
502+
}
503+
504+
jsonContent, err := os.ReadFile(cfg.PrivateKeyStorePath)
505+
if err != nil {
506+
return nil, err
507+
}
508+
key, err := keystore.DecryptKey(jsonContent, ecdsaPassword)
509+
if err != nil {
510+
return nil, err
511+
}
512+
513+
privateKey = key.PrivateKey
514+
} else if cfg.SignerType == types.FireBlocksSigner {
515+
return nil, errors.New("FireBlocksSigner is not implemented")
516+
} else if cfg.SignerType == types.Web3Signer {
517+
return nil, errors.New("Web3Signer is not implemented")
518+
} else if cfg.SignerType == types.PrivateKeySigner {
519+
privateKey = cfg.PrivateKey
520+
} else {
521+
return nil, errors.New("signer is not implemented")
522+
}
523+
524+
signed, err := crypto.Sign(digest, privateKey)
525+
if err != nil {
526+
return nil, err
527+
}
528+
529+
// account for EIP-155 by incrementing V if necessary
530+
if signed[crypto.RecoveryIDOffset] < 27 {
531+
signed[crypto.RecoveryIDOffset] += 27
532+
}
533+
534+
return signed, nil
535+
}

pkg/operator.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func OperatorCmd(p utils.Prompter) *cli.Command {
1717
operator.StatusCmd(p),
1818
operator.UpdateCmd(p),
1919
operator.UpdateMetadataURICmd(p),
20+
operator.GetApprovalCmd(p),
2021
},
2122
}
2223

pkg/operator/get_approval.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package operator
2+
3+
import (
4+
"context"
5+
"crypto/rand"
6+
"encoding/hex"
7+
"fmt"
8+
"math/big"
9+
"time"
10+
11+
"github.com/Layr-Labs/eigenlayer-cli/pkg/operator/keys"
12+
eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils"
13+
14+
"github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common"
15+
"github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags"
16+
"github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry"
17+
"github.com/Layr-Labs/eigenlayer-cli/pkg/utils"
18+
19+
"github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts"
20+
elContracts "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts"
21+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
22+
gethcommon "github.com/ethereum/go-ethereum/common"
23+
"github.com/ethereum/go-ethereum/ethclient"
24+
25+
"github.com/urfave/cli/v2"
26+
)
27+
28+
func GetApprovalCmd(p utils.Prompter) *cli.Command {
29+
getApprovalCmd := &cli.Command{
30+
Name: "get-approval",
31+
Usage: "Generate the smart contract approval details for the delegateTo method",
32+
UsageText: "get-approval <configuration-file> <staker-address>",
33+
Description: `
34+
Generate the smart contract approval details for the delegateTo method.
35+
36+
It expects the same configuration yaml file as an argument to the register command, along with the staker address.
37+
38+
Use the --expiry flag to override the default expiration of 3600 seconds.
39+
`,
40+
After: telemetry.AfterRunAction(),
41+
Flags: []cli.Flag{
42+
&flags.VerboseFlag,
43+
&flags.ExpiryFlag,
44+
},
45+
Action: func(cCtx *cli.Context) error {
46+
logger := common.GetLogger(cCtx)
47+
args := cCtx.Args()
48+
if args.Len() != 2 {
49+
return fmt.Errorf("%w: accepts 2 arg, received %d", keys.ErrInvalidNumberOfArgs, args.Len())
50+
}
51+
52+
expirySeconds := cCtx.Int64(flags.ExpiryFlag.Name)
53+
54+
configurationFilePath := args.Get(0)
55+
stakerAddress := args.Get(1)
56+
57+
if !eigenSdkUtils.IsValidEthereumAddress(stakerAddress) {
58+
return fmt.Errorf("staker address %s is not valid address", stakerAddress)
59+
}
60+
61+
operatorCfg, err := common.ReadConfigFile(configurationFilePath)
62+
if err != nil {
63+
return err
64+
}
65+
cCtx.App.Metadata["network"] = operatorCfg.ChainId.String()
66+
67+
logger.Infof(
68+
"%s Operator configuration file read successfully %s",
69+
utils.EmojiCheckMark,
70+
operatorCfg.Operator.Address,
71+
)
72+
logger.Info("%s validating operator config: %s", utils.EmojiWait, operatorCfg.Operator.Address)
73+
74+
ethClient, err := ethclient.Dial(operatorCfg.EthRPCUrl)
75+
if err != nil {
76+
return err
77+
}
78+
id, err := ethClient.ChainID(context.Background())
79+
if err != nil {
80+
return err
81+
}
82+
83+
if id.Cmp(&operatorCfg.ChainId) != 0 {
84+
return fmt.Errorf(
85+
"%w: chain ID in config file %d does not match the chain ID of the network %d",
86+
ErrInvalidYamlFile,
87+
&operatorCfg.ChainId,
88+
id,
89+
)
90+
}
91+
92+
logger.Infof(
93+
"%s Operator configuration file validated successfully %s",
94+
utils.EmojiCheckMark,
95+
operatorCfg.Operator.Address,
96+
)
97+
98+
contractCfg := elcontracts.Config{
99+
DelegationManagerAddress: gethcommon.HexToAddress(operatorCfg.ELDelegationManagerAddress),
100+
AvsDirectoryAddress: gethcommon.HexToAddress(operatorCfg.ELAVSDirectoryAddress),
101+
}
102+
reader, err := elContracts.NewReaderFromConfig(
103+
contractCfg,
104+
ethClient,
105+
logger,
106+
)
107+
if err != nil {
108+
return err
109+
}
110+
111+
var staker = gethcommon.HexToAddress(stakerAddress)
112+
var operator = gethcommon.HexToAddress(operatorCfg.Operator.Address)
113+
var delegationApprover = gethcommon.HexToAddress(operatorCfg.Operator.DelegationApproverAddress)
114+
salt := make([]byte, 32)
115+
116+
if _, err := rand.Read(salt); err != nil {
117+
return err
118+
}
119+
var expiry = new(big.Int).SetInt64(time.Now().Unix() + expirySeconds)
120+
121+
callOpts := &bind.CallOpts{Context: context.Background()}
122+
123+
hash, err := reader.CalculateDelegationApprovalDigestHash(
124+
callOpts,
125+
staker,
126+
operator,
127+
delegationApprover,
128+
[32]byte(salt),
129+
expiry,
130+
)
131+
if err != nil {
132+
return err
133+
}
134+
135+
signed, err := common.Sign(hash[:], operatorCfg.SignerConfig, p)
136+
if err != nil {
137+
return err
138+
}
139+
140+
fmt.Println()
141+
fmt.Println("--------------------------- delegateTo for the staker ---------------------------")
142+
fmt.Println()
143+
fmt.Printf("operator: %s\n", operator)
144+
fmt.Printf("approverSignatureAndExpiry.signature: %s\n", eigenSdkUtils.Add0x(hex.EncodeToString(signed)))
145+
fmt.Printf("approverSignatureAndExpiry.expiry: %d\n", expiry)
146+
fmt.Printf("approverSalt: %s\n", eigenSdkUtils.Add0x(hex.EncodeToString(salt)))
147+
fmt.Println()
148+
fmt.Println(
149+
"--------------------------- CalculateDelegationApprovalDigestHash details ---------------------------",
150+
)
151+
fmt.Println()
152+
fmt.Printf("staker: %s\n", staker)
153+
fmt.Printf("operator: %s\n", operator)
154+
fmt.Printf("_delegationApprover: %s\n", delegationApprover)
155+
fmt.Printf("approverSalt: %s\n", eigenSdkUtils.Add0x(hex.EncodeToString(salt)))
156+
fmt.Printf("expiry: %d\n", expiry)
157+
fmt.Println()
158+
fmt.Printf("result: %s\n", eigenSdkUtils.Add0x(hex.EncodeToString(hash[:])))
159+
fmt.Println()
160+
fmt.Println("------------------------------------------------------------------------")
161+
fmt.Println()
162+
163+
return nil
164+
},
165+
}
166+
return getApprovalCmd
167+
}

0 commit comments

Comments
 (0)