Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions bindings/megapool/megapool-contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import (
)

type SlotProof struct {
Slot uint64
Witnesses [][32]byte
Slot uint64 `json:"slot"`
Witnesses [][32]byte `json:"witnesses"`
}

type ValidatorProof struct {
ValidatorIndex *big.Int
Validator ProvedValidator
Witnesses [][32]byte
ValidatorIndex *big.Int `json:"validatorIndex"`
Validator ProvedValidator `json:"validator"`
Witnesses [][32]byte `json:"witnesses"`
}

type ProvedValidator struct {
Expand Down
53 changes: 31 additions & 22 deletions bindings/megapool/megapool-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,22 @@ type ExitChallenge struct {
}

type WithdrawalProof struct {
Slot uint64 `json:"slot"`
WithdrawalSlot uint64 `json:"withdrawalSlot"`
WithdrawalNum uint16 `json:"withdrawalNum"`
Withdrawal Withdrawal `json:"withdrawal"`
Witnesses [][32]byte `json:"witnesses"`
}

type NextWithdrawalIndexProof struct {
NextWithdrawalIndex uint64 `json:"nextWithdrawalIndex"`
Witnesses [][32]byte `json:"witnesses"`
}

type ValidatorBalanceProof struct {
BalanceChunk [32]byte `json:"balanceChunk"`
Witnesses [][32]byte `json:"witnesses"`
}

func GetValidatorCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint32, error) {
megapoolManager, err := getRocketMegapoolManager(rp, opts)
if err != nil {
Expand Down Expand Up @@ -111,65 +120,65 @@ func GetValidatorInfo(rp *rocketpool.RocketPool, index uint32, opts *bind.CallOp
}

// Estimate the gas of Stake
func EstimateStakeGas(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof ValidatorProof, slotProof SlotProof, opts *bind.TransactOpts) (gaslimit.Limits, error) {
func EstimateStakeGas(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, proofVersion *big.Int, proofData []byte, opts *bind.TransactOpts) (gaslimit.Limits, error) {
megapoolManager, err := getRocketMegapoolManager(rp, nil)
if err != nil {
return gaslimit.Limits{}, err
}
return megapoolManager.GetTransactionGasInfo(opts, "stake", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof)
return megapoolManager.GetTransactionGasInfo(opts, "stake", megapoolAddress, validatorId, slotTimestamp, proofVersion, proofData)
}

// Progress the prelaunch megapool to staking
func Stake(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof ValidatorProof, slotProof SlotProof, opts *bind.TransactOpts) (*types.Transaction, error) {
func Stake(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, proofVersion *big.Int, proofData []byte, opts *bind.TransactOpts) (*types.Transaction, error) {
megapoolManager, err := getRocketMegapoolManager(rp, nil)
if err != nil {
return nil, err
}
tx, err := megapoolManager.Transact(opts, "stake", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof)
tx, err := megapoolManager.Transact(opts, "stake", megapoolAddress, validatorId, slotTimestamp, proofVersion, proofData)
if err != nil {
return nil, fmt.Errorf("error staking megapool %s: %w", megapoolAddress, err)
}
return tx, nil
}

// Estimate the gas to call NotifyExit
func EstimateNotifyExitGas(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof ValidatorProof, slotProof SlotProof, opts *bind.TransactOpts) (gaslimit.Limits, error) {
func EstimateNotifyExitGas(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, proofVersion *big.Int, proofData []byte, opts *bind.TransactOpts) (gaslimit.Limits, error) {
megapoolManager, err := getRocketMegapoolManager(rp, nil)
if err != nil {
return gaslimit.Limits{}, err
}
return megapoolManager.GetTransactionGasInfo(opts, "notifyExit", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof)
return megapoolManager.GetTransactionGasInfo(opts, "notifyExit", megapoolAddress, validatorId, slotTimestamp, proofVersion, proofData)
}

// Notify the megapool that one of its validators is exiting
func NotifyExit(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof ValidatorProof, slotProof SlotProof, opts *bind.TransactOpts) (*types.Transaction, error) {
func NotifyExit(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, proofVersion *big.Int, proofData []byte, opts *bind.TransactOpts) (*types.Transaction, error) {
megapoolManager, err := getRocketMegapoolManager(rp, nil)
if err != nil {
return nil, err
}
tx, err := megapoolManager.Transact(opts, "notifyExit", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof)
tx, err := megapoolManager.Transact(opts, "notifyExit", megapoolAddress, validatorId, slotTimestamp, proofVersion, proofData)
if err != nil {
return nil, fmt.Errorf("error calling notify exit: %w", err)
}
return tx, nil
}

// Estimate the gas to call NotifyNotExit
func EstimateNotifyNotExitGas(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof ValidatorProof, slotProof SlotProof, opts *bind.TransactOpts) (gaslimit.Limits, error) {
func EstimateNotifyNotExitGas(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, proofVersion *big.Int, proofData []byte, opts *bind.TransactOpts) (gaslimit.Limits, error) {
megapoolManager, err := getRocketMegapoolManager(rp, nil)
if err != nil {
return gaslimit.Limits{}, err
}
return megapoolManager.GetTransactionGasInfo(opts, "notifyNotExit", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof)
return megapoolManager.GetTransactionGasInfo(opts, "notifyNotExit", megapoolAddress, validatorId, slotTimestamp, proofVersion, proofData)
}

// Used to prove a validator is not exiting after a challenge-exit
func NotifyNotExit(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof ValidatorProof, slotProof SlotProof, opts *bind.TransactOpts) (*types.Transaction, error) {
func NotifyNotExit(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, proofVersion *big.Int, proofData []byte, opts *bind.TransactOpts) (*types.Transaction, error) {
megapoolManager, err := getRocketMegapoolManager(rp, nil)
if err != nil {
return nil, err
}
tx, err := megapoolManager.Transact(opts, "notifyNotExit", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof)
tx, err := megapoolManager.Transact(opts, "notifyNotExit", megapoolAddress, validatorId, slotTimestamp, proofVersion, proofData)
if err != nil {
return nil, fmt.Errorf("error calling notify not exit: %w", err)
}
Expand Down Expand Up @@ -199,45 +208,45 @@ func ChallengeExit(rp *rocketpool.RocketPool, exitChallenge []ExitChallenge, opt
}

// Estimate the gas to call NotifyFinalBalance
func EstimateNotifyFinalBalance(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, withdrawalProof WithdrawalProof, validatorProof ValidatorProof, slotProof SlotProof, opts *bind.TransactOpts) (gaslimit.Limits, error) {
func EstimateNotifyFinalBalance(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, proofVersion *big.Int, proofData []byte, opts *bind.TransactOpts) (gaslimit.Limits, error) {
megapoolManager, err := getRocketMegapoolManager(rp, nil)
if err != nil {
return gaslimit.Limits{}, err
}
return megapoolManager.GetTransactionGasInfo(opts, "notifyFinalBalance", megapoolAddress, validatorId, slotTimestamp, withdrawalProof, validatorProof, slotProof)
return megapoolManager.GetTransactionGasInfo(opts, "notifyFinalBalance", megapoolAddress, validatorId, slotTimestamp, proofVersion, proofData)
}

// Notify the megapool of the final balance of an exited validator
func NotifyFinalBalance(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, withdrawalProof WithdrawalProof, validatorProof ValidatorProof, slotProof SlotProof, opts *bind.TransactOpts) (*types.Transaction, error) {
func NotifyFinalBalance(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, proofVersion *big.Int, proofData []byte, opts *bind.TransactOpts) (*types.Transaction, error) {
megapoolManager, err := getRocketMegapoolManager(rp, nil)
if err != nil {
return nil, err
}
tx, err := megapoolManager.Transact(opts, "notifyFinalBalance", megapoolAddress, validatorId, slotTimestamp, withdrawalProof, validatorProof, slotProof)
tx, err := megapoolManager.Transact(opts, "notifyFinalBalance", megapoolAddress, validatorId, slotTimestamp, proofVersion, proofData)
if err != nil {
return nil, fmt.Errorf("error calling notify final balance: %w", err)
}
return tx, nil
}

// Estimate the gas to call DissolveWithProof
func EstimateDissolveWithProof(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof ValidatorProof, slotProof SlotProof, opts *bind.TransactOpts) (gaslimit.Limits, error) {
func EstimateDissolveWithProof(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, proofVersion *big.Int, proofData []byte, opts *bind.TransactOpts) (gaslimit.Limits, error) {
megapoolManager, err := getRocketMegapoolManager(rp, nil)
if err != nil {
return gaslimit.Limits{}, err
}
return megapoolManager.GetTransactionGasInfo(opts, "dissolve", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof)
return megapoolManager.GetTransactionGasInfo(opts, "dissolve", megapoolAddress, validatorId, slotTimestamp, proofVersion, proofData)
}

// Dissolve a validator using a proof that it used wrong credentials
func DissolveWithProof(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof ValidatorProof, slotProof SlotProof, opts *bind.TransactOpts) (*types.Transaction, error) {
func DissolveWithProof(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, proofVersion *big.Int, proofData []byte, opts *bind.TransactOpts) (*types.Transaction, error) {
megapoolManager, err := getRocketMegapoolManager(rp, nil)
if err != nil {
return nil, err
}
tx, err := megapoolManager.Transact(opts, "dissolve", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof)
tx, err := megapoolManager.Transact(opts, "dissolve", megapoolAddress, validatorId, slotTimestamp, proofVersion, proofData)
if err != nil {
return nil, fmt.Errorf("error calling notify final balance: %w", err)
return nil, fmt.Errorf("error calling dissolve with proof: %w", err)
}
return tx, nil
}
Expand Down
132 changes: 132 additions & 0 deletions bindings/megapool/proof-bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package megapool

import (
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi"
)

// Proof versions accepted by BeaconStateVerifier on v1.4.1-dev.
var (
ValidatorProofVersion1 = big.NewInt(1)
FinalBalanceProofVersion1 = big.NewInt(1)
FinalBalanceProofVersion2 = big.NewInt(2)
)

type ValidatorProofBundleV1 struct {
ValidatorProof ValidatorProof `json:"validatorProof"`
SlotProof SlotProof `json:"slotProof"`
}

type FinalBalanceProofBundleV1 struct {
WithdrawalProof WithdrawalProof `json:"withdrawalProof"`
ValidatorProof ValidatorProof `json:"validatorProof"`
SlotProof SlotProof `json:"slotProof"`
}

type FinalBalanceProofBundleV2 struct {
WithdrawalProof WithdrawalProof `json:"withdrawalProof"`
ValidatorProof ValidatorProof `json:"validatorProof"`
SlotProof SlotProof `json:"slotProof"`
PreviousNextWithdrawalIndexProof NextWithdrawalIndexProof `json:"previousNextWithdrawalIndexProof"`
ValidatorBalanceProof ValidatorBalanceProof `json:"validatorBalanceProof"`
}
Comment thread
0xfornax marked this conversation as resolved.

var (
validatorComponents = []abi.ArgumentMarshaling{
{Name: "pubkey", Type: "bytes"},
{Name: "withdrawalCredentials", Type: "bytes32"},
{Name: "effectiveBalance", Type: "uint64"},
{Name: "slashed", Type: "bool"},
{Name: "activationEligibilityEpoch", Type: "uint64"},
{Name: "activationEpoch", Type: "uint64"},
{Name: "exitEpoch", Type: "uint64"},
{Name: "withdrawableEpoch", Type: "uint64"},
}
validatorProofComponents = []abi.ArgumentMarshaling{
{Name: "validatorIndex", Type: "uint40"},
{Name: "validator", Type: "tuple", Components: validatorComponents},
{Name: "witnesses", Type: "bytes32[]"},
}
slotProofComponents = []abi.ArgumentMarshaling{
{Name: "slot", Type: "uint64"},
{Name: "witnesses", Type: "bytes32[]"},
}
withdrawalComponents = []abi.ArgumentMarshaling{
{Name: "index", Type: "uint64"},
{Name: "validatorIndex", Type: "uint64"},
{Name: "withdrawalCredentials", Type: "bytes20"},
{Name: "amountInGwei", Type: "uint64"},
}
withdrawalProofComponents = []abi.ArgumentMarshaling{
{Name: "withdrawalSlot", Type: "uint64"},
{Name: "withdrawalNum", Type: "uint16"},
{Name: "withdrawal", Type: "tuple", Components: withdrawalComponents},
{Name: "witnesses", Type: "bytes32[]"},
}
nextWithdrawalIndexProofComponents = []abi.ArgumentMarshaling{
{Name: "nextWithdrawalIndex", Type: "uint64"},
{Name: "witnesses", Type: "bytes32[]"},
}
validatorBalanceProofComponents = []abi.ArgumentMarshaling{
{Name: "balanceChunk", Type: "bytes32"},
{Name: "witnesses", Type: "bytes32[]"},
}
)

func EncodeValidatorProofBundleV1(validatorProof ValidatorProof, slotProof SlotProof) ([]byte, error) {
return encodeTuple([]abi.ArgumentMarshaling{
{Name: "validatorProof", Type: "tuple", Components: validatorProofComponents},
{Name: "slotProof", Type: "tuple", Components: slotProofComponents},
}, ValidatorProofBundleV1{
ValidatorProof: validatorProof,
SlotProof: slotProof,
})
}

func EncodeFinalBalanceProofBundleV1(withdrawalProof WithdrawalProof, validatorProof ValidatorProof, slotProof SlotProof) ([]byte, error) {
return encodeTuple([]abi.ArgumentMarshaling{
{Name: "withdrawalProof", Type: "tuple", Components: withdrawalProofComponents},
{Name: "validatorProof", Type: "tuple", Components: validatorProofComponents},
{Name: "slotProof", Type: "tuple", Components: slotProofComponents},
}, FinalBalanceProofBundleV1{
WithdrawalProof: withdrawalProof,
ValidatorProof: validatorProof,
SlotProof: slotProof,
})
}

func EncodeFinalBalanceProofBundleV2(
withdrawalProof WithdrawalProof,
validatorProof ValidatorProof,
slotProof SlotProof,
previousNextWithdrawalIndexProof NextWithdrawalIndexProof,
validatorBalanceProof ValidatorBalanceProof,
) ([]byte, error) {
return encodeTuple([]abi.ArgumentMarshaling{
{Name: "withdrawalProof", Type: "tuple", Components: withdrawalProofComponents},
{Name: "validatorProof", Type: "tuple", Components: validatorProofComponents},
{Name: "slotProof", Type: "tuple", Components: slotProofComponents},
{Name: "previousNextWithdrawalIndexProof", Type: "tuple", Components: nextWithdrawalIndexProofComponents},
{Name: "validatorBalanceProof", Type: "tuple", Components: validatorBalanceProofComponents},
}, FinalBalanceProofBundleV2{
WithdrawalProof: withdrawalProof,
ValidatorProof: validatorProof,
SlotProof: slotProof,
PreviousNextWithdrawalIndexProof: previousNextWithdrawalIndexProof,
ValidatorBalanceProof: validatorBalanceProof,
})
}

func encodeTuple(components []abi.ArgumentMarshaling, value any) ([]byte, error) {
typ, err := abi.NewType("tuple", "", components)
if err != nil {
return nil, fmt.Errorf("error creating proof bundle tuple type: %w", err)
}
encoded, err := abi.Arguments{{Type: typ}}.Pack(value)
if err != nil {
return nil, fmt.Errorf("error encoding proof bundle: %w", err)
}
return encoded, nil
}
86 changes: 86 additions & 0 deletions bindings/megapool/proof-bundle_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package megapool

import (
"math/big"
"testing"
)

func sampleValidatorProof() ValidatorProof {
return ValidatorProof{
ValidatorIndex: big.NewInt(7),
Validator: ProvedValidator{
Pubkey: make([]byte, 48),
WithdrawalCredentials: [32]byte{0x01},
EffectiveBalance: 32_000_000_000,
Slashed: false,
ActivationEligibilityEpoch: 1,
ActivationEpoch: 2,
ExitEpoch: 3,
WithdrawableEpoch: 4,
},
Witnesses: [][32]byte{{0x11}, {0x22}},
}
}

func sampleSlotProof() SlotProof {
return SlotProof{
Slot: 99,
Witnesses: [][32]byte{{0x33}},
}
}

func sampleWithdrawalProof() WithdrawalProof {
return WithdrawalProof{
WithdrawalSlot: 50,
WithdrawalNum: 1,
Withdrawal: Withdrawal{
Index: 10,
ValidatorIndex: 7,
WithdrawalCredentials: [20]byte{0xaa},
AmountInGwei: 32_000_000_000,
},
Witnesses: [][32]byte{{0x44}, {0x55}},
}
}

func TestEncodeValidatorProofBundleV1(t *testing.T) {
encoded, err := EncodeValidatorProofBundleV1(sampleValidatorProof(), sampleSlotProof())
if err != nil {
t.Fatalf("encode: %v", err)
}
if len(encoded) == 0 {
t.Fatal("expected non-empty encoding")
}
}

func TestEncodeFinalBalanceProofBundleV1(t *testing.T) {
encoded, err := EncodeFinalBalanceProofBundleV1(sampleWithdrawalProof(), sampleValidatorProof(), sampleSlotProof())
if err != nil {
t.Fatalf("encode: %v", err)
}
if len(encoded) == 0 {
t.Fatal("expected non-empty encoding")
}
}

func TestEncodeFinalBalanceProofBundleV2(t *testing.T) {
encoded, err := EncodeFinalBalanceProofBundleV2(
sampleWithdrawalProof(),
sampleValidatorProof(),
sampleSlotProof(),
NextWithdrawalIndexProof{
NextWithdrawalIndex: 9,
Witnesses: [][32]byte{{0x66}},
},
ValidatorBalanceProof{
BalanceChunk: [32]byte{0x77},
Witnesses: [][32]byte{{0x88}},
},
)
if err != nil {
t.Fatalf("encode: %v", err)
}
if len(encoded) == 0 {
t.Fatal("expected non-empty encoding")
}
}
Loading
Loading