Skip to content

Commit

Permalink
Merge branch 'dev' into RewardsCoordinatorStorage
Browse files Browse the repository at this point in the history
  • Loading branch information
MegaRedHand authored Feb 14, 2025
2 parents b921cbd + b51e914 commit 710655a
Show file tree
Hide file tree
Showing 2 changed files with 329 additions and 0 deletions.
194 changes: 194 additions & 0 deletions chainio/clients/avsregistry/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"context"
"crypto/ecdsa"
"crypto/rand"
"encoding/hex"
"math/big"
"strings"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
gethcommon "github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -269,6 +271,168 @@ func (w *ChainWriter) UpdateStakesOfEntireOperatorSetForQuorums(

}

// Registers an operator while replacing existing operators in full quorums. If any quorum reaches its maximum
// operator capacity, `operatorKickParams` is used to replace an old operator with the new one.
func (w *ChainWriter) RegisterOperatorWithChurn(
ctx context.Context,
operatorEcdsaPrivateKey *ecdsa.PrivateKey,
churnApprovalEcdsaPrivateKey *ecdsa.PrivateKey,
blsKeyPair *bls.KeyPair,
quorumNumbers types.QuorumNums,
quorumNumbersToKick types.QuorumNums,
operatorsToKick []gethcommon.Address,
socket string,
waitForReceipt bool,
) (*gethtypes.Receipt, error) {
operatorAddr := crypto.PubkeyToAddress(operatorEcdsaPrivateKey.PublicKey)
g1HashedMsgToSign, err := w.registryCoordinator.PubkeyRegistrationMessageHash(&bind.CallOpts{}, operatorAddr)
if err != nil {
return nil, err
}
signedMsg := chainioutils.ConvertToBN254G1Point(
blsKeyPair.SignHashedToCurveMessage(chainioutils.ConvertBn254GethToGnark(g1HashedMsgToSign)).G1Point,
)
G1pubkeyBN254 := chainioutils.ConvertToBN254G1Point(blsKeyPair.GetPubKeyG1())
G2pubkeyBN254 := chainioutils.ConvertToBN254G2Point(blsKeyPair.GetPubKeyG2())
pubkeyRegParams := regcoord.IBLSApkRegistryTypesPubkeyRegistrationParams{
PubkeyRegistrationSignature: signedMsg,
PubkeyG1: G1pubkeyBN254,
PubkeyG2: G2pubkeyBN254,
}

// generate a random salt and 1 hour expiry for the signature
var signatureSalt [32]byte
_, err = rand.Read(signatureSalt[:])
if err != nil {
return nil, err
}

curBlockNum, err := w.ethClient.BlockNumber(context.Background())
if err != nil {
return nil, err
}
curBlock, err := w.ethClient.BlockByNumber(context.Background(), new(big.Int).SetUint64(curBlockNum))
if err != nil {
return nil, err
}
sigValidForSeconds := int64(60 * 60) // 1 hour

curTime := new(big.Int).SetUint64(curBlock.Time())
signatureExpiry := new(big.Int).Add(curTime, big.NewInt(sigValidForSeconds))

// params to register operator in delegation manager's operator-avs mapping
msgToSign, err := w.elReader.CalculateOperatorAVSRegistrationDigestHash(
ctx,
operatorAddr,
w.serviceManagerAddr,
signatureSalt,
signatureExpiry,
)
if err != nil {
return nil, err
}
operatorSignature, err := crypto.Sign(msgToSign[:], operatorEcdsaPrivateKey)
if err != nil {
return nil, err
}
// the crypto library is low level and deals with 0/1 v values, whereas ethereum expects 27/28, so we add 27
// see https://github.com/ethereum/go-ethereum/issues/28757#issuecomment-1874525854
// and https://twitter.com/pcaversaccio/status/1671488928262529031
operatorSignature[64] += 27
operatorSignatureWithSaltAndExpiry := regcoord.ISignatureUtilsSignatureWithSaltAndExpiry{
Signature: operatorSignature,
Salt: signatureSalt,
Expiry: signatureExpiry,
}

var operatorKickParams []regcoord.ISlashingRegistryCoordinatorTypesOperatorKickParam
for i, operatorToKick := range operatorsToKick {
operatorKickParams = append(operatorKickParams, regcoord.ISlashingRegistryCoordinatorTypesOperatorKickParam{
Operator: operatorToKick,
QuorumNumber: quorumNumbersToKick[i].UnderlyingType(),
})
}
var churnSignatureSalt [32]byte
_, err = rand.Read(churnSignatureSalt[:])
if err != nil {
return nil, err
}

var operatorIdBytes [32]byte

// `GetOperatorID()` returns the operator ID with `0x` prefix.
// We need to remove the prefix and decode the hex string to bytes.
operatorId := blsKeyPair.GetPubKeyG1().GetOperatorID()
operatorIdNoPrefix := strings.TrimPrefix(operatorId, "0x")
operatorIdBytesDecoded, err := hex.DecodeString(operatorIdNoPrefix)
if err != nil {
return nil, err
}
copy(operatorIdBytes[:], operatorIdBytesDecoded)

churnMsgToSign, err := w.registryCoordinator.CalculateOperatorChurnApprovalDigestHash(
&bind.CallOpts{Context: ctx},
operatorAddr,
operatorIdBytes,
operatorKickParams,
churnSignatureSalt,
signatureExpiry,
)
if err != nil {
return nil, err
}

churnApprovalSignature, err := crypto.Sign(churnMsgToSign[:], churnApprovalEcdsaPrivateKey)
if err != nil {
return nil, err
}

// the crypto library is low level and deals with 0/1 v values, whereas ethereum expects 27/28, so we add 27
// see https://github.com/ethereum/go-ethereum/issues/28757#issuecomment-1874525854
// and https://twitter.com/pcaversaccio/status/1671488928262529031
churnApprovalSignature[64] += 27
churnApproverSignatureWithSaltAndExpiry := regcoord.ISignatureUtilsSignatureWithSaltAndExpiry{
Signature: churnApprovalSignature,
Salt: churnSignatureSalt,
Expiry: signatureExpiry,
}

noSendTxOpts, err := w.txMgr.GetNoSendTxOpts()
if err != nil {
return nil, err
}

tx, err := w.registryCoordinator.RegisterOperatorWithChurn(
noSendTxOpts,
quorumNumbers.UnderlyingType(),
socket,
pubkeyRegParams,
operatorKickParams,
churnApproverSignatureWithSaltAndExpiry,
operatorSignatureWithSaltAndExpiry,
)

if err != nil {
return nil, err
}
receipt, err := w.txMgr.Send(ctx, tx, waitForReceipt)
if err != nil {
return nil, utils.WrapError("failed to send tx with err", err.Error())
}
w.logger.Info(
"successfully registered operator with AVS registry coordinator",
"txHash",
receipt.TxHash.String(),
"avs-service-manager",
w.serviceManagerAddr,
"operator",
operatorAddr,
"quorumNumbers",
quorumNumbers,
)
return receipt, nil
}

