Add True Eth2 Deposit Contract, Bytecode, ABI (#9637)

* solidity contract, abi, and deposit util

* gaz

* gaz

* drain contract remove

* build

* fix up deploy

* add readme

* fix e2e

* revert

* revert flag

* fix

* revert test flag

* fix broken test

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
This commit is contained in:
Raul Jordan 2021-09-22 12:27:13 -05:00 committed by GitHub
parent f52f737d2b
commit 191bce3655
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 386 additions and 729 deletions

View File

@ -6,12 +6,12 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/core",
visibility = [
"//beacon-chain:__subpackages__",
"//contracts/deposit:__pkg__",
"//fuzz:__pkg__",
"//network/forks:__pkg__",
"//proto/prysm/v1alpha1/attestation:__pkg__",
"//runtime/interop:__pkg__",
"//shared/attestationutil:__pkg__",
"//shared/depositutil:__pkg__",
"//shared/keystore:__pkg__",
"//shared/testutil:__pkg__",
"//shared/testutil/altair:__pkg__",

View File

@ -31,6 +31,7 @@ go_library(
"//config/params:go_default_library",
"//container/slice:go_default_library",
"//container/trie:go_default_library",
"//contracts/deposit:go_default_library",
"//crypto/bls:go_default_library",
"//crypto/hash:go_default_library",
"//math:go_default_library",
@ -41,7 +42,6 @@ go_library(
"//proto/prysm/v1alpha1/slashings:go_default_library",
"//runtime/version:go_default_library",
"//shared/bytesutil:go_default_library",
"//shared/depositutil:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",

View File

@ -9,11 +9,11 @@ import (
"github.com/prysmaticlabs/prysm/beacon-chain/state"
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/container/trie"
"github.com/prysmaticlabs/prysm/contracts/deposit"
"github.com/prysmaticlabs/prysm/crypto/bls"
"github.com/prysmaticlabs/prysm/math"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/depositutil"
)
// ProcessPreGenesisDeposits processes a deposit for the beacon state before chainstart.
@ -238,7 +238,7 @@ func verifyDeposit(beaconState state.ReadOnlyBeaconState, deposit *ethpb.Deposit
}
func verifyDepositDataSigningRoot(obj *ethpb.Deposit_Data, domain []byte) error {
return depositutil.VerifyDepositSignature(obj, domain)
return deposit.VerifyDepositSignature(obj, domain)
}
func verifyDepositDataWithDomain(ctx context.Context, deps []*ethpb.Deposit, domain []byte) error {

View File

@ -18,12 +18,12 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers",
visibility = [
"//beacon-chain:__subpackages__",
"//contracts/deposit:__pkg__",
"//fuzz:__pkg__",
"//network/forks:__pkg__",
"//proto/prysm/v1alpha1/attestation:__pkg__",
"//runtime/interop:__pkg__",
"//shared/attestationutil:__pkg__",
"//shared/depositutil:__pkg__",
"//shared/keystore:__pkg__",
"//shared/testutil:__pkg__",
"//shared/testutil/altair:__pkg__",

View File

@ -60,14 +60,14 @@ func TestConfigureProofOfWork(t *testing.T) {
set.String(flags.DepositContractFlag.Name, "", "")
require.NoError(t, set.Set(flags.ChainID.Name, strconv.Itoa(100)))
require.NoError(t, set.Set(flags.NetworkID.Name, strconv.Itoa(200)))
require.NoError(t, set.Set(flags.DepositContractFlag.Name, "deposit"))
require.NoError(t, set.Set(flags.DepositContractFlag.Name, "deposit-contract"))
cliCtx := cli.NewContext(&app, set, nil)
configureEth1Config(cliCtx)
assert.Equal(t, uint64(100), params.BeaconConfig().DepositChainID)
assert.Equal(t, uint64(200), params.BeaconConfig().DepositNetworkID)
assert.Equal(t, "deposit", params.BeaconConfig().DepositContractAddress)
assert.Equal(t, "deposit-contract", params.BeaconConfig().DepositContractAddress)
}
func TestConfigureNetwork(t *testing.T) {

View File

@ -32,7 +32,7 @@ go_library(
"//beacon-chain/state/v1:go_default_library",
"//config/params:go_default_library",
"//container/trie:go_default_library",
"//contracts/deposit-contract:go_default_library",
"//contracts/deposit:go_default_library",
"//crypto/hash:go_default_library",
"//io/logs:go_default_library",
"//monitoring/clientstats:go_default_library",
@ -85,7 +85,7 @@ go_test(
"//beacon-chain/powchain/types:go_default_library",
"//config/params:go_default_library",
"//container/trie:go_default_library",
"//contracts/deposit-contract:go_default_library",
"//contracts/deposit:go_default_library",
"//crypto/bls:go_default_library",
"//monitoring/clientstats:go_default_library",
"//network:go_default_library",

View File

@ -12,7 +12,7 @@ import (
"github.com/ethereum/go-ethereum/trie"
dbutil "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
mockPOW "github.com/prysmaticlabs/prysm/beacon-chain/powchain/testing"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
)

View File

@ -18,7 +18,7 @@ import (
coreState "github.com/prysmaticlabs/prysm/beacon-chain/core/transition"
v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1"
"github.com/prysmaticlabs/prysm/config/params"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit"
"github.com/prysmaticlabs/prysm/crypto/hash"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
protodb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"

View File

@ -16,7 +16,7 @@ import (
testDB "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
mockPOW "github.com/prysmaticlabs/prysm/beacon-chain/powchain/testing"
"github.com/prysmaticlabs/prysm/config/params"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit"
"github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"

View File

@ -34,7 +34,7 @@ import (
v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1"
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/container/trie"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit"
"github.com/prysmaticlabs/prysm/io/logs"
"github.com/prysmaticlabs/prysm/monitoring/clientstats"
"github.com/prysmaticlabs/prysm/network"

View File

@ -20,7 +20,7 @@ import (
dbutil "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
mockPOW "github.com/prysmaticlabs/prysm/beacon-chain/powchain/testing"
"github.com/prysmaticlabs/prysm/config/params"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit"
"github.com/prysmaticlabs/prysm/monitoring/clientstats"
"github.com/prysmaticlabs/prysm/network"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"

View File

@ -45,6 +45,7 @@ go_library(
"//config/features:go_default_library",
"//config/params:go_default_library",
"//container/trie:go_default_library",
"//contracts/deposit:go_default_library",
"//crypto/bls:go_default_library",
"//crypto/hash:go_default_library",
"//crypto/rand:go_default_library",
@ -59,7 +60,6 @@ go_library(
"//proto/prysm/v1alpha1/wrapper:go_default_library",
"//runtime/version:go_default_library",
"//shared/bytesutil:go_default_library",
"//shared/depositutil:go_default_library",
"//time:go_default_library",
"//time/slots:go_default_library",
"@com_github_ferranbt_fastssz//:go_default_library",

View File

@ -9,10 +9,10 @@ import (
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/beacon-chain/state"
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/contracts/deposit"
"github.com/prysmaticlabs/prysm/monitoring/tracing"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/depositutil"
"go.opencensus.io/trace"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@ -259,7 +259,7 @@ func (vs *Server) validatorStatus(
log.Warn("Not connected to ETH1. Cannot determine validator ETH1 deposit block number")
return resp, nonExistentIndex
}
deposit, eth1BlockNumBigInt := vs.DepositFetcher.DepositByPubkey(ctx, pubKey)
dep, eth1BlockNumBigInt := vs.DepositFetcher.DepositByPubkey(ctx, pubKey)
if eth1BlockNumBigInt == nil { // No deposit found in ETH1.
return resp, nonExistentIndex
}
@ -272,13 +272,13 @@ func (vs *Server) validatorStatus(
log.Warn("Could not compute domain")
return resp, nonExistentIndex
}
if err := depositutil.VerifyDepositSignature(deposit.Data, domain); err != nil {
if err := deposit.VerifyDepositSignature(dep.Data, domain); err != nil {
resp.Status = ethpb.ValidatorStatus_INVALID
log.Warn("Invalid Eth1 deposit")
return resp, nonExistentIndex
}
// Set validator deposit status if their deposit is visible.
resp.Status = depositStatus(deposit.Data.Amount)
resp.Status = depositStatus(dep.Data.Amount)
resp.Eth1DepositBlockNumber = eth1BlockNumBigInt.Uint64()
return resp, nonExistentIndex

View File

@ -9,9 +9,9 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state",
visibility = [
"//beacon-chain:__subpackages__",
"//contracts/deposit:__subpackages__",
"//proto/testing:__subpackages__",
"//shared/aggregation:__subpackages__",
"//shared/depositutil:__subpackages__",
"//shared/testutil:__pkg__",
"//slasher/rpc:__subpackages__",
"//testing/benchmark:__pkg__",

View File

@ -30,10 +30,10 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state/v1",
visibility = [
"//beacon-chain:__subpackages__",
"//contracts/deposit:__subpackages__",
"//proto/testing:__subpackages__",
"//runtime/interop:__subpackages__",
"//shared/aggregation:__subpackages__",
"//shared/depositutil:__subpackages__",
"//shared/testutil:__pkg__",
"//slasher/rpc:__subpackages__",
"//testing/benchmark:__pkg__",

View File

@ -23,7 +23,7 @@ go_test(
deps = [
":go_default_library",
"//config/params:go_default_library",
"//contracts/deposit-contract:go_default_library",
"//contracts/deposit:go_default_library",
"//crypto/hash:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//shared/bytesutil:go_default_library",

View File

@ -28,6 +28,13 @@ func NewTrie(depth uint64) (*SparseMerkleTrie, error) {
return GenerateTrieFromItems(items, depth)
}
// NewTrieWithBranches returns a new merkle trie filled with branches to use.
func NewTrieWithBranches(branches [][][]byte, depth uint64) (*SparseMerkleTrie, error) {
var zeroBytes [32]byte
items := [][]byte{zeroBytes[:]}
return GenerateTrieFromItems(items, depth)
}
// CreateTrieFromProto creates a Sparse Merkle Trie from its corresponding merkle trie.
func CreateTrieFromProto(trieObj *protodb.SparseMerkleTrie) *SparseMerkleTrie {
trie := &SparseMerkleTrie{

View File

@ -7,7 +7,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/container/trie"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit"
"github.com/prysmaticlabs/prysm/crypto/hash"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/bytesutil"

View File

@ -1,6 +0,0 @@
# Contracts
This page serves as a main reference for the smart contract tooling used internally in Prysm.
**THIS DOES NOT CONTAIN CONTRACTS TO BE USED IN PRODUCTION**.

View File

@ -1,28 +0,0 @@
## Prysm Internal Validator Deposit Contract
**NOTE: THIS IS NOT THE OFFICIAL ETHEREUM VALIDATOR DEPOSIT CONTRACT. THE OFFICIAL CONTRACT CAN ONLY BE FOUND [HERE](https://github.com/ethereum/consensus-specs/blob/e4a9c5fa29def20c4264cd860868f131d6f40e72/solidity_deposit_contract/deposit_contract.sol). THE ONLY DEPOSIT CONTRACT ON MAINNET HAS ADDRESS 0x00000000219ab540356cbb839cbe05303d7705fa. DO NOT USE THE CONTRACT IN THIS FOLDER OUTSIDE OF DEVELOPMENT**
## How to execute tests
```
bazel test //contracts/deposit-contract:go_default_test
```
Run with `-v` option for detailed log output
```
bazel test //contracts/deposit-contract:go_default_test --test_arg=-test.v --test_output=streamed
=== RUN TestSetupRegistrationContract_OK
--- PASS: TestSetupRegistrationContract_OK (0.07s)
=== RUN TestRegister_Below1ETH
--- PASS: TestRegister_Below1ETH (0.02s)
=== RUN TestRegister_Above32Eth
--- PASS: TestRegister_Above32Eth (0.02s)
=== RUN TestValidatorRegister_OK
--- PASS: TestValidatorRegister_OK (0.08s)
=== RUN TestDrain
--- PASS: TestDrain (0.04s)
PASS
ok contracts/deposit-contract 0.633s
```

View File

@ -1 +0,0 @@
[{"name": "DepositEvent", "inputs": [{"type": "bytes", "name": "pubkey", "indexed": false}, {"type": "bytes", "name": "withdrawal_credentials", "indexed": false}, {"type": "bytes", "name": "amount", "indexed": false}, {"type": "bytes", "name": "signature", "indexed": false}, {"type": "bytes", "name": "index", "indexed": false}], "anonymous": false, "type": "event"}, {"outputs": [], "inputs": [{"type": "address", "name": "_drain_address"}], "constant": false, "payable": false, "type": "constructor"}, {"name": "get_deposit_root", "outputs": [{"type": "bytes32", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 95389}, {"name": "get_deposit_count", "outputs": [{"type": "bytes", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 17683}, {"name": "deposit", "outputs": [], "inputs": [{"type": "bytes", "name": "pubkey"}, {"type": "bytes", "name": "withdrawal_credentials"}, {"type": "bytes", "name": "signature"}, {"type": "bytes32", "name": "deposit_data_root"}], "constant": false, "payable": true, "type": "function", "gas": 1754607}, {"name": "drain", "outputs": [], "inputs": [], "constant": false, "payable": false, "type": "function", "gas": 35793}, {"name": "drain_address", "outputs": [{"type": "address", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 663}]

File diff suppressed because one or more lines are too long

View File

@ -1,117 +0,0 @@
# Vyper target 0.1.0b12
MIN_DEPOSIT_AMOUNT: constant(uint256) = 1000000000 # Gwei
DEPOSIT_CONTRACT_TREE_DEPTH: constant(uint256) = 32
MAX_DEPOSIT_COUNT: constant(uint256) = 4294967295 # 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1
PUBKEY_LENGTH: constant(uint256) = 48 # bytes
WITHDRAWAL_CREDENTIALS_LENGTH: constant(uint256) = 32 # bytes
SIGNATURE_LENGTH: constant(uint256) = 96 # bytes
AMOUNT_LENGTH: constant(uint256) = 8 # bytes
DepositEvent: event({
pubkey: bytes[48],
withdrawal_credentials: bytes[32],
amount: bytes[8],
signature: bytes[96],
index: bytes[8],
})
branch: bytes32[DEPOSIT_CONTRACT_TREE_DEPTH]
deposit_count: uint256
drain_address: public(address)
# Compute hashes in empty sparse Merkle tree
zero_hashes: bytes32[DEPOSIT_CONTRACT_TREE_DEPTH]
@public
def __init__(_drain_address: address):
self.drain_address = _drain_address
for i in range(DEPOSIT_CONTRACT_TREE_DEPTH - 1):
self.zero_hashes[i + 1] = sha256(concat(self.zero_hashes[i], self.zero_hashes[i]))
@private
@constant
def to_little_endian_64(value: uint256) -> bytes[8]:
# Reversing bytes using bitwise uint256 manipulations
# Note: array accesses of bytes[] are not currently supported in Vyper
# Note: this function is only called when `value < 2**64`
y: uint256 = 0
x: uint256 = value
for _ in range(8):
y = shift(y, 8)
y = y + bitwise_and(x, 255)
x = shift(x, -8)
return slice(convert(y, bytes32), start=24, len=8)
@public
@constant
def get_deposit_root() -> bytes32:
zero_bytes32: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
node: bytes32 = zero_bytes32
size: uint256 = self.deposit_count
for height in range(DEPOSIT_CONTRACT_TREE_DEPTH):
if bitwise_and(size, 1) == 1: # More gas efficient than `size % 2 == 1`
node = sha256(concat(self.branch[height], node))
else:
node = sha256(concat(node, self.zero_hashes[height]))
size /= 2
return sha256(concat(node, self.to_little_endian_64(self.deposit_count), slice(zero_bytes32, start=0, len=24)))
@public
@constant
def get_deposit_count() -> bytes[8]:
return self.to_little_endian_64(self.deposit_count)
@payable
@public
def deposit(pubkey: bytes[PUBKEY_LENGTH],
withdrawal_credentials: bytes[WITHDRAWAL_CREDENTIALS_LENGTH],
signature: bytes[SIGNATURE_LENGTH],
deposit_data_root: bytes32):
# Avoid overflowing the Merkle tree (and prevent edge case in computing `self.branch`)
assert self.deposit_count < MAX_DEPOSIT_COUNT
# Check deposit amount
deposit_amount: uint256 = msg.value / as_wei_value(1, "gwei")
assert deposit_amount >= MIN_DEPOSIT_AMOUNT
# Length checks to facilitate formal verification (see https://github.com/ethereum/consensus-specs/pull/1362/files#r320361859)
assert len(pubkey) == PUBKEY_LENGTH
assert len(withdrawal_credentials) == WITHDRAWAL_CREDENTIALS_LENGTH
assert len(signature) == SIGNATURE_LENGTH
# Emit `DepositEvent` log
amount: bytes[8] = self.to_little_endian_64(deposit_amount)
log.DepositEvent(pubkey, withdrawal_credentials, amount, signature, self.to_little_endian_64(self.deposit_count))
# Compute deposit data root (`DepositData` hash tree root)
zero_bytes32: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
pubkey_root: bytes32 = sha256(concat(pubkey, slice(zero_bytes32, start=0, len=64 - PUBKEY_LENGTH)))
signature_root: bytes32 = sha256(concat(
sha256(slice(signature, start=0, len=64)),
sha256(concat(slice(signature, start=64, len=SIGNATURE_LENGTH - 64), zero_bytes32)),
))
node: bytes32 = sha256(concat(
sha256(concat(pubkey_root, withdrawal_credentials)),
sha256(concat(amount, slice(zero_bytes32, start=0, len=32 - AMOUNT_LENGTH), signature_root)),
))
# Verify computed and expected deposit data roots match
assert node == deposit_data_root
# Add deposit data root to Merkle tree (update a single `branch` node)
self.deposit_count += 1
size: uint256 = self.deposit_count
for height in range(DEPOSIT_CONTRACT_TREE_DEPTH):
if bitwise_and(size, 1) == 1: # More gas efficient than `size % 2 == 1`
self.branch[height] = node
break
node = sha256(concat(self.branch[height], node))
size /= 2
# !!! DEBUG ONLY !!!
# This method is NOT part of the final ETH2.0 deposit contract, but we use it
# to recover test funds.
@public
def drain():
send(self.drain_address, self.balance)

View File

@ -1,75 +0,0 @@
package depositcontract
import (
"crypto/ecdsa"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
)
var (
amount32Eth = "32000000000000000000"
amountLessThan1Eth = "500000000000000000"
)
// TestAccount represents a test account in the simulated backend,
// through which we can perform actions on the eth1.0 chain.
type TestAccount struct {
Addr common.Address
ContractAddr common.Address
Contract *DepositContract
Backend *backends.SimulatedBackend
TxOpts *bind.TransactOpts
}
// Setup creates the simulated backend with the deposit contract deployed
func Setup() (*TestAccount, error) {
genesis := make(core.GenesisAlloc)
privKey, err := crypto.GenerateKey()
if err != nil {
return nil, err
}
pubKeyECDSA, ok := privKey.Public().(*ecdsa.PublicKey)
if !ok {
return nil, fmt.Errorf("error casting public key to ECDSA")
}
// strip off the 0x and the first 2 characters 04 which is always the EC prefix and is not required.
publicKeyBytes := crypto.FromECDSAPub(pubKeyECDSA)[4:]
var pubKey = make([]byte, 48)
copy(pubKey, publicKeyBytes)
addr := crypto.PubkeyToAddress(privKey.PublicKey)
txOpts, err := bind.NewKeyedTransactorWithChainID(privKey, big.NewInt(1337))
if err != nil {
return nil, err
}
startingBalance, _ := new(big.Int).SetString("100000000000000000000000000000000000000", 10)
genesis[addr] = core.GenesisAccount{Balance: startingBalance}
backend := backends.NewSimulatedBackend(genesis, 210000000000)
contractAddr, _, contract, err := DeployDepositContract(txOpts, backend, addr)
if err != nil {
return nil, err
}
backend.Commit()
return &TestAccount{addr, contractAddr, contract, backend, txOpts}, nil
}
// Amount32Eth returns 32Eth(in wei) in terms of the big.Int type.
func Amount32Eth() *big.Int {
amount, _ := new(big.Int).SetString(amount32Eth, 10)
return amount
}
// LessThan1Eth returns less than 1 Eth(in wei) in terms of the big.Int type.
func LessThan1Eth() *big.Int {
amount, _ := new(big.Int).SetString(amountLessThan1Eth, 10)
return amount
}

View File

@ -3,13 +3,20 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"ETH1logs.go",
"depositContract.go",
"testutils.go",
"contract.go",
"deposit.go",
"logs.go",
"mock.go",
],
importpath = "github.com/prysmaticlabs/prysm/contracts/deposit-contract",
importpath = "github.com/prysmaticlabs/prysm/contracts/deposit",
visibility = ["//visibility:public"],
deps = [
"//beacon-chain/core/helpers:go_default_library",
"//config/features:go_default_library",
"//config/params:go_default_library",
"//crypto/bls:go_default_library",
"//crypto/hash:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"@com_github_ethereum_go_ethereum//:go_default_library",
"@com_github_ethereum_go_ethereum//accounts/abi:go_default_library",
"@com_github_ethereum_go_ethereum//accounts/abi/bind:go_default_library",
@ -27,14 +34,19 @@ go_test(
name = "go_default_test",
size = "medium",
srcs = [
"depositContract_test.go",
"contract_test.go",
"deposit_test.go",
"deposit_tree_test.go",
],
deps = [
":go_default_library",
"//beacon-chain/core/helpers:go_default_library",
"//config/params:go_default_library",
"//container/trie:go_default_library",
"//crypto/bls:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/interop:go_default_library",
"//shared/testutil:go_default_library",
"//shared/testutil/assert:go_default_library",
"//shared/testutil/require:go_default_library",
"@com_github_ethereum_go_ethereum//:go_default_library",

View File

@ -0,0 +1,3 @@
# Validator Deposit Contract Local Copy
This package contains a copy of the official Ethereum [Validator Deposit Contract](https://github.com/ethereum/consensus-specs/tree/e4a9c5fa29def20c4264cd860868f131d6f40e72/solidity_deposit_contract) along with its ABI, bytecode, and Go bindings generated by go-ethereum's [abigen](https://github.com/ethereum/go-ethereum/tree/master/cmd/abigen) `version 1.10.4-stable`. It contains useful test harnesses for setting up and deploying a validator deposit contract using Go bindings, which are used across tests in Prysm.

View File

@ -0,0 +1 @@
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"pubkey","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"withdrawal_credentials","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"amount","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"signature","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"index","type":"bytes"}],"name":"DepositEvent","type":"event"},{"inputs":[{"internalType":"bytes","name":"pubkey","type":"bytes"},{"internalType":"bytes","name":"withdrawal_credentials","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes32","name":"deposit_data_root","type":"bytes32"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"get_deposit_count","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"get_deposit_root","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"}]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,13 @@
package depositcontract_test
package deposit_test
import (
"context"
"encoding/binary"
"math/big"
"testing"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
depositcontract "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
depositcontract "github.com/prysmaticlabs/prysm/contracts/deposit"
"github.com/prysmaticlabs/prysm/runtime/interop"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
@ -82,38 +81,3 @@ func TestValidatorRegister_OK(t *testing.T) {
assert.Equal(t, uint64(1), merkleTreeIndex[1], "Deposit event total desposit count miss matched")
assert.Equal(t, uint64(2), merkleTreeIndex[2], "Deposit event total desposit count miss matched")
}
func TestDrain(t *testing.T) {
testAccount, err := depositcontract.Setup()
require.NoError(t, err)
testAccount.TxOpts.Value = depositcontract.Amount32Eth()
// Generate deposit data
privKeys, pubKeys, err := interop.DeterministicallyGenerateKeys(0 /*startIndex*/, 1)
require.NoError(t, err)
depositDataItems, depositDataRoots, err := interop.DepositDataFromKeys(privKeys, pubKeys)
require.NoError(t, err)
var depositDataRoot [32]byte
copy(depositDataRoot[:], depositDataRoots[0])
_, err = testAccount.Contract.Deposit(testAccount.TxOpts, pubKeys[0].Marshal(), depositDataItems[0].WithdrawalCredentials, depositDataItems[0].Signature, depositDataRoot)
testAccount.Backend.Commit()
require.NoError(t, err, "Validator registration failed")
testAccount.Backend.Commit()
ctx := context.Background()
bal, err := testAccount.Backend.BalanceAt(ctx, testAccount.ContractAddr, nil)
require.NoError(t, err)
require.Equal(t, 0, bal.Cmp(depositcontract.Amount32Eth()), "Deposit did not work")
testAccount.TxOpts.Value = big.NewInt(0)
_, err = testAccount.Contract.Drain(testAccount.TxOpts)
require.NoError(t, err)
testAccount.Backend.Commit()
bal, err = testAccount.Backend.BalanceAt(ctx, testAccount.ContractAddr, nil)
require.NoError(t, err)
assert.Equal(t, 0, big.NewInt(0).Cmp(bal), "Drain did not drain balance")
}

View File

@ -1,6 +1,6 @@
// Package depositutil contains useful functions for dealing
// with Ethereum deposit inputs.
package depositutil
package deposit
import (
"github.com/pkg/errors"

View File

@ -0,0 +1,178 @@
//
//
//
//
//
//
//
//
// SPDX-License-Identifier: CC0-1.0
pragma solidity 0.6.11;
// This interface is designed to be compatible with the Vyper version.
/// @notice This is the Ethereum 2.0 deposit contract interface.
/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
interface IDepositContract {
/// @notice A processed deposit event.
event DepositEvent(
bytes pubkey,
bytes withdrawal_credentials,
bytes amount,
bytes signature,
bytes index
);
/// @notice Submit a Phase 0 DepositData object.
/// @param pubkey A BLS12-381 public key.
/// @param withdrawal_credentials Commitment to a public key for withdrawals.
/// @param signature A BLS12-381 signature.
/// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.
/// Used as a protection against malformed input.
function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
) external payable;
/// @notice Query the current deposit root hash.
/// @return The deposit root hash.
function get_deposit_root() external view returns (bytes32);
/// @notice Query the current deposit count.
/// @return The deposit count encoded as a little endian 64-bit number.
function get_deposit_count() external view returns (bytes memory);
}
// Based on official specification in https://eips.ethereum.org/EIPS/eip-165
interface ERC165 {
/// @notice Query if a contract implements an interface
/// @param interfaceId The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceId` and
/// `interfaceId` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceId) external pure returns (bool);
}
// This is a rewrite of the Vyper Eth2.0 deposit contract in Solidity.
// It tries to stay as close as possible to the original source code.
/// @notice This is the Ethereum 2.0 deposit contract interface.
/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
contract DepositContract is IDepositContract, ERC165 {
uint constant DEPOSIT_CONTRACT_TREE_DEPTH = 32;
// NOTE: this also ensures `deposit_count` will fit into 64-bits
uint constant MAX_DEPOSIT_COUNT = 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1;
bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch;
uint256 deposit_count;
bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] zero_hashes;
constructor() public {
// Compute hashes in empty sparse Merkle tree
for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH - 1; height++)
zero_hashes[height + 1] = sha256(abi.encodePacked(zero_hashes[height], zero_hashes[height]));
}
function get_deposit_root() override external view returns (bytes32) {
bytes32 node;
uint size = deposit_count;
for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) {
if ((size & 1) == 1)
node = sha256(abi.encodePacked(branch[height], node));
else
node = sha256(abi.encodePacked(node, zero_hashes[height]));
size /= 2;
}
return sha256(abi.encodePacked(
node,
to_little_endian_64(uint64(deposit_count)),
bytes24(0)
));
}
function get_deposit_count() override external view returns (bytes memory) {
return to_little_endian_64(uint64(deposit_count));
}
function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
) override external payable {
// Extended ABI length checks since dynamic types are used.
require(pubkey.length == 48, "DepositContract: invalid pubkey length");
require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length");
require(signature.length == 96, "DepositContract: invalid signature length");
// Check deposit amount
require(msg.value >= 1 ether, "DepositContract: deposit value too low");
require(msg.value % 1 gwei == 0, "DepositContract: deposit value not multiple of gwei");
uint deposit_amount = msg.value / 1 gwei;
require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high");
// Emit `DepositEvent` log
bytes memory amount = to_little_endian_64(uint64(deposit_amount));
emit DepositEvent(
pubkey,
withdrawal_credentials,
amount,
signature,
to_little_endian_64(uint64(deposit_count))
);
// Compute deposit data root (`DepositData` hash tree root)
bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, bytes16(0)));
bytes32 signature_root = sha256(abi.encodePacked(
sha256(abi.encodePacked(signature[:64])),
sha256(abi.encodePacked(signature[64:], bytes32(0)))
));
bytes32 node = sha256(abi.encodePacked(
sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)),
sha256(abi.encodePacked(amount, bytes24(0), signature_root))
));
// Verify computed and expected deposit data roots match
require(node == deposit_data_root, "DepositContract: reconstructed DepositData does not match supplied deposit_data_root");
// Avoid overflowing the Merkle tree (and prevent edge case in computing `branch`)
require(deposit_count < MAX_DEPOSIT_COUNT, "DepositContract: merkle tree full");
// Add deposit data root to Merkle tree (update a single `branch` node)
deposit_count += 1;
uint size = deposit_count;
for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) {
if ((size & 1) == 1) {
branch[height] = node;
return;
}
node = sha256(abi.encodePacked(branch[height], node));
size /= 2;
}
// As the loop should always end prematurely with the `return` statement,
// this code should be unreachable. We assert `false` just to be safe.
assert(false);
}
function supportsInterface(bytes4 interfaceId) override external pure returns (bool) {
return interfaceId == type(ERC165).interfaceId || interfaceId == type(IDepositContract).interfaceId;
}
function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) {
ret = new bytes(8);
bytes8 bytesValue = bytes8(value);
// Byteswapping during copying to bytes.
ret[0] = bytesValue[7];
ret[1] = bytesValue[6];
ret[2] = bytesValue[5];
ret[3] = bytesValue[4];
ret[4] = bytesValue[3];
ret[5] = bytesValue[2];
ret[6] = bytesValue[1];
ret[7] = bytesValue[0];
}
}

View File

@ -1,13 +1,13 @@
package depositutil_test
package deposit_test
import (
"testing"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/contracts/deposit"
"github.com/prysmaticlabs/prysm/crypto/bls"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/depositutil"
"github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
@ -19,7 +19,7 @@ func TestDepositInput_GeneratesPb(t *testing.T) {
k2, err := bls.RandKey()
require.NoError(t, err)
result, _, err := depositutil.DepositInput(k1, k2, 0)
result, _, err := deposit.DepositInput(k1, k2, 0)
require.NoError(t, err)
assert.DeepEqual(t, k1.PublicKey().Marshal(), result.PublicKey)
@ -46,29 +46,29 @@ func TestDepositInput_GeneratesPb(t *testing.T) {
func TestVerifyDepositSignature_ValidSig(t *testing.T) {
deposits, _, err := testutil.DeterministicDepositsAndKeys(1)
require.NoError(t, err)
deposit := deposits[0]
dep := deposits[0]
domain, err := helpers.ComputeDomain(
params.BeaconConfig().DomainDeposit,
params.BeaconConfig().GenesisForkVersion,
params.BeaconConfig().ZeroHash[:],
)
require.NoError(t, err)
err = depositutil.VerifyDepositSignature(deposit.Data, domain)
err = deposit.VerifyDepositSignature(dep.Data, domain)
require.NoError(t, err)
}
func TestVerifyDepositSignature_InvalidSig(t *testing.T) {
deposits, _, err := testutil.DeterministicDepositsAndKeys(1)
require.NoError(t, err)
deposit := deposits[0]
dep := deposits[0]
domain, err := helpers.ComputeDomain(
params.BeaconConfig().DomainDeposit,
params.BeaconConfig().GenesisForkVersion,
params.BeaconConfig().ZeroHash[:],
)
require.NoError(t, err)
deposit.Data.Signature = deposit.Data.Signature[1:]
err = depositutil.VerifyDepositSignature(deposit.Data, domain)
dep.Data.Signature = dep.Data.Signature[1:]
err = deposit.VerifyDepositSignature(dep.Data, domain)
if err == nil {
t.Fatal("Deposit Verification succeeds with a invalid signature")
}

View File

@ -1,4 +1,4 @@
package depositcontract_test
package deposit_test
import (
"strconv"
@ -7,7 +7,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/container/trie"
depositcontract "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
depositcontract "github.com/prysmaticlabs/prysm/contracts/deposit"
"github.com/prysmaticlabs/prysm/runtime/interop"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"

View File

@ -1,4 +1,4 @@
package depositcontract
package deposit
import (
"bytes"

93
contracts/deposit/mock.go Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,32 +0,0 @@
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["deposit.go"],
importpath = "github.com/prysmaticlabs/prysm/shared/depositutil",
visibility = ["//visibility:public"],
deps = [
"//beacon-chain/core/helpers:go_default_library",
"//config/features:go_default_library",
"//config/params:go_default_library",
"//crypto/bls:go_default_library",
"//crypto/hash:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"@com_github_pkg_errors//:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["deposit_test.go"],
deps = [
":go_default_library",
"//beacon-chain/core/helpers:go_default_library",
"//config/params:go_default_library",
"//crypto/bls:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//shared/testutil:go_default_library",
"//shared/testutil/assert:go_default_library",
"//shared/testutil/require:go_default_library",
],
)

View File

@ -19,7 +19,7 @@ go_library(
"//cmd/validator/flags:go_default_library",
"//config/features:go_default_library",
"//config/params:go_default_library",
"//contracts/deposit-contract:go_default_library",
"//contracts/deposit:go_default_library",
"//shared/bytesutil:go_default_library",
"//shared/testutil:go_default_library",
"//testing/endtoend/helpers:go_default_library",

View File

@ -20,7 +20,7 @@ import (
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/prysmaticlabs/prysm/config/params"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit"
"github.com/prysmaticlabs/prysm/testing/endtoend/helpers"
e2e "github.com/prysmaticlabs/prysm/testing/endtoend/params"
e2etypes "github.com/prysmaticlabs/prysm/testing/endtoend/types"
@ -133,7 +133,7 @@ func (node *Eth1Node) Start(ctx context.Context) error {
}
txOpts.Nonce = big.NewInt(int64(nonce))
txOpts.Context = context.Background()
contractAddr, tx, _, err := contracts.DeployDepositContract(txOpts, web3, txOpts.From)
contractAddr, tx, _, err := contracts.DeployDepositContract(txOpts, web3)
if err != nil {
return fmt.Errorf("failed to deploy deposit contract: %w", err)
}

View File

@ -21,7 +21,7 @@ import (
"github.com/prysmaticlabs/prysm/cmd/validator/flags"
"github.com/prysmaticlabs/prysm/config/features"
"github.com/prysmaticlabs/prysm/config/params"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/testing/endtoend/helpers"

View File

@ -7,11 +7,10 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/tools/deployContract",
visibility = ["//visibility:private"],
deps = [
"//contracts/deposit-contract:go_default_library",
"//contracts/deposit:go_default_library",
"//runtime/version: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",

View File

@ -11,11 +11,10 @@ import (
"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"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit"
"github.com/prysmaticlabs/prysm/runtime/version"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
@ -148,18 +147,12 @@ func main() {
txOps.Context = context.Background()
}
drain := txOps.From
if drainAddress != "" {
drain = common.HexToAddress(drainAddress)
}
txOps.GasPrice = big.NewInt(10 * 1e9 /* 10 gwei */)
// Deploy validator registration contract
addr, tx, _, err := contracts.DeployDepositContract(
txOps,
client,
drain,
)
if err != nil {

View File

@ -1,30 +0,0 @@
load("@prysm//tools/go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_binary")
go_library(
name = "go_default_library",
srcs = ["drainContracts.go"],
importpath = "github.com/prysmaticlabs/prysm/tools/drainContracts",
visibility = ["//visibility:private"],
deps = [
"//contracts/deposit-contract:go_default_library",
"//runtime/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_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_urfave_cli_v2//:go_default_library",
"@com_github_x_cray_logrus_prefixed_formatter//:go_default_library",
],
)
go_binary(
name = "drainContracts",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)

View File

@ -1,39 +0,0 @@
## 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
```

View File

@ -1,227 +0,0 @@
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"
"github.com/pkg/errors"
contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
"github.com/prysmaticlabs/prysm/runtime/version"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
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.App{}
app.Name = "drainContracts"
app.Usage = "this is a util to drain all (testing) deposit contracts of their ETH."
app.Version = version.Version()
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) error {
// 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 {
return err
}
client := ethclient.NewClient(rpcClient)
// User inputs private key, sign tx with private key
if privKeyString != "" {
privKey, err := crypto.HexToECDSA(privKeyString)
if err != nil {
return err
}
txOps, err = bind.NewKeyedTransactorWithChainID(privKey, big.NewInt(1337))
if err != nil {
return err
}
txOps.Value = big.NewInt(0)
txOps.GasLimit = 4000000
txOps.Context = context.Background()
nonce, err := client.NonceAt(context.Background(), crypto.PubkeyToAddress(privKey.PublicKey), nil)
if err != nil {
return errors.Wrap(err, "could not get account nonce")
}
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 {
return err
}
privKey, err := keystore.DecryptKey(keyJSON, password)
if err != nil {
return err
}
txOps, err = bind.NewKeyedTransactorWithChainID(privKey.PrivateKey, big.NewInt(1337))
if err != nil {
return err
}
txOps.Value = big.NewInt(0)
txOps.GasLimit = 4000000
txOps.Context = context.Background()
nonce, err := client.NonceAt(context.Background(), privKey.Address, nil)
if err != nil {
return 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 {
return errors.Wrap(err, "Could not get all deposit contract address")
}
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 {
return 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)
}
return nil
}
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)
// Hash of deposit log signature
// DepositEvent: event({
// pubkey: bytes[48],
// withdrawal_credentials: bytes[32],
// amount: bytes[8],
// signature: bytes[96],
// index: bytes[8],
// })
depositTopicHash := common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5")
fmt.Println(depositTopicHash.Hex())
query := ethereum.FilterQuery{
Addresses: []common.Address{},
Topics: [][]common.Hash{
{depositTopicHash},
},
FromBlock: big.NewInt(800000), // Contracts before this may not have drain().
}
logs, err := client.FilterLogs(context.Background(), query)
if err != nil {
return nil, errors.Wrap(err, "could not get all deposit logs")
}
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
}