github.com/prysmaticlabs/prysm@v1.4.4/tools/drainContracts/drainContracts.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"log"
     9  	"math/big"
    10  	"os"
    11  	"time"
    12  
    13  	"github.com/ethereum/go-ethereum"
    14  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    15  	"github.com/ethereum/go-ethereum/accounts/keystore"
    16  	"github.com/ethereum/go-ethereum/common"
    17  	"github.com/ethereum/go-ethereum/crypto"
    18  	"github.com/ethereum/go-ethereum/ethclient"
    19  	"github.com/ethereum/go-ethereum/rpc"
    20  	"github.com/pkg/errors"
    21  	contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
    22  	"github.com/prysmaticlabs/prysm/shared/version"
    23  	"github.com/sirupsen/logrus"
    24  	"github.com/urfave/cli/v2"
    25  	prefixed "github.com/x-cray/logrus-prefixed-formatter"
    26  )
    27  
    28  func main() {
    29  	var keystoreUTCPath string
    30  	var passwordFile string
    31  	var httpPath string
    32  	var privKeyString string
    33  
    34  	customFormatter := new(prefixed.TextFormatter)
    35  	customFormatter.TimestampFormat = "2006-01-02 15:04:05"
    36  	customFormatter.FullTimestamp = true
    37  	logrus.SetFormatter(customFormatter)
    38  
    39  	app := cli.App{}
    40  	app.Name = "drainContracts"
    41  	app.Usage = "this is a util to drain all (testing) deposit contracts of their ETH."
    42  	app.Version = version.Version()
    43  	app.Flags = []cli.Flag{
    44  		&cli.StringFlag{
    45  			Name:        "keystoreUTCPath",
    46  			Usage:       "Location of keystore",
    47  			Destination: &keystoreUTCPath,
    48  		},
    49  		&cli.StringFlag{
    50  			Name:        "httpPath",
    51  			Value:       "https://goerli.infura.io/v3/be3fb7ed377c418087602876a40affa1",
    52  			Usage:       "HTTP-RPC server listening interface",
    53  			Destination: &httpPath,
    54  		},
    55  		&cli.StringFlag{
    56  			Name:        "passwordFile",
    57  			Value:       "./password.txt",
    58  			Usage:       "Password file for unlock account",
    59  			Destination: &passwordFile,
    60  		},
    61  		&cli.StringFlag{
    62  			Name:        "privKey",
    63  			Usage:       "Private key to send ETH transaction",
    64  			Destination: &privKeyString,
    65  		},
    66  	}
    67  
    68  	app.Action = func(c *cli.Context) error {
    69  		// Set up RPC client
    70  		var rpcClient *rpc.Client
    71  		var err error
    72  		var txOps *bind.TransactOpts
    73  
    74  		// Uses HTTP-RPC if IPC is not set
    75  		rpcClient, err = rpc.Dial(httpPath)
    76  		if err != nil {
    77  			return err
    78  		}
    79  
    80  		client := ethclient.NewClient(rpcClient)
    81  
    82  		// User inputs private key, sign tx with private key
    83  		if privKeyString != "" {
    84  			privKey, err := crypto.HexToECDSA(privKeyString)
    85  			if err != nil {
    86  				return err
    87  			}
    88  			txOps, err = bind.NewKeyedTransactorWithChainID(privKey, big.NewInt(1337))
    89  			if err != nil {
    90  				return err
    91  			}
    92  			txOps.Value = big.NewInt(0)
    93  			txOps.GasLimit = 4000000
    94  			txOps.Context = context.Background()
    95  			nonce, err := client.NonceAt(context.Background(), crypto.PubkeyToAddress(privKey.PublicKey), nil)
    96  			if err != nil {
    97  				return errors.Wrap(err, "could not get account nonce")
    98  			}
    99  			txOps.Nonce = big.NewInt(int64(nonce))
   100  			fmt.Printf("current address is %s\n", crypto.PubkeyToAddress(privKey.PublicKey).String())
   101  			fmt.Printf("nonce is %d\n", nonce)
   102  			// User inputs keystore json file, sign tx with keystore json
   103  		} else {
   104  			password := loadTextFromFile(passwordFile)
   105  
   106  			// #nosec - Inclusion of file via variable is OK for this tool.
   107  			keyJSON, err := ioutil.ReadFile(keystoreUTCPath)
   108  			if err != nil {
   109  				return err
   110  			}
   111  			privKey, err := keystore.DecryptKey(keyJSON, password)
   112  			if err != nil {
   113  				return err
   114  			}
   115  
   116  			txOps, err = bind.NewKeyedTransactorWithChainID(privKey.PrivateKey, big.NewInt(1337))
   117  			if err != nil {
   118  				return err
   119  			}
   120  			txOps.Value = big.NewInt(0)
   121  			txOps.GasLimit = 4000000
   122  			txOps.Context = context.Background()
   123  			nonce, err := client.NonceAt(context.Background(), privKey.Address, nil)
   124  			if err != nil {
   125  				return err
   126  			}
   127  			txOps.Nonce = big.NewInt(int64(nonce))
   128  			fmt.Printf("current address is %s\n", privKey.Address.String())
   129  			fmt.Printf("nonce is %d\n", nonce)
   130  		}
   131  
   132  		addresses, err := allDepositContractAddresses(client)
   133  		if err != nil {
   134  			return errors.Wrap(err, "Could not get all deposit contract address")
   135  		}
   136  
   137  		fmt.Printf("%d contracts ready to drain found\n", len(addresses))
   138  
   139  		for _, address := range addresses {
   140  			bal, err := client.BalanceAt(context.Background(), address, nil /*blockNum*/)
   141  			if err != nil {
   142  				return err
   143  			}
   144  			if bal.Cmp(big.NewInt(0)) < 1 {
   145  				continue
   146  			}
   147  			depositContract, err := contracts.NewDepositContract(address, client)
   148  			if err != nil {
   149  				log.Fatal(err)
   150  			}
   151  			tx, err := depositContract.Drain(txOps)
   152  			if err != nil {
   153  				log.Fatalf("unable to send transaction to contract: %v", err)
   154  			}
   155  
   156  			txOps.Nonce = txOps.Nonce.Add(txOps.Nonce, big.NewInt(1))
   157  
   158  			fmt.Printf("Contract address %s drained in TX hash: %s\n", address.String(), tx.Hash().String())
   159  			time.Sleep(time.Duration(1) * time.Second)
   160  		}
   161  		return nil
   162  	}
   163  
   164  	err := app.Run(os.Args)
   165  	if err != nil {
   166  		log.Fatal(err)
   167  	}
   168  }
   169  
   170  func loadTextFromFile(filepath string) string {
   171  	// #nosec - Inclusion of file via variable is OK for this tool.
   172  	file, err := os.Open(filepath)
   173  	if err != nil {
   174  		log.Fatal(err)
   175  	}
   176  
   177  	scanner := bufio.NewScanner(file)
   178  	scanner.Split(bufio.ScanWords)
   179  	scanner.Scan()
   180  	return scanner.Text()
   181  }
   182  
   183  func allDepositContractAddresses(client *ethclient.Client) ([]common.Address, error) {
   184  	log.Print("Looking up contracts")
   185  	addresses := make(map[common.Address]bool)
   186  
   187  	// Hash of deposit log signature
   188  	// DepositEvent: event({
   189  	//    pubkey: bytes[48],
   190  	//    withdrawal_credentials: bytes[32],
   191  	//    amount: bytes[8],
   192  	//    signature: bytes[96],
   193  	//    index: bytes[8],
   194  	// })
   195  	depositTopicHash := common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5")
   196  	fmt.Println(depositTopicHash.Hex())
   197  
   198  	query := ethereum.FilterQuery{
   199  		Addresses: []common.Address{},
   200  		Topics: [][]common.Hash{
   201  			{depositTopicHash},
   202  		},
   203  		FromBlock: big.NewInt(800000), // Contracts before this may not have drain().
   204  	}
   205  
   206  	logs, err := client.FilterLogs(context.Background(), query)
   207  	if err != nil {
   208  		return nil, errors.Wrap(err, "could not get all deposit logs")
   209  	}
   210  
   211  	fmt.Printf("%d deposit logs found\n", len(logs))
   212  	for i := len(logs)/2 - 1; i >= 0; i-- {
   213  		opp := len(logs) - 1 - i
   214  		logs[i], logs[opp] = logs[opp], logs[i]
   215  	}
   216  
   217  	for _, ll := range logs {
   218  		addresses[ll.Address] = true
   219  	}
   220  
   221  	keys := make([]common.Address, 0, len(addresses))
   222  	for key := range addresses {
   223  		keys = append(keys, key)
   224  	}
   225  
   226  	return keys, nil
   227  }