Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[slashing] Withdrawal support in CLI #163

Merged
merged 26 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from 17 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
45 changes: 0 additions & 45 deletions .github/workflows/cli_release.yml

This file was deleted.

32 changes: 0 additions & 32 deletions .github/workflows/go_test.yml

This file was deleted.

36 changes: 0 additions & 36 deletions .github/workflows/lint.yml

This file was deleted.

97 changes: 97 additions & 0 deletions .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# .github/workflows/release.yml
name: Build, test, release

on:
pull_request:
push:
# run only against tags
tags:
- "*"

permissions:
contents: write

jobs:
lint:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Go 1.22
uses: actions/setup-go@v1
env:
GOPATH: /home/runner/.go
with:
go-version: 1.22.4

- name: Install dependencies
env:
GOPATH: /home/runner/.go
run: |
mkdir /home/runner/.go
make setup
go env
ls -lar $GOPATH

- name: Run Lint
env:
GOPATH: /home/runner/.go
run: /home/runner/.go/bin/golangci-lint run ./cli
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'

- name: Install dependencies
run: |
go mod download
sudo apt-get install -y unzip

- name: Download blockchain data
run: |
curl -o data/deneb_holesky_beacon_state_2227472.ssz.zip https://dviu8zszosyat.cloudfront.net/deneb_holesky_beacon_state_2227472.ssz.zip
(cd data && unzip deneb_holesky_beacon_state_2227472.ssz.zip)