// Updates the stakes of a the given `operators` for all the quorums.
// On success, returns the receipt of the transaction.
func (w *ChainWriter) UpdateStakesOfOperatorSubsetForAllQuorums(
Expand Down Expand Up @@ -598,6 +762,36 @@ func (w *ChainWriter) SetEjector(
return receipt, nil
}

// Modifies the multiplier of existing strategies for the given quorum number.
func (w *ChainWriter) ModifyStrategyParams(
ctx context.Context,
quorumNumber types.QuorumNum,
strategyIndices []*big.Int,
multipliers []*big.Int,
waitForReceipt bool,
) (*gethtypes.Receipt, error) {
w.logger.Info("modifying strategy params for quorum ", quorumNumber)

noSendTxOpts, err := w.txMgr.GetNoSendTxOpts()
if err != nil {
return nil, err
}
tx, err := w.stakeRegistry.ModifyStrategyParams(
noSendTxOpts,
quorumNumber.UnderlyingType(),
strategyIndices,
multipliers,
)
if err != nil {
return nil, err
}
receipt, err := w.txMgr.Send(ctx, tx, waitForReceipt)
if err != nil {
return nil, utils.WrapError("failed to send ModifyStrategyParams tx with err", err)
}
return receipt, nil
}

// Sets the accountIdentifier as the address received as parameter. Identifier should only be set once, since
// changing it could break existing operator sets. Returns the receipt of the transaction in case of success.
func (w *ChainWriter) SetAccountIdentifier(
Expand Down
135 changes: 135 additions & 0 deletions chainio/clients/avsregistry/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,124 @@ func TestWriterMethods(t *testing.T) {
})
}

