Skip to content

Commit

Permalink
Add drain script (#2418)
Browse files Browse the repository at this point in the history
* Add drain script

* Fix script to drain contracts from newest to oldest

* Add README

* remove comments

* Only after block 400k, look up by deposit event
  • Loading branch information
0xKiwi authored and prestonvanloon committed May 5, 2019
1 parent 88df0f6 commit d721d7f
Show file tree
Hide file tree
Showing 3 changed files with 279 additions and 0 deletions.
30 changes: 30 additions & 0 deletions contracts/deposit-contract/drainContracts/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
load("@io_bazel_rules_docker//go:image.bzl", "go_image")
load("@io_bazel_rules_docker//container:container.bzl", "container_push")

go_library(
name = "go_default_library",
srcs = ["drainContracts.go"],
importpath = "github.com/prysmaticlabs/prysm/contracts/deposit-contract/drainContracts",
visibility = ["//visibility:private"],
deps = [
"//contracts/deposit-contract:go_default_library",
"//shared/version:go_default_library",
"@com_github_ethereum_go_ethereum//:go_default_library",
"@com_github_ethereum_go_ethereum//accounts/abi/bind:go_default_library",
"@com_github_ethereum_go_ethereum//accounts/keystore:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//crypto:go_default_library",
"@com_github_ethereum_go_ethereum//ethclient:go_default_library",
"@com_github_ethereum_go_ethereum//rpc:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_urfave_cli//:go_default_library",
"@com_github_x_cray_logrus_prefixed_formatter//:go_default_library",
],
)

go_binary(
name = "drainContracts",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)
39 changes: 39 additions & 0 deletions contracts/deposit-contract/drainContracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
## Utility to Drain All Deposit Contracts

This is a utility to help users drain the contract addresses they have deployed in order to get their testnet ether back. To run the utility, it defaults to an infura link but you can use your own provider through the flags. The utility will print out each address it sends a transaction to.

### Usage

_Name:_
**drainContracts** - this is a util to drain all deposit contracts

_Usage:_
drainContracts [global options]

_Flags:_

- --keystoreUTCPath value keystore JSON for account
- --httpPath value HTTP-RPC server listening interface (default: "http://localhost:8545/")
- --passwordFile value Password file for unlock account (default: "./password.txt")
- --privKey value Private key to unlock account
- --help, -h show help
- --version, -v print the version

### Example

To use private key with default RPC:

```
bazel run //contracts/deposit-contract/drainContracts -- --httpPath=https://goerli.prylabs.net --privKey=$(echo /path/to/private/key/file)
```

### Output

```
current address is 0xdbA543721462680431eC4eeB26163079B3645660
nonce is 7060
0xd1faa3f9bca1d698df559716fe6d1c9999155b38d3158fffbc98d76d568091fc
1190 chain start logs found
1190 contracts ready to drain found
Contract address 0x4cb8976E4Bf0b6A462AF8704F0f724775B67b4Ce drained in TX hash: 0x3f963c30c4fd4ff875c641be1e7b873bfe02ae2cd2d73554cc6087c2d3acaa9e
```
210 changes: 210 additions & 0 deletions contracts/deposit-contract/drainContracts/drainContracts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package main

import (
"bufio"
"context"
"fmt"
"io/ioutil"
"log"
"math/big"
"os"
"time"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
"github.com/prysmaticlabs/prysm/shared/version"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
prefixed "github.com/x-cray/logrus-prefixed-formatter"
)

func main() {
var keystoreUTCPath string
var passwordFile string
var httpPath string
var privKeyString string

customFormatter := new(prefixed.TextFormatter)
customFormatter.TimestampFormat = "2006-01-02 15:04:05"
customFormatter.FullTimestamp = true
logrus.SetFormatter(customFormatter)

app := cli.NewApp()
app.Name = "drainContracts"
app.Usage = "this is a util to drain all (testing) deposit contracts of their ETH."
app.Version = version.GetVersion()
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "keystoreUTCPath",
Usage: "Location of keystore",
Destination: &keystoreUTCPath,
},
cli.StringFlag{
Name: "httpPath",
Value: "https://goerli.infura.io/v3/be3fb7ed377c418087602876a40affa1",
Usage: "HTTP-RPC server listening interface",
Destination: &httpPath,
},
cli.StringFlag{
Name: "passwordFile",
Value: "./password.txt",
Usage: "Password file for unlock account",
Destination: &passwordFile,
},
cli.StringFlag{
Name: "privKey",
Usage: "Private key to send ETH transaction",
Destination: &privKeyString,
},
}

