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 }