- name: Run tests
run: |
go test -v ./...
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: stable
- name: Run GoReleaser (tagged release)
uses: goreleaser/goreleaser-action@v6
if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
with:
distribution: goreleaser
version: "~> v2"
args: release --clean
workdir: cli
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run GoReleaser (branch)
uses: goreleaser/goreleaser-action@v6
if: ${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/') }}
with:
distribution: goreleaser
version: "~> v2"
args: release --snapshot
workdir: cli
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5 changes: 2 additions & 3 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ linters-settings:
- performance
- style
govet:
check-shadowing: true
shadow: true
enable:
- fieldalignment
nolintlint:
Expand All @@ -26,12 +26,11 @@ linters:
- dogsled
- dupl
- errcheck
- exportloopref
- copyloopvar
- exhaustive
- goconst
- gocritic
- gofmt
- gomnd
- gocyclo
- gosec
- gosimple
Expand Down
154 changes: 154 additions & 0 deletions cli/commands/completeAllWithdrawals.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package commands

import (
"context"
"fmt"
"math/big"

"github.com/pkg/errors"
lo "github.com/samber/lo"

"github.com/Layr-Labs/eigenlayer-contracts/pkg/bindings/EigenPod"
"github.com/Layr-Labs/eigenlayer-contracts/pkg/bindings/IDelegationManager"
"github.com/Layr-Labs/eigenpod-proofs-generation/cli/core"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)

type TCompleteWithdrawalArgs struct {
EthNode string
EigenPod string
Sender string
EstimateGas bool
}

func DelegationManager(chainId *big.Int) common.Address {
data := map[uint64]string{
// TODO(zeus) - make this runnable via zeus.
1: "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", // mainnet
17000: "0x75dfE5B44C2E530568001400D3f704bC8AE350CC", // holesky preprod
jbrower95 marked this conversation as resolved.
Show resolved Hide resolved
}
contract, ok := data[chainId.Uint64()]
if !ok {
panic("no delegation manager found for chain")
}
addr := common.HexToAddress(contract)
return addr
}

func CompleteAllWithdrawalsCommand(args TCompleteWithdrawalArgs) error {
ctx := context.Background()

isSimulation := args.EstimateGas

eth, err := ethclient.DialContext(ctx, args.EthNode)
core.PanicOnError("failed to reach eth node", err)

chainId, err := eth.ChainID(ctx)
core.PanicOnError("failed to load chainId", err)

acc, err := core.PrepareAccount(&args.Sender, chainId, isSimulation)
core.PanicOnError("failed to parse private key", err)

curBlockNumber, err := eth.BlockNumber(ctx)
core.PanicOnError("failed to load current block number", err)

pod, err := EigenPod.NewEigenPod(common.HexToAddress(args.EigenPod), eth)
core.PanicOnError("failed to reach eigenpod", err)

reg, err := pod.WithdrawableRestakedExecutionLayerGwei(nil)
core.PanicOnError("failed to fetch REG", err)

podOwner, err := pod.PodOwner(nil)
core.PanicOnError("failed to read podOwner", err)

delegationManager, err := IDelegationManager.NewIDelegationManager(DelegationManager(chainId), eth)
core.PanicOnError("failed to reach delegation manager", err)

minDelay, err := delegationManager.MinWithdrawalDelayBlocks(nil)
core.PanicOnError("failed to read MinWithdrawalDelayBlocks", err)

queuedWithdrawals, err := delegationManager.GetQueuedWithdrawals(nil, podOwner)
core.PanicOnError("failed to read queuedWithdrawals", err)

eligibleWithdrawals := lo.Map(queuedWithdrawals.Withdrawals, func(withdrawal IDelegationManager.IDelegationManagerTypesWithdrawal, index int) *IDelegationManager.IDelegationManagerTypesWithdrawal {
isBeaconWithdrawal := len(withdrawal.Strategies) == 1 && withdrawal.Strategies[0].Cmp(core.BeaconStrategy()) == 0
isExecutable := curBlockNumber >= uint64(withdrawal.StartBlock+minDelay)
if isBeaconWithdrawal && isExecutable {
return &withdrawal
}
return nil
})

if len(eligibleWithdrawals) == 0 {
fmt.Printf("Your pod has no eligible withdrawals.\n")
return nil
}

var runningSum uint64 = 0
affordedWithdrawals := lo.Map(eligibleWithdrawals, func(withdrawal *IDelegationManager.IDelegationManagerTypesWithdrawal, index int) *IDelegationManager.IDelegationManagerTypesWithdrawal {
if withdrawal == nil {
return nil
}
withdrawalShares := queuedWithdrawals.Shares[index][0].Uint64()
if reg < (runningSum + withdrawalShares) {
jbrower95 marked this conversation as resolved.
Show resolved Hide resolved
runningSum = runningSum + withdrawalShares
return withdrawal
}
return nil
})

affordedWithdrawals = lo.Filter(affordedWithdrawals, func(withdrawal *IDelegationManager.IDelegationManagerTypesWithdrawal, index int) bool {
return withdrawal != nil
})

if len(affordedWithdrawals) == 0 && len(eligibleWithdrawals) > 0 {
fmt.Printf("WARN: Your pod has %d withdrawals available, but your pod does not have enough funding to proceed.\n", len(eligibleWithdrawals))
fmt.Printf("Consider checkpointing to claim beacon rewards, or depositing ETH and checkpointing to complete these withdrawals.\n\n")
return errors.New("Insufficient funds")
}

if len(affordedWithdrawals) != len(eligibleWithdrawals) {
jbrower95 marked this conversation as resolved.
Show resolved Hide resolved
fmt.Printf("WARN: Your pod has %d withdrawals available, but you only have enough balance to satisfy %d of them.\n", len(eligibleWithdrawals), len(affordedWithdrawals))
fmt.Printf("Consider checkpointing to claim beacon rewards, or depositing ETH and checkpointing to complete these withdrawals.\n\n")
}

fmt.Printf("Your podOwner(%s) has %d withdrawals that can be completed right now.\n", podOwner.Hex(), len(affordedWithdrawals))
fmt.Printf("Total ETH: %sETH\n", core.GweiToEther(core.WeiToGwei(new(big.Int).SetUint64(runningSum))).String())

if !isSimulation {
core.PanicIfNoConsent("Would you like to continue?")
} else {
fmt.Printf("THIS IS A SIMULATION. No transaction will be recorded onchain.\n")
}

withdrawals := lo.Map(affordedWithdrawals, func(w *IDelegationManager.IDelegationManagerTypesWithdrawal, i int) IDelegationManager.IDelegationManagerTypesWithdrawal {
return *w
})

tokens := lo.Map(withdrawals, func(_ IDelegationManager.IDelegationManagerTypesWithdrawal, _ int) []common.Address {
return []common.Address{common.BigToAddress(big.NewInt(0))}
})

receiveAsTokens := lo.Map(withdrawals, func(_ IDelegationManager.IDelegationManagerTypesWithdrawal, _ int) bool {
return true
})

txn, err := delegationManager.CompleteQueuedWithdrawals(acc.TransactionOptions, withdrawals, tokens, receiveAsTokens)
core.PanicOnError("CompleteQueuedWithdrawals failed.", err)

if !isSimulation {
fmt.Printf("%s\n", txn.Hash().Hex())
} else {
printAsJSON(Transaction{
Type: "complete-withdrawals",
To: txn.To().Hex(),
CallData: common.Bytes2Hex(txn.Data()),
GasEstimateGwei: func() *uint64 {
gas := txn.Gas()
return &gas
}(),
})
}
return nil
}
Loading
Loading