app.Action = func(c *cli.Context) {
// Set up RPC client
var rpcClient *rpc.Client
var err error
var txOps *bind.TransactOpts

// Uses HTTP-RPC if IPC is not set
rpcClient, err = rpc.Dial(httpPath)
if err != nil {
log.Fatal(err)
}

client := ethclient.NewClient(rpcClient)

// User inputs private key, sign tx with private key
if privKeyString != "" {
privKey, err := crypto.HexToECDSA(privKeyString)
if err != nil {
log.Fatal(err)
}
txOps = bind.NewKeyedTransactor(privKey)
txOps.Value = big.NewInt(0)
txOps.GasLimit = 4000000
nonce, err := client.NonceAt(context.Background(), crypto.PubkeyToAddress(privKey.PublicKey), nil)
if err != nil {
log.Fatalf("could not get account nonce: %v", err)
}
txOps.Nonce = big.NewInt(int64(nonce))
fmt.Printf("current address is %s\n", crypto.PubkeyToAddress(privKey.PublicKey).String())
fmt.Printf("nonce is %d\n", nonce)
// User inputs keystore json file, sign tx with keystore json
} else {
password := loadTextFromFile(passwordFile)

// #nosec - Inclusion of file via variable is OK for this tool.
keyJSON, err := ioutil.ReadFile(keystoreUTCPath)
if err != nil {
log.Fatal(err)
}
privKey, err := keystore.DecryptKey(keyJSON, password)
if err != nil {
log.Fatal(err)
}

txOps = bind.NewKeyedTransactor(privKey.PrivateKey)
txOps.Value = big.NewInt(0)
txOps.GasLimit = 4000000
nonce, err := client.NonceAt(context.Background(), privKey.Address, nil)
if err != nil {
log.Fatalf("could not get account nonce: %v", err)
}
txOps.Nonce = big.NewInt(int64(nonce))
fmt.Printf("current address is %s\n", privKey.Address.String())
fmt.Printf("nonce is %d\n", nonce)
}

addresses, err := allDepositContractAddresses(client)
if err != nil {
log.Fatalf("Could not get all deposit contract address: %v", err)
}

fmt.Printf("%d contracts ready to drain found\n", len(addresses))

for _, address := range addresses {
bal, err := client.BalanceAt(context.Background(), address, nil /*blockNum*/)
if err != nil {
log.Fatal(err)
}
if bal.Cmp(big.NewInt(0)) < 1 {
continue
}
depositContract, err := contracts.NewDepositContract(address, client)
if err != nil {
log.Fatal(err)
}
tx, err := depositContract.Drain(txOps)
if err != nil {
log.Fatalf("unable to send transaction to contract: %v", err)
}

txOps.Nonce = txOps.Nonce.Add(txOps.Nonce, big.NewInt(1))

fmt.Printf("Contract address %s drained in TX hash: %s\n", address.String(), tx.Hash().String())
time.Sleep(time.Duration(1) * time.Second)
}
}

err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}

func loadTextFromFile(filepath string) string {
// #nosec - Inclusion of file via variable is OK for this tool.
file, err := os.Open(filepath)
if err != nil {
log.Fatal(err)
}

scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanWords)
scanner.Scan()
return scanner.Text()
}

func allDepositContractAddresses(client *ethclient.Client) ([]common.Address, error) {
log.Print("Looking up contracts")
addresses := make(map[common.Address]bool)

depositEventSignature := []byte("Deposit(bytes32,bytes,bytes,bytes32[32])")
depositTopicHash := crypto.Keccak256Hash(depositEventSignature)
fmt.Println(depositTopicHash.Hex())

query := ethereum.FilterQuery{
Addresses: []common.Address{},
Topics: [][]common.Hash{
[]common.Hash{depositTopicHash},
},
FromBlock: big.NewInt(400000), // Contracts before this may not have drain().
}

logs, err := client.FilterLogs(context.Background(), query)
if err != nil {
return nil, fmt.Errorf("could not get all deposit logs: %v", err)
}

fmt.Printf("%d deposit logs found\n", len(logs))
for i := len(logs)/2 - 1; i >= 0; i-- {
opp := len(logs) - 1 - i
logs[i], logs[opp] = logs[opp], logs[i]
}

for _, ll := range logs {
addresses[ll.Address] = true
}

keys := make([]common.Address, 0, len(addresses))
for key := range addresses {
keys = append(keys, key)
}

return keys, nil
}

0 comments on commit d721d7f

Please sign in to comment.