func TestRegisterOperatorWithChurn(t *testing.T) {
clients, anvilHttpEndpoint := testclients.BuildTestClients(t)

contractAddrs := testutils.GetContractAddressesFromContractRegistry(anvilHttpEndpoint)

chainWriter := clients.AvsRegistryChainWriter
chainReader := clients.AvsRegistryChainReader

firstOperatorAddress := gethcommon.HexToAddress(testutils.ANVIL_FIRST_ADDRESS)
firstOperatorECDSAPrivateKey, err := crypto.HexToECDSA(testutils.ANVIL_FIRST_PRIVATE_KEY)
require.NoError(t, err)
firstOperatorKeyPair, err := bls.NewKeyPairFromString("0x01")
require.NoError(t, err)

quorumNumbers := types.QuorumNums{0}

ethHttpClient := clients.EthHttpClient

registryCoordinatorContract, err := regcoord.NewContractRegistryCoordinator(
contractAddrs.RegistryCoordinator,
ethHttpClient,
)
require.NoError(t, err)

// At first, churnApprover is ANVIL_FIRST_ADDRESS
approver, err := registryCoordinatorContract.ChurnApprover(&bind.CallOpts{})
require.NoError(t, err)
assert.Equal(t, approver.String(), testutils.ANVIL_FIRST_ADDRESS)

// Set ANVIL_SECOND_ADDRESS as the new churnApprover
churnApproverAddress := gethcommon.HexToAddress(testutils.ANVIL_SECOND_ADDRESS)
churnECDSAPrivateKey, err := crypto.HexToECDSA(testutils.ANVIL_SECOND_PRIVATE_KEY)
require.NoError(t, err)

receipt, err := chainWriter.SetChurnApprover(context.Background(), churnApproverAddress, true)
require.NoError(t, err)
require.Equal(t, receipt.Status, gethtypes.ReceiptStatusSuccessful)

// After change, churnApprover is ANVIL_SECOND_ADDRESS
newApprover, err := registryCoordinatorContract.ChurnApprover(&bind.CallOpts{})
require.NoError(t, err)
assert.Equal(t, newApprover.String(), testutils.ANVIL_SECOND_ADDRESS)

//Register ANVIL_FIRST_ADDRESS as operator
receipt, err = chainWriter.RegisterOperator(
context.Background(),
firstOperatorECDSAPrivateKey,
firstOperatorKeyPair,
quorumNumbers,
"",
true,
)
require.NoError(t, err)
require.NotNil(t, receipt)

// Change the OperatorSetParams to allow only 1 operator
receipt, err = chainWriter.SetOperatorSetParams(
context.Background(),
0,
regcoord.ISlashingRegistryCoordinatorTypesOperatorSetParam{
MaxOperatorCount: 1,
KickBIPsOfOperatorStake: 10,
KickBIPsOfTotalStake: 10000,
},
true,
)
require.NoError(t, err)
require.NotNil(t, receipt)

// We want to kick the first operator
operatorsToKick := []gethcommon.Address{firstOperatorAddress}

thirdOperatorAddress := gethcommon.HexToAddress(testutils.ANVIL_THIRD_ADDRESS)
thirdOperatorECDSAPrivateKey, err := crypto.HexToECDSA(testutils.ANVIL_THIRD_PRIVATE_KEY)
require.NoError(t, err)
thirdOperatorPrivateKey := testutils.ANVIL_THIRD_PRIVATE_KEY
newKeyPair, err := bls.NewKeyPairFromString("0x03")
require.NoError(t, err)

config := avsregistry.Config{
RegistryCoordinatorAddress: contractAddrs.RegistryCoordinator,
OperatorStateRetrieverAddress: contractAddrs.OperatorStateRetriever,
ServiceManagerAddress: contractAddrs.ServiceManager,
}
chainWriter3, err := testclients.NewTestAvsRegistryWriterFromConfig(
anvilHttpEndpoint,
thirdOperatorPrivateKey,
config,
)
require.NoError(t, err)

// Register ANVIL_THIRD_ADDRESS as operator. Since there is only one slot available, ANVIL_FIRST_ADDRESS should be
// kicked
receipt, err = chainWriter3.RegisterOperatorWithChurn(
context.Background(),
thirdOperatorECDSAPrivateKey,
churnECDSAPrivateKey,
newKeyPair,
quorumNumbers,
quorumNumbers,
operatorsToKick,
"",
true,
)
require.NoError(t, err)
require.NotNil(t, receipt)

// ANVIL_FIRST_ADDRESS should be deregistered
deregisteredOperatorWithChurn, err := chainReader.IsOperatorRegistered(&bind.CallOpts{}, firstOperatorAddress)
require.NoError(t, err)
require.False(t, deregisteredOperatorWithChurn)

// ANVIL_THIRD_ADDRESS should be registered
registeredOperatorWithChurn, err := chainReader.IsOperatorRegistered(&bind.CallOpts{}, thirdOperatorAddress)
require.NoError(t, err)
require.True(t, registeredOperatorWithChurn)
}

// Compliance test for BLS signature
func TestBlsSignature(t *testing.T) {
// read input from JSON if available, otherwise use default values
Expand Down Expand Up @@ -719,6 +837,23 @@ func TestCreateAVSRewardsSubmission(t *testing.T) {
require.Equal(t, gethtypes.ReceiptStatusSuccessful, receipt.Status)
}

func TestModifyStrategyParams(t *testing.T) {
clients, _ := testclients.BuildTestClients(t)
chainWriter := clients.AvsRegistryChainWriter
chainReader := clients.AvsRegistryChainReader

indices := []*big.Int{big.NewInt(0)}
multiplier := []*big.Int{big.NewInt(5e18)}

receipt, err := chainWriter.ModifyStrategyParams(context.Background(), 0, indices, multiplier, true)
require.NoError(t, err)
require.Equal(t, gethtypes.ReceiptStatusSuccessful, receipt.Status)

strategies, err := chainReader.StrategyParamsByIndex(nil, 0, indices[0])
require.NoError(t, err)
require.Equal(t, strategies.Multiplier, multiplier[0])
}

func TestCreateOperatorDirectedAVSRewardsSubmission(t *testing.T) {
clients, _ := testclients.BuildTestClients(t)
chainWriter := clients.AvsRegistryChainWriter
Expand Down

0 comments on commit 710655a

Please sign in to comment.