-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
88df0f6
commit d721d7f
Showing
3 changed files
with
279 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
210
contracts/deposit-contract/drainContracts/drainContracts.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |