Merge pull request #7 from poanetwork/v2-contracts
Update to V2 contracts
This commit is contained in:
commit
41332c21dd
|
@ -2,3 +2,4 @@
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
.env
|
.env
|
||||||
|
/logs
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
language: rust
|
language: rust
|
||||||
|
|
||||||
rust:
|
rust:
|
||||||
- nightly
|
- stable
|
||||||
|
|
||||||
cache: cargo
|
cache: cargo
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- RUST_BACKTRACE=1 cargo build
|
- RUST_BACKTRACE=1 cargo build
|
||||||
|
|
||||||
|
|
37
Cargo.toml
37
Cargo.toml
|
@ -1,24 +1,27 @@
|
||||||
[package]
|
[package]
|
||||||
name = "poagov"
|
name = "poagov"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
authors = ["Peter van Nostrand <jnz@riseup.net>"]
|
license = "GPL-3.0"
|
||||||
|
authors = ["DrPeterVanNostrand <jnz@riseup.net>"]
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4"
|
chrono = "0.4.6"
|
||||||
clap = "2.31.2"
|
clap = "2.32.0"
|
||||||
dotenv = "0.11.0"
|
ctrlc = "3.1.1"
|
||||||
ethabi = "5.1.1"
|
dotenv = "0.13.0"
|
||||||
ethereum-types = "0.3.1"
|
ethabi = "6.0.1"
|
||||||
hex = "0.3.1"
|
ethereum-types = "0.4.0"
|
||||||
|
failure = "0.1.2"
|
||||||
|
hex = "0.3.2"
|
||||||
jsonrpc-core = "8.0.1"
|
jsonrpc-core = "8.0.1"
|
||||||
lazy_static = "1.0.0"
|
# TODO: after `lettre` and `lettre_email` v0.9.x have been published to crates.io, remove these
|
||||||
lettre = "0.8"
|
# GitHub dependencies.
|
||||||
lettre_email = "0.8"
|
lettre = { git = "https://github.com/lettre/lettre.git" }
|
||||||
native-tls = "0.1.5"
|
lettre_email = { git = "https://github.com/lettre/lettre.git" }
|
||||||
reqwest = "0.8.5"
|
native-tls = "0.2"
|
||||||
serde = "1.0.36"
|
reqwest = "0.8.8"
|
||||||
serde_derive = "1.0.36"
|
serde_json = "1.0.27"
|
||||||
serde_json = "1.0.13"
|
slog = { version = "2.3.3", features = ["release_max_level_trace"] }
|
||||||
slog = { version = "2.2.3", features = ["release_max_level_trace"] }
|
|
||||||
slog-term = "2.4.0"
|
slog-term = "2.4.0"
|
||||||
web3 = "0.3.0"
|
web3 = "0.4.0"
|
||||||
|
|
249
README.md
249
README.md
|
@ -1,12 +1,20 @@
|
||||||
[![Build Status](https://travis-ci.org/poanetwork/poa-governance-notifications.svg?branch=master)](https://travis-ci.org/poanetwork/poa-governance-notifications)
|
[![Build Status](https://travis-ci.org/poanetwork/poa-governance-notifications.svg?branch=master)](https://travis-ci.org/poanetwork/poa-governance-notifications)
|
||||||
|
|
||||||
# About
|
# `poa-governance-notifications`
|
||||||
|
|
||||||
A tool to monitor a POA Network blockchain for
|
A tool to monitor a POA Network blockchain for
|
||||||
[governance events](https://github.com/poanetwork/wiki/wiki/Governance-Overview).
|
[governance events](https://github.com/poanetwork/wiki/wiki/Governance-Overview).
|
||||||
|
|
||||||
The `poagov` command line tool is distributed as a binary for Linux and
|
The `poagov` command line tool is distributed as a binary for Linux and
|
||||||
OSX, or it can be built from source.
|
OSX; it can also be built from source for both platforms.
|
||||||
|
|
||||||
|
You can find the source code for the currently deployed governance contracts
|
||||||
|
[here](https://github.com/poanetwork/poa-network-consensus-contracts/tree/master/contracts).
|
||||||
|
|
||||||
|
You can find the addresses for governance contracts currently deployed to Core
|
||||||
|
[here](https://github.com/poanetwork/poa-chain-spec/blob/core/contracts.json)
|
||||||
|
and Sokol
|
||||||
|
[here](https://github.com/poanetwork/poa-chain-spec/blob/sokol/contracts.json).
|
||||||
|
|
||||||
# Installing the `poagov` Binary
|
# Installing the `poagov` Binary
|
||||||
|
|
||||||
|
@ -16,23 +24,26 @@ section in this README to find out how to download it.
|
||||||
|
|
||||||
On Debian/Ubuntu:
|
On Debian/Ubuntu:
|
||||||
|
|
||||||
$ curl -OL https://github.com/poanetwork/poa-governance-notifications/releases/download/v0.1.0/poagov-0.1.0-linux-x86_64.tar.gz
|
$ curl -OL https://github.com/poanetwork/poa-governance-notifications/releases/download/v1.0.0/poagov-1.0.0-linux-x86_64.tar.gz
|
||||||
$ tar -xvzf poagov-0.1.0-linux-x86_64.tar.gz
|
$ tar -xvzf poagov-1.0.0-linux-x86_64.tar.gz
|
||||||
$ rm poagov-0.1.0-linux-x86_64.tar.gz
|
$ rm poagov-1.0.0-linux-x86_64.tar.gz
|
||||||
$ cd poagov
|
$ cd poagov
|
||||||
|
$ cp sample.env .env
|
||||||
$ chmod +x poagov
|
$ chmod +x poagov
|
||||||
$ ./poagov --help
|
$ ./poagov --help
|
||||||
|
|
||||||
On OSX:
|
On OSX:
|
||||||
|
|
||||||
$ curl -OL https://github.com/poanetwork/poa-governance-notifications/releases/download/v0.1.0/poagov-0.1.0-osx-x86_64.tar.gz
|
$ curl -OL https://github.com/poanetwork/poa-governance-notifications/releases/download/v1.0.0/poagov-1.0.0-osx-x86_64.tar.gz
|
||||||
$ tar -xvzf poagov-0.1.0-osx-x86_64.tar.gz
|
$ tar -xvzf poagov-1.0.0-osx-x86_64.tar.gz
|
||||||
$ rm poagov-0.1.0-osx-x86_64.tar.gz
|
$ rm poagov-1.0.0-osx-x86_64.tar.gz
|
||||||
$ cd poagov
|
$ cd poagov
|
||||||
|
$ cp sample.env .env
|
||||||
$ chmod +x poagov
|
$ chmod +x poagov
|
||||||
$ ./poagov --help
|
$ ./poagov --help
|
||||||
|
|
||||||
# Building the `poagov` Binary from Source
|
|
||||||
|
# Building `poagov` from Source
|
||||||
|
|
||||||
To build the `poagov` CLI tool, run the following:
|
To build the `poagov` CLI tool, run the following:
|
||||||
|
|
||||||
|
@ -40,19 +51,23 @@ To build the `poagov` CLI tool, run the following:
|
||||||
$ cd poa-governance-notifications
|
$ cd poa-governance-notifications
|
||||||
$ cargo build --release
|
$ cargo build --release
|
||||||
|
|
||||||
### Requires Rust Nightly
|
`poagov` can be built using Rust 1.29 stable and requires `libssl` to be
|
||||||
|
installed; see the following "Requires libssl" section for more information.
|
||||||
|
|
||||||
`poagov` uses experimental Rust features that are currently only available
|
Building `poagov` requires Rust `1.29.0-stable` or later and `libssl`; see the
|
||||||
in Rust version >= 1.26.0-nightly. You can check which version of Rust that
|
"Requires `libssl`" section for more information.
|
||||||
you are using by running:
|
|
||||||
|
|
||||||
$ rustc --version
|
### Testing
|
||||||
|
|
||||||
If you are not using Rust >= 1.26.0-nightly, you can switch to it using:
|
You can run `poagov`'s tests to ensure that it everything is working properly:
|
||||||
|
|
||||||
$ rustup default nightly
|
$ cargo test --release
|
||||||
|
|
||||||
### Requires libssl
|
The test suite will verify: that the required env-vars are found the `.env`
|
||||||
|
file, that each network's JSON-RPC server can be reached, and that each
|
||||||
|
contract ABI can be loaded.
|
||||||
|
|
||||||
|
# Requires `libssl`
|
||||||
|
|
||||||
SMTP over TLS requires that you have a native TLS library installed on your
|
SMTP over TLS requires that you have a native TLS library installed on your
|
||||||
machine, the preferred library for Linux and OSX is OpenSSL >= 1.0.1,
|
machine, the preferred library for Linux and OSX is OpenSSL >= 1.0.1,
|
||||||
|
@ -67,11 +82,11 @@ If running `cargo build --release` panics with an error like:
|
||||||
Could not find directory of OpenSSL installation
|
Could not find directory of OpenSSL installation
|
||||||
..."
|
..."
|
||||||
|
|
||||||
you probably do not have libssl installed.
|
you probably do not have `libssl` installed.
|
||||||
|
|
||||||
To install libssl on Debian/Ubuntu run the following:
|
To install `libssl` on Debian/Ubuntu run the following:
|
||||||
|
|
||||||
$ sudo apt-get update -y
|
$ sudo apt update
|
||||||
$ sudo apt-get install -y pkg-config libssl-dev
|
$ sudo apt-get install -y pkg-config libssl-dev
|
||||||
|
|
||||||
To install libssl on MacOS run the following:
|
To install libssl on MacOS run the following:
|
||||||
|
@ -85,76 +100,136 @@ Then try to rebuild `poagov` using:
|
||||||
$ cargo build --release
|
$ cargo build --release
|
||||||
|
|
||||||
If you are on OSX and installed OpenSSL using Homebrew and continue to get
|
If you are on OSX and installed OpenSSL using Homebrew and continue to get
|
||||||
compilation errors for any of the Rust crates: openssl, openssl-sys, or
|
compilation errors for any of the Rust crates: `openssl`, `openssl-sys`, or
|
||||||
openssl-sys-extras, try building with the following:
|
`openssl-sys-extras`, try building with the following:
|
||||||
|
|
||||||
$ cargo clean
|
$ cargo clean
|
||||||
$ OPENSSL_INCLUDE_DIR=$(brew --prefix openssl)/include \
|
$ OPENSSL_INCLUDE_DIR=$(brew --prefix openssl)/include \
|
||||||
OPENSSL_LIB_DIR=$(brew --prefix openssl)/lib \
|
OPENSSL_LIB_DIR=$(brew --prefix openssl)/lib \
|
||||||
cargo build
|
cargo build --release
|
||||||
|
|
||||||
There is a known issue regarding the openssl-sys crate not being able to
|
There is a known issue regarding the `openssl-sys` crate not being able to
|
||||||
find libssl installed with Homebrew on OSX that is well documented on
|
find `libssl` installed with Homebrew on OSX; more information can be found on
|
||||||
[Stack Overflow](https://stackoverflow.com/questions/34612395/openssl-crate-fails-compilation-on-mac-os-x-10-11/34615626#34615626).
|
[Stack Overflow](https://stackoverflow.com/questions/34612395/openssl-crate-fails-compilation-on-mac-os-x-10-11/34615626#34615626).
|
||||||
The above solution comes from the linked Stack Overflow thread.
|
The above solution comes from the linked Stack Overflow thread.
|
||||||
|
|
||||||
More information on common issues encountered while installing the
|
More information on common issues encountered while installing the
|
||||||
openssl Rust crate can be found [here](https://crates.io/crates/openssl).
|
`openssl` Rust crate can be found [here](https://crates.io/crates/openssl).
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
Once you have built `poagov`, you can print out the CLI usage by running:
|
Once you have built or downloaded `poagov`, you can print out the CLI usage by
|
||||||
|
running:
|
||||||
|
|
||||||
$ ./target/release/poagov --help
|
# If you downloaded the `poagov` binary run:
|
||||||
|
$ poagov --help
|
||||||
|
# Or, if you built `poagov` from source run:
|
||||||
|
$ target/release/poagov --help
|
||||||
|
|
||||||
poagov 1.0
|
poagov 1.0.0
|
||||||
Monitores the POA Network's blockchain for governance events.
|
Monitors a POA Network blockchain for governance events.
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
poagov [FLAGS] [OPTIONS]
|
poagov [FLAGS] [OPTIONS]
|
||||||
|
|
||||||
FLAGS:
|
FLAGS:
|
||||||
--core monitor voting contracts deployed to the Core network (same as using --network=core)
|
--core monitor voting contracts deployed to the Core network
|
||||||
--earliest start monitoring for goverance events starting from the first block in the chain
|
--earliest begin monitoring for governance events starting at the first block in the blockchain
|
||||||
--email send governance notifications via email
|
--email enables email notifications (SMTP configurations must be set in your `.env` file)
|
||||||
-h, --help prints help information
|
-e, --emission monitors the blockchain for ballots to manage emission funds
|
||||||
-k monitor the blockchain for ballots to change keys (same as --monitor=keys)
|
-h, --help prints help information
|
||||||
--latest start monitoring for goverance events starting from the most recently mined block in the chain
|
-k, --keys monitors the blockchain for ballots to change keys
|
||||||
--local monitor voting contracts deployed to a locally running POA chain (same as using --network=local)
|
--latest begin monitoring for governance events starting at the last block mined
|
||||||
-p monitor the change for ballots to change the proxy address (same as --monitor=proxy)
|
-p, --proxy monitors the blockchain for ballots to change the proxy address
|
||||||
--push send governance notifications via push notification
|
--sokol monitor voting contracts deployed to the Sokol network
|
||||||
--sokol monitor voting contracts deployed to the Sokol test network (same as using --network=sokol)
|
-t, --threshold monitors the blockchain for ballots to change the minimum threshold
|
||||||
-t monitor the chain for ballots to change the minimum threshold (same as --monitor=threshold)
|
--v1 monitors the v1 voting contracts
|
||||||
-V, --version prints version information
|
--v2 monitors the v2 voting contracts
|
||||||
|
-V, --version prints version information
|
||||||
|
--log-emails logs each notification's email body; does not require the --email flag to be set
|
||||||
|
--log-file logs are written to files in the ./logs directory, log files are rotated when they reach a size of 4MB
|
||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
--block-time <value> the average time it takes to mine a new block
|
--block-time <value> the average number of seconds it takes to mine a new block
|
||||||
--monitor <value> a comma-separated list of ballot types to monitor for governance events; the available values are: keys, threshold, proxy
|
-n, --limit <value> shutdown `poagov` after this many notifications have been generated, useful when testing
|
||||||
--network <value> the name of the network to monitor for ballots; the values available for this option are: core, sokol, local
|
--start <value> start monitoring for governance events at this block (inclusive)
|
||||||
--rpc <value> the URL for the RPC endpoint
|
--tail <value> start monitoring for governance events for the `n` blocks prior to the last block mined
|
||||||
--start <value> start monitoring for governance events at this block (inclusive)
|
|
||||||
--tail <value> start monitoring for governance events for the `n` blocks prior to the last mined block in the chain
|
|
||||||
|
|
||||||
# Setting up the Config File
|
Hitting `[ctrl-c]` while `poagov` is running will cause the process to gracefully shutdown.
|
||||||
|
|
||||||
|
### Required Arguments
|
||||||
|
|
||||||
|
Each time you run `poagov`, four CLI arguments are required:
|
||||||
|
|
||||||
|
1. The chain (specify only one): `--core`, `--sokol`.
|
||||||
|
2. The contracts to monitor (specify at least one): `--keys`, `--threshold`, `--proxy`, `--emission`.
|
||||||
|
2. The hardfork version (specify only one): `--v1`, `--v2`.
|
||||||
|
4. The block in the chain from where to start monitoring (specify only one): `--earliest`, `--latest`, `--start=<block_number>`, `--tail=<value>`.
|
||||||
|
|
||||||
|
### Notes on the hardfork version options `--v1` and `--v2`
|
||||||
|
|
||||||
|
`--v1` indicates that you want to monitor for governance events prior to the
|
||||||
|
Sokol and Core hardforks that will occur in September-2018 and November-2018
|
||||||
|
respectively.
|
||||||
|
|
||||||
|
`--v2` indicates that you want to monitor for governance events that occured
|
||||||
|
after the above hardfork dates.
|
||||||
|
|
||||||
|
- More information regarding the planned hardforks for the Sokol and Core
|
||||||
|
chains in September and November 2018 can be found
|
||||||
|
[here](https://medium.com/poa-network/poa-network-news-and-updates-36-2e6e00550c15).
|
||||||
|
|
||||||
|
### Optional Arguments
|
||||||
|
|
||||||
|
Providing the `--email` flag will enable governance notification emails. To use
|
||||||
|
this option, you must first configure SMTP in your `.env`.
|
||||||
|
|
||||||
|
Providing the `--block-time=<value>` will set how often `poagov` will query the
|
||||||
|
blockchain for new governance events. Defaults to 30 seconds.
|
||||||
|
|
||||||
|
Providing the `--log-emails` flag will print the full text for a notification
|
||||||
|
email to stderr when governance events are found. When this option is set,
|
||||||
|
email text will be logged regardless of whether or not the `--email` flag is
|
||||||
|
set.
|
||||||
|
|
||||||
|
Setting the `--log-file` flag will write logs to a file in the `./logs/`
|
||||||
|
directory. Logs are rotated chronologically across three files. Once the
|
||||||
|
`logs` directory has reached its max number of files, the oldest log file will
|
||||||
|
be deleted to make room for the next log file. Log files have a max size of
|
||||||
|
4MB; the log files will rotated once the current log file has reached the max
|
||||||
|
file size.
|
||||||
|
|
||||||
|
Setting the `--limit=<value>` option will cause `poagov` to stop once `value`
|
||||||
|
number of notifications have been generated. This option is useful when testing.
|
||||||
|
|
||||||
|
# Setting up the `.env` File
|
||||||
|
|
||||||
When the `poagov` CLI tool is run, the process' environment variables are
|
When the `poagov` CLI tool is run, the process' environment variables are
|
||||||
loaded via a `.env` file. An example `.env` file can be found at
|
loaded via an `.env` file.
|
||||||
`sample.env`. Before running this tool change the name of `sample.env` to
|
|
||||||
`.env` using:
|
|
||||||
|
|
||||||
$ mv sample.env .env
|
The `.env` file contains configuration variables that are not specified via the
|
||||||
|
command line. You are required to have an `.env` file in the same directory as
|
||||||
|
your `Cargo.toml` or `poagov` binary.
|
||||||
|
|
||||||
This will enable `poagov's` default configuration. You can update your
|
When building from source, the `sample.env` file will be copied into the `.env`
|
||||||
config file to allow `poagov` to use a locally running chain, governance
|
file. This `.env` file will contain the default configuration values required
|
||||||
contracts that you have deployed locally, and to setup email
|
to run `poagov`.
|
||||||
notifications.
|
|
||||||
|
|
||||||
# Setting up Email Notifications
|
If you did not build `poagov` from source, you will have to create an `.env`
|
||||||
|
file in the same directory as the `poagov` binary; then copy the contents of
|
||||||
|
`sample.env` into it.
|
||||||
|
|
||||||
|
If you wish to enable email notifications, you must add the required SMTP
|
||||||
|
config values to your `.env` file. See the "Setting up Email Notifications"
|
||||||
|
section for details.
|
||||||
|
|
||||||
|
### Setting up Email Notifications
|
||||||
|
|
||||||
In order to enable email notifications, you must change the name of the
|
In order to enable email notifications, you must change the name of the
|
||||||
`sample.env` file to `.env`. Then, you must add values for the following
|
`sample.env` file to `.env`. Then, you must add values for the following
|
||||||
SMTP config options in your `.env` file:
|
SMTP config options in your `.env` file:
|
||||||
|
|
||||||
|
EMAIL_RECIPIENTS=
|
||||||
SMTP_HOST_DOMAIN=
|
SMTP_HOST_DOMAIN=
|
||||||
SMTP_PORT=
|
SMTP_PORT=
|
||||||
SMTP_USERNAME=
|
SMTP_USERNAME=
|
||||||
|
@ -172,36 +247,44 @@ but you may use port 465 for TLS, or any other port that your outgoing
|
||||||
email server is lisening for secure connections. If you require unencrypted
|
email server is lisening for secure connections. If you require unencrypted
|
||||||
SMTP, submit an issue and I can add it.
|
SMTP, submit an issue and I can add it.
|
||||||
|
|
||||||
|
Your SMTP configuration should look something like the following:
|
||||||
|
|
||||||
|
EMAIL_RECIPIENTS=alice@poa.network,bob@poa.network
|
||||||
|
SMTP_HOST_DOMAIN=mail.riseup.net
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_USERNAME=evariste_galois
|
||||||
|
SMTP_PASSWORD='finteFIELDS#$!'
|
||||||
|
OUTGOING_EMAIL_ADDRESS=evariste_galois@riseup.net
|
||||||
|
|
||||||
# An Explained Example
|
# An Explained Example
|
||||||
|
|
||||||
$ ./target/release/poagov --sokol --earliest -kt --email
|
$ poagov --sokol --v1 -kt --earliest --email --log-emails --limit=1
|
||||||
|
|
||||||
- `--sokol` is used to monitor contracts deployed to POA's test network.
|
- `--sokol` monitors the Sokol chain.
|
||||||
- `--earliest` starts monitoring from the first block in the blockchain.
|
- `--v1` monitors the governance contracts deployed prior to September-2018.
|
||||||
- `-k` get notifications for ballots to change keys.
|
- `-k` monitors the `VotingToChangeKeys` contract.
|
||||||
- `-t` get notifications for ballots to change the min threshold.
|
- `-t` monitors the `VotingToChangeMinThreshold` contract.
|
||||||
- `--email` sends out email notifications to each address in the
|
- `--earliest` start monitoring from the first block in the blockchain.
|
||||||
"VALIDATORS" config value (located in the .env file).
|
- `--email` sends out email notifications to each address in the `EMAIL_RECIPIENTS` env-var.
|
||||||
|
- `--log-emails` for each governance notification generated, log the corresponding email body.
|
||||||
Press [ctrl-c] to exit `poagov`.
|
- `--limit=1` stop running `poagov` after one ballot notification has been generated.
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
|
|
||||||
Logs are output to stderr. Any notifications that were generated, sent,
|
Logs are output to `stderr` unless the `--log-file` CLI flag is set. Events
|
||||||
or failed to be sent will be logged. The following is an example log for a
|
that are logged include: the generation of governance notifications, sending an
|
||||||
a notification for a ballot to change the min threshold that was generated
|
email successesfully or failing to send an email, aned what range of blocks
|
||||||
using the command `$ poagov --earliest -t`:
|
from the chain have been successfully monitored for governance events.
|
||||||
|
Optionally, you can log the email body for each governance notification
|
||||||
|
generated by setting the `--log-emails` CLI flag.
|
||||||
|
|
||||||
Apr 21 08:31:54.219 INFO notification, data: ThresholdNotification {
|
The following is an example command with its corresponding logs:
|
||||||
network: Sokol,
|
|
||||||
endpoint: "https://sokol.poa.network",
|
$ poagov --sokol --v1 --threshold --earliest --limit=3
|
||||||
block_number: 1078816,
|
|
||||||
contract_type: Threshold,
|
Oct 10 15:18:09.863 INFO starting poagov...
|
||||||
ballot_type: ChangeMinThreshold,
|
Oct 10 15:18:10.287 INFO governance notification, block_number: 525296, ballot_id: 0, ballot: Threshold
|
||||||
ballot_id: 2,
|
Oct 10 15:18:10.287 INFO governance notification, block_number: 599789, ballot_id: 1, ballot: Threshold
|
||||||
start_time: 2018-02-23T05:28:22Z,
|
Oct 10 15:18:10.287 INFO governance notification, block_number: 1078816, ballot_id: 2, ballot: Threshold
|
||||||
end_time: 2018-02-25T05:33:00Z,
|
Oct 10 15:18:10.287 WARN reached notification limit, gracefully shutting down..., limit: 3
|
||||||
memo: "*TEST* ballot to increase the consensus threshold to 51% (rounded to the higher integer) of the total number of validators. The idea is to legitimize passing the ballot by the majority participation.",
|
|
||||||
proposed_value: 4
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,665 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "finalize",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getIsFinalized",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_votingKey",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "isValidVote",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "getBallotsStorage",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "activeBallots",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getTotalVoters",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "getTime",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_startTime",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_endTime",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_proposedValue",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_contractType",
|
|
||||||
"type": "uint8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "memo",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "createBallotToChangeProxyAddress",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_miningKey",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "withinLimit",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_votingKey",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getMiningByVotingKey",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "activeBallotsLength",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getMemo",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "isActive",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getEndTime",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_choice",
|
|
||||||
"type": "uint8"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "vote",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "getKeysManager",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "proxyStorage",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "maxOldMiningKeysDeepCheck",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint8"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getStartTime",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getContractType",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getMinThresholdOfVoters",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_votingKey",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "hasAlreadyVoted",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "getGlobalMinThresholdOfVoters",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_miningKey",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "areOldMiningKeysVoted",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getProposedValue",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "nextBallotId",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getProgress",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "int256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "validatorActiveBallots",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "getBallotLimitPerValidator",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "votingState",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "startTime",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "endTime",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "totalVoters",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "progress",
|
|
||||||
"type": "int256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "isFinalized",
|
|
||||||
"type": "bool"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "quorumState",
|
|
||||||
"type": "uint8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "index",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "minThresholdOfVoters",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "proposedValue",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "contractType",
|
|
||||||
"type": "uint8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "creator",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "memo",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_proxyStorage",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "constructor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"name": "id",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": false,
|
|
||||||
"name": "decision",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"name": "voter",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": false,
|
|
||||||
"name": "time",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "Vote",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"name": "id",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"name": "voter",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "BallotFinalized",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"name": "id",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"name": "ballotType",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"name": "creator",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "BallotCreated",
|
|
||||||
"type": "event"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -770,4 +770,4 @@
|
||||||
"name": "BallotCreated",
|
"name": "BallotCreated",
|
||||||
"type": "event"
|
"type": "event"
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -635,4 +635,4 @@
|
||||||
"name": "BallotCreated",
|
"name": "BallotCreated",
|
||||||
"type": "event"
|
"type": "event"
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -662,4 +662,4 @@
|
||||||
"name": "BallotCreated",
|
"name": "BallotCreated",
|
||||||
"type": "event"
|
"type": "event"
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -1,4 +1,37 @@
|
||||||
[
|
[
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "canBeFinalizedNow",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "minBallotDuration",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"constant": false,
|
"constant": false,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -13,32 +46,6 @@
|
||||||
"stateMutability": "nonpayable",
|
"stateMutability": "nonpayable",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_startTime",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_endTime",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_proposedValue",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "memo",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "createBallotToChangeThreshold",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -47,11 +54,11 @@
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "getIsFinalized",
|
"name": "getQuorumState",
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"type": "bool"
|
"type": "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"payable": false,
|
"payable": false,
|
||||||
|
@ -81,25 +88,11 @@
|
||||||
"stateMutability": "view",
|
"stateMutability": "view",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "getBallotsStorage",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "_index",
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -116,17 +109,12 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [
|
"inputs": [],
|
||||||
{
|
"name": "initDisabled",
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getTotalVoters",
|
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"type": "uint256"
|
"type": "bool"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"payable": false,
|
"payable": false,
|
||||||
|
@ -147,44 +135,6 @@
|
||||||
"stateMutability": "view",
|
"stateMutability": "view",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_miningKey",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "withinLimit",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_votingKey",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getMiningByVotingKey",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [],
|
"inputs": [],
|
||||||
|
@ -199,25 +149,6 @@
|
||||||
"stateMutability": "view",
|
"stateMutability": "view",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getMemo",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -245,7 +176,7 @@
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "getEndTime",
|
"name": "getIndex",
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
@ -256,38 +187,43 @@
|
||||||
"stateMutability": "view",
|
"stateMutability": "view",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "maxBallotDuration",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "pure",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "migrateDisabled",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"constant": false,
|
"constant": false,
|
||||||
"inputs": [
|
"inputs": [],
|
||||||
{
|
"name": "migrateDisable",
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_choice",
|
|
||||||
"type": "uint8"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "vote",
|
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"payable": false,
|
"payable": false,
|
||||||
"stateMutability": "nonpayable",
|
"stateMutability": "nonpayable",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "getKeysManager",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [],
|
"inputs": [],
|
||||||
|
@ -303,36 +239,21 @@
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": false,
|
||||||
"inputs": [],
|
|
||||||
"name": "maxOldMiningKeysDeepCheck",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint8"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"name": "_id",
|
"name": "_id",
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
}
|
},
|
||||||
],
|
|
||||||
"name": "getStartTime",
|
|
||||||
"outputs": [
|
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "_choice",
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"name": "vote",
|
||||||
|
"outputs": [],
|
||||||
"payable": false,
|
"payable": false,
|
||||||
"stateMutability": "view",
|
"stateMutability": "nonpayable",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -377,20 +298,6 @@
|
||||||
"stateMutability": "view",
|
"stateMutability": "view",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "getGlobalMinThresholdOfVoters",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -414,25 +321,6 @@
|
||||||
"stateMutability": "view",
|
"stateMutability": "view",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getProposedValue",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [],
|
"inputs": [],
|
||||||
|
@ -451,26 +339,7 @@
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"name": "_id",
|
"name": "_miningKey",
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getProgress",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "int256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
"type": "address"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -486,88 +355,18 @@
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": false,
|
||||||
"inputs": [],
|
|
||||||
"name": "getBallotLimitPerValidator",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "_prevVotingToChange",
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "votingState",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "startTime",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "endTime",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "totalVoters",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "progress",
|
|
||||||
"type": "int256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "isFinalized",
|
|
||||||
"type": "bool"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "quorumState",
|
|
||||||
"type": "uint8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "index",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "minThresholdOfVoters",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "proposedValue",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "creator",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "memo",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_proxyStorage",
|
|
||||||
"type": "address"
|
"type": "address"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"name": "migrateBasicAll",
|
||||||
|
"outputs": [],
|
||||||
"payable": false,
|
"payable": false,
|
||||||
"stateMutability": "nonpayable",
|
"stateMutability": "nonpayable",
|
||||||
"type": "constructor"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"anonymous": false,
|
"anonymous": false,
|
||||||
|
@ -578,22 +377,17 @@
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"indexed": false,
|
"indexed": true,
|
||||||
"name": "decision",
|
"name": "ballotType",
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"indexed": true,
|
"indexed": true,
|
||||||
"name": "voter",
|
"name": "creator",
|
||||||
"type": "address"
|
"type": "address"
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": false,
|
|
||||||
"name": "time",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "Vote",
|
"name": "BallotCreated",
|
||||||
"type": "event"
|
"type": "event"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -622,17 +416,216 @@
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"indexed": true,
|
"indexed": false,
|
||||||
"name": "ballotType",
|
"name": "decision",
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"indexed": true,
|
"indexed": true,
|
||||||
"name": "creator",
|
"name": "voter",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "time",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "voterMiningKey",
|
||||||
"type": "address"
|
"type": "address"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "BallotCreated",
|
"name": "Vote",
|
||||||
"type": "event"
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_startTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_endTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_ballotType",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_affectedKeyType",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_memo",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_affectedKey",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_miningKey",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "createBallot",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_startTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_endTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_memo",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_newMiningKey",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_newVotingKey",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_newPayoutKey",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "createBallotToAddNewValidator",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "getBallotInfo",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "startTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "endTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "affectedKey",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "affectedKeyType",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newVotingKey",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newPayoutKey",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "miningKey",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "totalVoters",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "progress",
|
||||||
|
"type": "int256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "isFinalized",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ballotType",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "creator",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "memo",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "canBeFinalizedNow",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_minBallotDuration",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "init",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_prevVotingToChange",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_voters",
|
||||||
|
"type": "address[]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "migrateBasicOne",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -1,4 +1,37 @@
|
||||||
[
|
[
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "canBeFinalizedNow",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "minBallotDuration",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"constant": false,
|
"constant": false,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -21,11 +54,11 @@
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "getIsFinalized",
|
"name": "getQuorumState",
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"type": "bool"
|
"type": "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"payable": false,
|
"payable": false,
|
||||||
|
@ -59,40 +92,7 @@
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"name": "_id",
|
"name": "_index",
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getMiningKey",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "getBallotsStorage",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -109,17 +109,12 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [
|
"inputs": [],
|
||||||
{
|
"name": "initDisabled",
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getTotalVoters",
|
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"type": "uint256"
|
"type": "bool"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"payable": false,
|
"payable": false,
|
||||||
|
@ -140,63 +135,6 @@
|
||||||
"stateMutability": "view",
|
"stateMutability": "view",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_miningKey",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "withinLimit",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getAffectedKeyType",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_votingKey",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getMiningByVotingKey",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [],
|
"inputs": [],
|
||||||
|
@ -211,48 +149,6 @@
|
||||||
"stateMutability": "view",
|
"stateMutability": "view",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_currentKey",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_affectedKey",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "checkIfMiningExisted",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getMemo",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -276,23 +172,39 @@
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"name": "_ballotType",
|
"name": "_id",
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_affectedKey",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_affectedKeyType",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_miningKey",
|
|
||||||
"type": "address"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "areBallotParamsValid",
|
"name": "getIndex",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "maxBallotDuration",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "pure",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "migrateDisabled",
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
@ -303,112 +215,13 @@
|
||||||
"stateMutability": "view",
|
"stateMutability": "view",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getAffectedKey",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getEndTime",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"constant": false,
|
"constant": false,
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_startTime",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_endTime",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_affectedKey",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_affectedKeyType",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_miningKey",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_ballotType",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "memo",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "createVotingForKeys",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_choice",
|
|
||||||
"type": "uint8"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "vote",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
"inputs": [],
|
||||||
"name": "getKeysManager",
|
"name": "migrateDisable",
|
||||||
"outputs": [
|
"outputs": [],
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
"payable": false,
|
||||||
"stateMutability": "view",
|
"stateMutability": "nonpayable",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -426,36 +239,21 @@
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": false,
|
||||||
"inputs": [],
|
|
||||||
"name": "maxOldMiningKeysDeepCheck",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint8"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"name": "_id",
|
"name": "_id",
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
}
|
},
|
||||||
],
|
|
||||||
"name": "getStartTime",
|
|
||||||
"outputs": [
|
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "_choice",
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"name": "vote",
|
||||||
|
"outputs": [],
|
||||||
"payable": false,
|
"payable": false,
|
||||||
"stateMutability": "view",
|
"stateMutability": "nonpayable",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -500,20 +298,6 @@
|
||||||
"stateMutability": "view",
|
"stateMutability": "view",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "getGlobalMinThresholdOfVoters",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -537,25 +321,6 @@
|
||||||
"stateMutability": "view",
|
"stateMutability": "view",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_id",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getBallotType",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [],
|
"inputs": [],
|
||||||
|
@ -574,26 +339,7 @@
|
||||||
"constant": true,
|
"constant": true,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"name": "_id",
|
"name": "_miningKey",
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getProgress",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "int256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
"type": "address"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -609,100 +355,18 @@
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"constant": true,
|
"constant": false,
|
||||||
"inputs": [],
|
|
||||||
"name": "getBallotLimitPerValidator",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "_prevVotingToChange",
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "votingState",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "startTime",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "endTime",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "affectedKey",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "affectedKeyType",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "miningKey",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "totalVoters",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "progress",
|
|
||||||
"type": "int256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "isFinalized",
|
|
||||||
"type": "bool"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "quorumState",
|
|
||||||
"type": "uint8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ballotType",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "index",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "minThresholdOfVoters",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "creator",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "memo",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "_proxyStorage",
|
|
||||||
"type": "address"
|
"type": "address"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"name": "migrateBasicAll",
|
||||||
|
"outputs": [],
|
||||||
"payable": false,
|
"payable": false,
|
||||||
"stateMutability": "nonpayable",
|
"stateMutability": "nonpayable",
|
||||||
"type": "constructor"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"anonymous": false,
|
"anonymous": false,
|
||||||
|
@ -713,22 +377,17 @@
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"indexed": false,
|
"indexed": true,
|
||||||
"name": "decision",
|
"name": "ballotType",
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"indexed": true,
|
"indexed": true,
|
||||||
"name": "voter",
|
"name": "creator",
|
||||||
"type": "address"
|
"type": "address"
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": false,
|
|
||||||
"name": "time",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "Vote",
|
"name": "BallotCreated",
|
||||||
"type": "event"
|
"type": "event"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -757,17 +416,166 @@
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"indexed": true,
|
"indexed": false,
|
||||||
"name": "ballotType",
|
"name": "decision",
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"indexed": true,
|
"indexed": true,
|
||||||
"name": "creator",
|
"name": "voter",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "time",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "voterMiningKey",
|
||||||
"type": "address"
|
"type": "address"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "BallotCreated",
|
"name": "Vote",
|
||||||
"type": "event"
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_startTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_endTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_proposedValue",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_memo",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "createBallot",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_votingKey",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "getBallotInfo",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "startTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "endTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "totalVoters",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "progress",
|
||||||
|
"type": "int256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "isFinalized",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "proposedValue",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "creator",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "memo",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "canBeFinalizedNow",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "alreadyVoted",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_minBallotDuration",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_minPossibleThreshold",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "init",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_prevVotingToChange",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_voters",
|
||||||
|
"type": "address[]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "migrateBasicOne",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "minPossibleThreshold",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -0,0 +1,571 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "canBeFinalizedNow",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "minBallotDuration",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "finalize",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "getQuorumState",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_votingKey",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "isValidVote",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_index",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "activeBallots",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "initDisabled",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "getTime",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "activeBallotsLength",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "isActive",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "getIndex",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "maxBallotDuration",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "pure",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "migrateDisabled",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "migrateDisable",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "proxyStorage",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_choice",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "vote",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "getMinThresholdOfVoters",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_votingKey",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "hasAlreadyVoted",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_miningKey",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "areOldMiningKeysVoted",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "nextBallotId",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_miningKey",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "validatorActiveBallots",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_prevVotingToChange",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "migrateBasicAll",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "ballotType",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "creator",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "BallotCreated",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "voter",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "BallotFinalized",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "decision",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "voter",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "time",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "voterMiningKey",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Vote",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_startTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_endTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_contractType",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_memo",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_proposedValue",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "createBallot",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_votingKey",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "getBallotInfo",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "startTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "endTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "totalVoters",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "progress",
|
||||||
|
"type": "int256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "isFinalized",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "proposedValue",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "contractType",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "creator",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "memo",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "canBeFinalizedNow",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "alreadyVoted",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_minBallotDuration",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "init",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_prevVotingToChange",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_voters",
|
||||||
|
"type": "address[]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "migrateBasicOne",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,564 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "getQuorumState",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_votingKey",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "isValidVote",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "initDisabled",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "getTime",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "isActive",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "proxyStorage",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "getMinThresholdOfVoters",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_votingKey",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "hasAlreadyVoted",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_miningKey",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "areOldMiningKeysVoted",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "nextBallotId",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "votingKey",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "BallotCanceled",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "ballotType",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "creator",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "BallotCreated",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "voter",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "BallotFinalized",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "decision",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": true,
|
||||||
|
"name": "voter",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "time",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "voterMiningKey",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Vote",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "ballotCancelingThreshold",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "pure",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "canBeFinalizedNow",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "cancelNewBallot",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_startTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_endTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_receiver",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_memo",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "createBallot",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "distributionThreshold",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "emissionFunds",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "emissionReleaseThreshold",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "emissionReleaseTime",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "finalize",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "getBallotInfo",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "creationTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "startTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "endTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "isCanceled",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "isFinalized",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "creator",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "memo",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "amount",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "burnVotes",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "freezeVotes",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sendVotes",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "receiver",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "getTotalVoters",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_emissionReleaseTime",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_emissionReleaseThreshold",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_distributionThreshold",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_emissionFunds",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "init",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "noActiveBallotExists",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "refreshEmissionReleaseTime",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_choice",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "vote",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
fn create_env_file_if_dne() {
|
||||||
|
let env_path = Path::new(".env");
|
||||||
|
if !env_path.exists() {
|
||||||
|
let sample_env_path = Path::new("sample.env");
|
||||||
|
if !sample_env_path.exists() {
|
||||||
|
panic!("neither the .env nor the sample.env files exist");
|
||||||
|
}
|
||||||
|
fs::copy(sample_env_path, env_path)
|
||||||
|
.unwrap_or_else(|e| panic!("could not create .env file from sample.env: {:?}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
create_env_file_if_dne();
|
||||||
|
}
|
62
sample.env
62
sample.env
|
@ -1,35 +1,39 @@
|
||||||
# Defaults.
|
# RPC endpoints: https://github.com/poanetwork/wiki
|
||||||
USE_NETWORK=sokol
|
CORE_RPC_ENDPOINT=https://core.poa.network
|
||||||
MONITOR_BALLOTS=keys,threshold,proxy
|
SOKOL_RPC_ENDPOINT=https://sokol.poa.network
|
||||||
START_BLOCK=latest
|
|
||||||
AVG_BLOCK_TIME_SECS=5
|
|
||||||
SEND_EMAIL_NOTIFICATIONS=false
|
|
||||||
SEND_PUSH_NOTIFICATIONS=false
|
|
||||||
|
|
||||||
# Email configuration.
|
# Core Network V1 contract addresses:
|
||||||
|
# github.com/poanetwork/poa-chain-spec/blob/f037171b344d6138a6a7a7217ee6d2c85dbfd466/contracts.json
|
||||||
|
KEYS_CONTRACT_ADDRESS_CORE_V1=0x215794efe4b86a2fbcbf706bc9ade63663f1eae1
|
||||||
|
THRESHOLD_CONTRACT_ADDRESS_CORE_V1=0xca863b0d12193a87b5173fd51fa4aa1703fb8a32
|
||||||
|
PROXY_CONTRACT_ADDRESS_CORE_V1=0x9c8a06f0197ee718cd820adeb48a88ea2a9b5c48
|
||||||
|
|
||||||
|
# Core Network V2 contract addresses:
|
||||||
|
# github.com/poanetwork/poa-chain-spec/blob/38f3f634524923c4d74638852da1ace52e5a0cf0/contracts.json
|
||||||
|
KEYS_CONTRACT_ADDRESS_CORE_V2=0xa4508af18f1005943678769db3d95223c062258d
|
||||||
|
THRESHOLD_CONTRACT_ADDRESS_CORE_V2=0xa45e35472693ae60a95db8cB1ce73eea22ab5328
|
||||||
|
PROXY_CONTRACT_ADDRESS_CORE_V2=0x468758926c796722d85bded792d1831f0839caa6
|
||||||
|
EMISSION_FUNDS_CONTRACT_ADDRESS_CORE_V2=0x7e9b90b22cdd1f6aa206f0d852ac96212217d60e
|
||||||
|
|
||||||
|
# Sokol Network V1 contract addresses:
|
||||||
|
# github.com/poanetwork/poa-chain-spec/blob/821dc399471c80c6b0c6a95a2ba77d7c064eb321/contracts.json
|
||||||
|
KEYS_CONTRACT_ADDRESS_SOKOL_V1=0xc40cdf254a4a35498aa84f35e9842c110729a2a0
|
||||||
|
THRESHOLD_CONTRACT_ADDRESS_SOKOL_V1=0x700db8ba3128087f3b23f60de4bc3179bafa467d
|
||||||
|
PROXY_CONTRACT_ADDRESS_SOKOL_V1=0x0aa4a75549757a90f62f88b3b96b69bead2db0ff
|
||||||
|
|
||||||
|
# Sokol Network V2 contract addresses:
|
||||||
|
# github.com/poanetwork/poa-chain-spec/blob/36927101401de9dc3e21274d6f46e600d08fd1a7/contracts.json
|
||||||
|
KEYS_CONTRACT_ADDRESS_SOKOL_V2=0xb974df531c1b27324618175b442edf95f7f7a621
|
||||||
|
THRESHOLD_CONTRACT_ADDRESS_SOKOL_V2=0xd75ad6e3840a18dacc67bf3cd2080b24be409f79
|
||||||
|
PROXY_CONTRACT_ADDRESS_SOKOL_V2=0x604cdc518f3eb0446e15fc05a22923c82d8a8e21
|
||||||
|
EMISSION_FUNDS_CONTRACT_ADDRESS_SOKOL_V2=0x7cfa6f2c0d032f9dde652996e989a4d385b8b9d7
|
||||||
|
|
||||||
|
# Email notificaions settings.
|
||||||
|
# NOTE: if any of the following values contain special characters (as defined by your shell),
|
||||||
|
# wrap the entire env-var value in single quotations.
|
||||||
|
EMAIL_RECIPIENTS=
|
||||||
SMTP_HOST_DOMAIN=
|
SMTP_HOST_DOMAIN=
|
||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
SMTP_USERNAME=
|
SMTP_USERNAME=
|
||||||
SMTP_PASSWORD=
|
SMTP_PASSWORD=
|
||||||
OUTGOING_EMAIL_ADDRESS=
|
OUTGOING_EMAIL_ADDRESS=
|
||||||
|
|
||||||
# Validators (comma-separated list of emails).
|
|
||||||
VALIDATORS=
|
|
||||||
|
|
||||||
# Core network configuration.
|
|
||||||
CORE_RPC_ENDPOINT=https://core.poa.network
|
|
||||||
CORE_KEYS_CONTRACT_ADDRESS=0x215794efe4b86a2fbcbf706bc9ade63663f1eae1
|
|
||||||
CORE_THRESHOLD_CONTRACT_ADDRESS=0x8829ebe113535826e8af17ed51f83755f675789a
|
|
||||||
CORE_PROXY_CONTRACT_ADDRESS=0x6b728399b41a38d4109f7af2213d4cc31ca87812
|
|
||||||
|
|
||||||
# Sokol network configuration.
|
|
||||||
SOKOL_RPC_ENDPOINT=https://sokol.poa.network
|
|
||||||
SOKOL_KEYS_CONTRACT_ADDRESS=0xc40cdf254a4a35498aa84f35e9842c110729a2a0
|
|
||||||
SOKOL_THRESHOLD_CONTRACT_ADDRESS=0x700db8ba3128087f3b23f60de4bc3179bafa467d
|
|
||||||
SOKOL_PROXY_CONTRACT_ADDRESS=0x0aa4a75549757a90f62f88b3b96b69bead2db0ff
|
|
||||||
|
|
||||||
# Personal/local network configuration.
|
|
||||||
LOCAL_RPC_ENDPOINT=http://127.0.0.1:8545
|
|
||||||
LOCAL_KEYS_CONTRACT_ADDRESS=
|
|
||||||
LOCAL_THRESHOLD_CONTRACT_ADDRESS=
|
|
||||||
LOCAL_PROXY_CONTRACT_ADDRESS=
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use web3::types::BlockNumber;
|
||||||
|
|
||||||
|
use client::RpcClient;
|
||||||
|
use config::{Config, StartBlock};
|
||||||
|
use error::{Error, Result};
|
||||||
|
|
||||||
|
fn sleep_or_ctrlc(n_secs: u64, running: Arc<AtomicBool>) -> Option<()> {
|
||||||
|
let done_sleeping = Arc::new(AtomicBool::new(false));
|
||||||
|
{
|
||||||
|
let done_sleeping = done_sleeping.clone();
|
||||||
|
let _handle = thread::spawn(move || {
|
||||||
|
thread::sleep(Duration::from_secs(n_secs));
|
||||||
|
done_sleeping.store(true, Ordering::SeqCst);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
if !running.load(Ordering::SeqCst) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if done_sleeping.load(Ordering::SeqCst) {
|
||||||
|
return Some(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BlockchainIter<'a> {
|
||||||
|
client: &'a RpcClient,
|
||||||
|
start_block: u64,
|
||||||
|
stop_block: u64,
|
||||||
|
on_first_iteration: bool,
|
||||||
|
block_time: u64,
|
||||||
|
running: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BlockchainIter<'a> {
|
||||||
|
pub fn new(client: &'a RpcClient, config: &Config, running: Arc<AtomicBool>) -> Result<Self> {
|
||||||
|
let last_mined_block = client.get_last_mined_block_number()?;
|
||||||
|
let start_block = match config.start_block {
|
||||||
|
StartBlock::Earliest => 0,
|
||||||
|
StartBlock::Latest => last_mined_block,
|
||||||
|
StartBlock::Number(block_number) => block_number,
|
||||||
|
StartBlock::Tail(tail) => last_mined_block - tail,
|
||||||
|
};
|
||||||
|
if start_block > last_mined_block {
|
||||||
|
return Err(Error::StartBlockExceedsLastBlockMined { start_block, last_mined_block });
|
||||||
|
}
|
||||||
|
let bc_iter = BlockchainIter {
|
||||||
|
client,
|
||||||
|
start_block,
|
||||||
|
stop_block: last_mined_block,
|
||||||
|
on_first_iteration: true,
|
||||||
|
block_time: config.block_time,
|
||||||
|
running,
|
||||||
|
};
|
||||||
|
Ok(bc_iter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for BlockchainIter<'a> {
|
||||||
|
type Item = Result<(BlockNumber, BlockNumber)>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.on_first_iteration {
|
||||||
|
self.on_first_iteration = false;
|
||||||
|
} else {
|
||||||
|
self.start_block = self.stop_block + 1;
|
||||||
|
while self.start_block >= self.stop_block {
|
||||||
|
sleep_or_ctrlc(self.block_time, self.running.clone())?;
|
||||||
|
match self.client.get_last_mined_block_number() {
|
||||||
|
Ok(last_mined) => self.stop_block = last_mined,
|
||||||
|
Err(e) => return Some(Err(e)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if self.running.load(Ordering::SeqCst) {
|
||||||
|
let range = (self.start_block.into(), self.stop_block.into());
|
||||||
|
Some(Ok(range))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
133
src/cli.rs
133
src/cli.rs
|
@ -1,30 +1,119 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use clap::{ArgMatches, App};
|
use clap::{ArgMatches, App};
|
||||||
|
|
||||||
pub struct Cli;
|
#[derive(Debug)]
|
||||||
|
pub struct Cli(ArgMatches<'static>);
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
pub fn load() -> ArgMatches<'static> {
|
pub fn parse() -> Self {
|
||||||
App::new("poagov")
|
let cli_args = App::new("poagov")
|
||||||
.version("1.0")
|
.version("1.0.0")
|
||||||
.about("A tool to monitor POA Network's blockchain for governance events.")
|
.about("Monitors a POA Network blockchain for governance events.")
|
||||||
.args_from_usage(
|
.args_from_usage(
|
||||||
"[network] --network [value] 'the name of the network to monitor for ballots; the values available for this option are: core, sokol, local'
|
"[core] --core 'monitor voting contracts deployed to the Core network'
|
||||||
[core] --core 'monitor voting contracts deployed to the Core network (same as using --network=core)'
|
[sokol] --sokol 'monitor voting contracts deployed to the Sokol network'
|
||||||
[sokol] --sokol 'monitor voting contracts deployed to the Sokol test network (same as using --network=sokol)'
|
[keys] -k --keys 'monitors the blockchain for ballots to change keys'
|
||||||
[local] --local 'monitor voting contracts deployed to a locally running POA chain (same as using --network=local)'
|
[threshold] -t --threshold 'monitors the blockchain for ballots to change the minimum threshold'
|
||||||
[rpc] --rpc [value] 'the URL for the RPC endpoint'
|
[proxy] -p --proxy 'monitors the blockchain for ballots to change the proxy address'
|
||||||
[monitor] --monitor [value] 'a comma-separated list of ballot types to monitor for governance events; the available values are: keys, threshold, proxy`
|
[emission] -e --emission 'monitors the blockchain for ballots to manage emission funds'
|
||||||
[keys] -k 'monitor the blockchain for ballots to change keys (same as --monitor=keys)'
|
[v1] --v1 'monitors the v1 voting contracts'
|
||||||
[threshold] -t 'monitor the chain for ballots to change the minimum threshold (same as --monitor=threshold)'
|
[v2] --v2 'monitors the v2 voting contracts'
|
||||||
[proxy] -p 'monitor the change for ballots to change the proxy address (same as --monitor=proxy)'
|
[earliest] --earliest 'begin monitoring for governance events starting at the first block in the blockchain'
|
||||||
|
[latest] --latest 'begin monitoring for governance events starting at the last block mined'
|
||||||
[start_block] --start [value] 'start monitoring for governance events at this block (inclusive)'
|
[start_block] --start [value] 'start monitoring for governance events at this block (inclusive)'
|
||||||
[tail] --tail [value] 'start monitoring for governance events for the `n` blocks prior to the last mined block in the chain'
|
[tail] --tail [value] 'start monitoring for governance events for the `n` blocks prior to the last block mined'
|
||||||
[earliest] --earliest 'start monitoring for goverance events starting from the first block in the chain'
|
[email] --email 'enables email notifications (SMTP configurations must be set in your `.env` file)'
|
||||||
[latest] --latest 'start monitoring for goverance events starting from the most recently mined block in the chain'
|
[block_time] --block-time [value] 'the average number of seconds it takes to mine a new block'
|
||||||
[email] --email 'send governance notifications via email'
|
[notification_limit] -n --limit [value] 'shutdown `poagov` after this many notifications have been generated'
|
||||||
[push] --push 'send governance notifications via push notification'
|
[log_emails] --log-emails 'logs each notification's email body; does not require the --email flag to be set'
|
||||||
[block_time] --block-time [value] 'the average time it takes to mine a new block'"
|
[log_to_file] --log-file 'logs are written to files in the ./logs directory, logs are rotated chronologically across 3 files, each file has a max size of 8MB'"
|
||||||
)
|
).get_matches();
|
||||||
.get_matches()
|
Cli(cli_args)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn core(&self) -> bool {
|
||||||
|
self.0.is_present("core")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sokol(&self) -> bool {
|
||||||
|
self.0.is_present("sokol")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keys(&self) -> bool {
|
||||||
|
self.0.is_present("keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn threshold(&self) -> bool {
|
||||||
|
self.0.is_present("threshold")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn proxy(&self) -> bool {
|
||||||
|
self.0.is_present("proxy")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emission(&self) -> bool {
|
||||||
|
self.0.is_present("emission")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn v1(&self) -> bool {
|
||||||
|
self.0.is_present("v1")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn v2(&self) -> bool {
|
||||||
|
self.0.is_present("v2")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn earliest(&self) -> bool {
|
||||||
|
self.0.is_present("earliest")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn latest(&self) -> bool {
|
||||||
|
self.0.is_present("latest")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_block(&self) -> Option<&str> {
|
||||||
|
self.0.value_of("start_block")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tail(&self) -> Option<&str> {
|
||||||
|
self.0.value_of("tail")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn multiple_start_blocks_specified(&self) -> bool {
|
||||||
|
let mut count = 0;
|
||||||
|
if self.earliest() {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
if self.latest() {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
if self.start_block().is_some() {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
if self.tail().is_some() {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
count != 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn email(&self) -> bool {
|
||||||
|
self.0.is_present("email")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn block_time(&self) -> Option<&str> {
|
||||||
|
self.0.value_of("block_time")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn notification_limit(&self) -> Option<&str> {
|
||||||
|
self.0.value_of("notification_limit")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_emails(&self) -> bool {
|
||||||
|
self.0.is_present("log_emails")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_to_file(&self) -> bool {
|
||||||
|
self.0.is_present("log_to_file")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,310 @@
|
||||||
|
use std::u64;
|
||||||
|
|
||||||
|
use ethabi;
|
||||||
|
use hex;
|
||||||
|
use jsonrpc_core as json_rpc;
|
||||||
|
use reqwest;
|
||||||
|
use serde_json as json;
|
||||||
|
use web3;
|
||||||
|
use web3::types::{Address, BlockNumber, Filter, FilterBuilder, U256};
|
||||||
|
|
||||||
|
use config::{ContractType, PoaContract};
|
||||||
|
use error::{Error, Result};
|
||||||
|
use response::{v1, v2};
|
||||||
|
use response::common::BallotCreatedLog;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum RpcMethod {
|
||||||
|
CallContractFunction,
|
||||||
|
GetLogs,
|
||||||
|
GetLastMinedBlockNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<String> for RpcMethod {
|
||||||
|
fn into(self) -> String {
|
||||||
|
let s = match self {
|
||||||
|
RpcMethod::CallContractFunction => "eth_call",
|
||||||
|
RpcMethod::GetLogs => "eth_getLogs",
|
||||||
|
RpcMethod::GetLastMinedBlockNumber => "eth_blockNumber",
|
||||||
|
};
|
||||||
|
s.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RpcClient {
|
||||||
|
endpoint: String,
|
||||||
|
client: reqwest::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpcClient {
|
||||||
|
pub fn new(endpoint: String) -> Self {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
RpcClient { endpoint, client }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_request(
|
||||||
|
&self,
|
||||||
|
method: RpcMethod,
|
||||||
|
params: Vec<json::Value>,
|
||||||
|
) -> Result<reqwest::Request>
|
||||||
|
{
|
||||||
|
let method_call = json_rpc::types::request::MethodCall {
|
||||||
|
jsonrpc: Some(json_rpc::types::version::Version::V2),
|
||||||
|
method: method.into(),
|
||||||
|
params: Some(json_rpc::types::Params::Array(params)),
|
||||||
|
id: json_rpc::types::id::Id::Num(1),
|
||||||
|
};
|
||||||
|
let request_data: json_rpc::types::request::Call = method_call.into();
|
||||||
|
self.client
|
||||||
|
.post(&self.endpoint)
|
||||||
|
.json(&request_data)
|
||||||
|
.build()
|
||||||
|
.map_err(|e| Error::FailedToBuildRequest(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send(&self, req: reqwest::Request) -> Result<json::Value> {
|
||||||
|
let resp: json_rpc::types::response::Response = self.client
|
||||||
|
.execute(req)
|
||||||
|
.map_err(|e| Error::RequestFailed(e))?
|
||||||
|
.json()
|
||||||
|
.unwrap();
|
||||||
|
if let json_rpc::types::response::Response::Single(resp_status) = resp {
|
||||||
|
match resp_status {
|
||||||
|
json_rpc::types::response::Output::Success(resp) => return Ok(resp.result),
|
||||||
|
json_rpc::types::response::Output::Failure(e) => return Err(Error::JsonRpcResponseFailure(e)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
unreachable!("Recieved multiple responses for single request");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_last_mined_block_number(&self) -> Result<u64> {
|
||||||
|
let req = self.build_request(RpcMethod::GetLastMinedBlockNumber, vec![])?;
|
||||||
|
if let json::Value::String(s) = self.send(req)? {
|
||||||
|
let s = s.trim_left_matches("0x");
|
||||||
|
let block_number = u64::from_str_radix(s, 16).unwrap();
|
||||||
|
return Ok(block_number);
|
||||||
|
}
|
||||||
|
unreachable!("Received a non-string response from `eth_blockNumber` call");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_logs(&self, filter: Filter) -> Result<Vec<web3::types::Log>> {
|
||||||
|
let params = vec![json::to_value(filter).unwrap()];
|
||||||
|
let req = self.build_request(RpcMethod::GetLogs, params)?;
|
||||||
|
let result = self.send(req)?;
|
||||||
|
Ok(json::from_value(result).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// V1 and V2
|
||||||
|
pub fn get_ballot_created_logs(
|
||||||
|
&self,
|
||||||
|
contract: &PoaContract,
|
||||||
|
start: BlockNumber,
|
||||||
|
stop: BlockNumber,
|
||||||
|
) -> Result<Vec<BallotCreatedLog>>
|
||||||
|
{
|
||||||
|
let event = contract.event("BallotCreated");
|
||||||
|
let event_sig = event.signature();
|
||||||
|
let filter = FilterBuilder::default()
|
||||||
|
.topics(Some(vec![event_sig]), None, None, None)
|
||||||
|
.address(vec![contract.addr])
|
||||||
|
.from_block(start)
|
||||||
|
.to_block(stop)
|
||||||
|
.build();
|
||||||
|
self.get_logs(filter)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|web3_log| {
|
||||||
|
let web3::types::Log {topics, data, block_number, .. } = web3_log;
|
||||||
|
let raw_log = ethabi::RawLog::from((topics, data.0));
|
||||||
|
let ethabi_log = event.parse_log(raw_log)
|
||||||
|
.map_err(|e| Error::FailedToParseRawLogToLog(e))?;
|
||||||
|
BallotCreatedLog::from_ethabi_log(ethabi_log, block_number.unwrap())
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// V1
|
||||||
|
pub fn get_voting_state(&self, contract: &PoaContract, ballot_id: U256) -> Result<v1::VotingState> {
|
||||||
|
let function = contract.function("votingState");
|
||||||
|
let tokens = vec![ethabi::Token::Uint(ballot_id)];
|
||||||
|
let encoded_input = function.encode_input(&tokens).unwrap();
|
||||||
|
let function_call_request = web3::types::CallRequest {
|
||||||
|
to: contract.addr,
|
||||||
|
data: Some(encoded_input.into()),
|
||||||
|
from: None,
|
||||||
|
gas: None,
|
||||||
|
gas_price: None,
|
||||||
|
value: None,
|
||||||
|
};
|
||||||
|
let rpc_method_params = vec![
|
||||||
|
json::to_value(function_call_request).unwrap(),
|
||||||
|
json::to_value(BlockNumber::Latest).unwrap(),
|
||||||
|
];
|
||||||
|
let req = self.build_request(RpcMethod::CallContractFunction, rpc_method_params)?;
|
||||||
|
if let json::Value::String(s) = self.send(req)? {
|
||||||
|
let s = s.trim_left_matches("0x");
|
||||||
|
let bytes = hex::decode(s).unwrap();
|
||||||
|
let outputs = function.decode_output(&bytes).unwrap();
|
||||||
|
let voting_state: v1::VotingState = match contract.kind {
|
||||||
|
ContractType::Keys => v1::KeysVotingState::from(outputs).into(),
|
||||||
|
ContractType::Threshold => v1::ThresholdVotingState::from(outputs).into(),
|
||||||
|
ContractType::Proxy => v1::ProxyVotingState::from(outputs).into(),
|
||||||
|
ContractType::Emission => return Err(Error::EmissionFundsV1ContractDoesNotExist),
|
||||||
|
};
|
||||||
|
return Ok(voting_state);
|
||||||
|
}
|
||||||
|
unreachable!("received non-string JSON response from `votingState`");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// V2
|
||||||
|
// TODO: When V2 contracts have been published and ballots have begun, test that calling
|
||||||
|
// `.getBallotInfo()` with `Address::zero()` for the `votingKey` works (we don't care if
|
||||||
|
// `votingKey` has voted yet).
|
||||||
|
pub fn get_ballot_info(&self, contract: &PoaContract, ballot_id: U256) -> Result<v2::BallotInfo> {
|
||||||
|
// pub fn get_ballot_info(&self, contract: &PoaContract, ballot_id: U256, voting_key: Option<Address>) -> Result<v2::BallotInfo> {
|
||||||
|
let function = contract.function("getBallotInfo");
|
||||||
|
/*
|
||||||
|
let mut tokens = vec![ethabi::Token::Uint(ballot_id)];
|
||||||
|
if function.inputs.len() == 2 {
|
||||||
|
if let Some(voting_key) = voting_key {
|
||||||
|
tokens.push(ethabi::Token::Address(voting_key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
let mut tokens = vec![ethabi::Token::Uint(ballot_id)];
|
||||||
|
if function.inputs.len() == 2 {
|
||||||
|
tokens.push(ethabi::Token::Address(Address::zero()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let encoded_input = function.encode_input(&tokens).unwrap();
|
||||||
|
let function_call_request = web3::types::CallRequest {
|
||||||
|
to: contract.addr,
|
||||||
|
data: Some(encoded_input.into()),
|
||||||
|
from: None,
|
||||||
|
gas: None,
|
||||||
|
gas_price: None,
|
||||||
|
value: None,
|
||||||
|
};
|
||||||
|
let rpc_method_params = vec![
|
||||||
|
json::to_value(function_call_request).unwrap(),
|
||||||
|
json::to_value(BlockNumber::Latest).unwrap(),
|
||||||
|
];
|
||||||
|
let req = self.build_request(RpcMethod::CallContractFunction, rpc_method_params)?;
|
||||||
|
if let json::Value::String(s) = self.send(req)? {
|
||||||
|
let s = s.trim_left_matches("0x");
|
||||||
|
let bytes = hex::decode(s).unwrap();
|
||||||
|
let outputs = function.decode_output(&bytes).unwrap();
|
||||||
|
let ballot_info: v2::BallotInfo = match contract.kind {
|
||||||
|
ContractType::Keys => v2::KeysBallotInfo::from(outputs).into(),
|
||||||
|
ContractType::Threshold => v2::ThresholdBallotInfo::from(outputs).into(),
|
||||||
|
ContractType::Proxy => v2::ProxyBallotInfo::from(outputs).into(),
|
||||||
|
ContractType::Emission => v2::EmissionBallotInfo::from(outputs).into(),
|
||||||
|
};
|
||||||
|
return Ok(ballot_info);
|
||||||
|
}
|
||||||
|
unreachable!("received non-string JSON response from `getBallotInfo`");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use web3::types::BlockNumber;
|
||||||
|
|
||||||
|
use super::super::tests::setup;
|
||||||
|
use super::RpcClient;
|
||||||
|
use config::{ContractType, ContractVersion, Network, PoaContract};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_last_mined_block() {
|
||||||
|
setup();
|
||||||
|
|
||||||
|
let sokol_url = env::var("SOKOL_RPC_ENDPOINT").expect("Missing env-var: `SOKOL_RPC_ENDPOINT`");
|
||||||
|
let client = RpcClient::new(sokol_url);
|
||||||
|
let res = client.get_last_mined_block_number();
|
||||||
|
println!("\nsokol last mined block number => {:?}", res);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
|
||||||
|
let core_url = env::var("CORE_RPC_ENDPOINT").expect("Missing env-var: `CORE_RPC_ENDPOINT`");
|
||||||
|
let client = RpcClient::new(core_url);
|
||||||
|
let res = client.get_last_mined_block_number();
|
||||||
|
println!("core last mined block number => {:?}", res);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_ballot_created_logs() {
|
||||||
|
setup();
|
||||||
|
let contract = PoaContract::read(
|
||||||
|
ContractType::Keys,
|
||||||
|
Network::Sokol,
|
||||||
|
ContractVersion::V1,
|
||||||
|
).unwrap_or_else(|e| panic!("Failed to load contract: {:?}", e));
|
||||||
|
let endpoint = env::var("SOKOL_RPC_ENDPOINT").expect("Missing env-var: `SOKOL_RPC_ENDPOINT`");
|
||||||
|
let client = RpcClient::new(endpoint);
|
||||||
|
let res = client.get_ballot_created_logs(
|
||||||
|
&contract,
|
||||||
|
BlockNumber::Earliest,
|
||||||
|
BlockNumber::Latest,
|
||||||
|
);
|
||||||
|
println!("{:#?}", res);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: uncomment this test once V2 ballots are created.
|
||||||
|
/*
|
||||||
|
#[test]
|
||||||
|
fn test_get_ballot_created_logs_v2() {
|
||||||
|
setup();
|
||||||
|
let contract = PoaContract::read(
|
||||||
|
ContractType::Keys,
|
||||||
|
Network::Core,
|
||||||
|
ContractVersion::V2
|
||||||
|
).unwrap_or_else(|e| panic!("Failed to load contract: {:?}", e));
|
||||||
|
let rpc_url = env::var("CORE_RPC_ENDPOINT").expect("Missing env-var: `CORE_RPC_ENDPOINT`");
|
||||||
|
let client = RpcClient::new(rpc_url);
|
||||||
|
let res = client.get_ballot_created_logs(
|
||||||
|
&contract,
|
||||||
|
BlockNumber::Earliest,
|
||||||
|
BlockNumber::Latest,
|
||||||
|
);
|
||||||
|
println!("RES => {:#?}", res);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_voting_state() {
|
||||||
|
setup();
|
||||||
|
let contract = PoaContract::read(
|
||||||
|
ContractType::Threshold,
|
||||||
|
Network::Sokol,
|
||||||
|
ContractVersion::V1
|
||||||
|
).unwrap_or_else(|e| panic!("Failed to load contract: {:?}", e));
|
||||||
|
let sokol_url = env::var("SOKOL_RPC_ENDPOINT").expect("Missing env-var: `SOKOL_RPC_ENDPOINT`");
|
||||||
|
let client = RpcClient::new(sokol_url);
|
||||||
|
let res = client.get_voting_state(&contract, 2.into());
|
||||||
|
println!("{:#?}", res);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: uncomment this test once V2 ballots are created.
|
||||||
|
/*
|
||||||
|
#[test]
|
||||||
|
fn test_get_ballot_info() {
|
||||||
|
setup();
|
||||||
|
let contract = PoaContract::read(
|
||||||
|
ContractType::Emission,
|
||||||
|
Network::Core,
|
||||||
|
ContractVersion::V2
|
||||||
|
).unwrap_or_else(|e| panic!("Failed to load contract: {:?}", e));
|
||||||
|
let sokol_url = env::var("CORE_RPC_ENDPOINT").expect("Missing env-var: `SOKOL_RPC_ENDPOINT`");
|
||||||
|
let client = RpcClient::new(sokol_url);
|
||||||
|
let ballot_id
|
||||||
|
let res = client.get_ballot_info(&contract, 2.into());
|
||||||
|
println!("{:#?}", res);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
517
src/config.rs
517
src/config.rs
|
@ -1,118 +1,148 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fmt::{self, Debug, Display, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use dotenv::dotenv;
|
use ethabi::{Address, Contract, Event, Function};
|
||||||
use ethabi::{Contract, Event, Function};
|
|
||||||
use ethereum_types::Address;
|
|
||||||
|
|
||||||
use cli::Cli;
|
use cli::Cli;
|
||||||
use utils::hex_string_to_u64;
|
use error::{Error, Result};
|
||||||
|
use response::common::BallotType;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
const DEFAULT_BLOCK_TIME_SECS: u64 = 30;
|
||||||
pub enum Network { Core, Sokol, Local }
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for Network {
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
fn from(s: &'a str) -> Self {
|
pub enum Network {
|
||||||
match s {
|
Core,
|
||||||
"core" => Network::Core,
|
Sokol,
|
||||||
"sokol" => Network::Sokol,
|
|
||||||
"local" => Network::Local,
|
|
||||||
_ => panic!(format!("Invalid network: {}", s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Network {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
Network::Core => write!(f, "core"),
|
|
||||||
Network::Sokol => write!(f, "sokol"),
|
|
||||||
Network::Local => write!(f, "local")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Network {
|
impl Network {
|
||||||
fn to_uppercase(&self) -> String {
|
fn uppercase(&self) -> &str {
|
||||||
format!("{}", self).to_uppercase()
|
match self {
|
||||||
}
|
Network::Core => "CORE",
|
||||||
}
|
Network::Sokol => "SOKOL",
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum ContractType { Keys, Threshold, Proxy }
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for ContractType {
|
|
||||||
fn from(s: &'a str) -> Self {
|
|
||||||
match s {
|
|
||||||
"keys" => ContractType::Keys,
|
|
||||||
"threshold" => ContractType::Threshold,
|
|
||||||
"proxy" => ContractType::Proxy,
|
|
||||||
_ => panic!("Invalid contract type: {}", s)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ContractType {
|
/// Note that the `Emission` contract is V2 only.
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
match *self {
|
pub enum ContractType {
|
||||||
ContractType::Keys => write!(f, "keys"),
|
Keys,
|
||||||
ContractType::Threshold => write!(f, "threshold"),
|
Threshold,
|
||||||
ContractType::Proxy => write!(f, "proxy")
|
Proxy,
|
||||||
|
Emission,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BallotType> for ContractType {
|
||||||
|
fn from(ballot_type: BallotType) -> Self {
|
||||||
|
match ballot_type {
|
||||||
|
BallotType::InvalidKey => ContractType::Keys,
|
||||||
|
BallotType::AddKey => ContractType::Keys,
|
||||||
|
BallotType::RemoveKey => ContractType::Keys,
|
||||||
|
BallotType::SwapKey => ContractType::Keys,
|
||||||
|
BallotType::Threshold => ContractType::Threshold,
|
||||||
|
BallotType::Proxy => ContractType::Proxy,
|
||||||
|
BallotType::Emission => ContractType::Emission,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContractType {
|
impl ContractType {
|
||||||
fn to_uppercase(&self) -> String {
|
fn uppercase(&self) -> &str {
|
||||||
format!("{}", self).to_uppercase()
|
match self {
|
||||||
|
ContractType::Keys => "KEYS",
|
||||||
|
ContractType::Threshold => "THRESHOLD",
|
||||||
|
ContractType::Proxy => "PROXY",
|
||||||
|
ContractType::Emission => "EMISSION_FUNDS",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
fn abi_file_name(&self) -> &str {
|
||||||
pub enum StartBlock {
|
match self {
|
||||||
Earliest,
|
ContractType::Keys => "VotingToChangeKeys.abi.json",
|
||||||
Latest,
|
ContractType::Threshold => "VotingToChangeMinThreshold.abi.json",
|
||||||
Number(u64),
|
ContractType::Proxy => "VotingToChangeProxyAddress.abi.json",
|
||||||
Tail(u64)
|
ContractType::Emission => "VotingToManageEmissionFunds.abi.json",
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for StartBlock {
|
|
||||||
fn from(s: &'a str) -> Self {
|
|
||||||
if s == "earliest" {
|
|
||||||
StartBlock::Earliest
|
|
||||||
} else if s == "latest" {
|
|
||||||
StartBlock::Latest
|
|
||||||
} else if s.starts_with('-') {
|
|
||||||
let tail: u64 = s[1..].parse().expect("Invalid tail start-block");
|
|
||||||
StartBlock::Tail(tail)
|
|
||||||
} else if s.starts_with("0x") {
|
|
||||||
let block_number = hex_string_to_u64(s).expect("Invalid hex start-block");
|
|
||||||
StartBlock::Number(block_number)
|
|
||||||
} else {
|
|
||||||
let block_number: u64 = s.parse().expect("Invaild decimal start-block");
|
|
||||||
StartBlock::Number(block_number)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct Validator {
|
pub enum ContractVersion {
|
||||||
pub name: String,
|
V1,
|
||||||
pub email: String
|
V2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ContractVersion {
|
||||||
|
fn lowercase(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
ContractVersion::V1 => "v1",
|
||||||
|
ContractVersion::V2 => "v2",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct PoaContract {
|
pub struct PoaContract {
|
||||||
pub kind: ContractType,
|
pub kind: ContractType,
|
||||||
|
pub version: ContractVersion,
|
||||||
pub addr: Address,
|
pub addr: Address,
|
||||||
pub abi: Contract
|
pub abi: Contract,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for PoaContract {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("PoaContract")
|
||||||
|
.field("kind", &self.kind)
|
||||||
|
.field("addr", &self.addr)
|
||||||
|
.field("abi", &"<ethabi::Contract>")
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PoaContract {
|
impl PoaContract {
|
||||||
fn new(kind: ContractType, addr: Address, abi: Contract) -> Self {
|
pub fn read(
|
||||||
PoaContract { kind, addr, abi }
|
contract_type: ContractType,
|
||||||
|
network: Network,
|
||||||
|
version: ContractVersion,
|
||||||
|
) -> Result<Self>
|
||||||
|
{
|
||||||
|
if contract_type == ContractType::Emission && version == ContractVersion::V1 {
|
||||||
|
return Err(Error::EmissionFundsV1ContractDoesNotExist);
|
||||||
|
}
|
||||||
|
|
||||||
|
let addr_env_var = format!(
|
||||||
|
"{}_CONTRACT_ADDRESS_{}_{:?}",
|
||||||
|
contract_type.uppercase(),
|
||||||
|
network.uppercase(),
|
||||||
|
version,
|
||||||
|
);
|
||||||
|
|
||||||
|
let addr = if let Ok(s) = env::var(&addr_env_var) {
|
||||||
|
match Address::from_str(s.trim_left_matches("0x")) {
|
||||||
|
Ok(addr) => addr,
|
||||||
|
Err(_) => return Err(Error::InvalidContractAddr(s.into())),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::MissingEnvVar(addr_env_var));
|
||||||
|
};
|
||||||
|
|
||||||
|
let abi_path = format!(
|
||||||
|
"abis/{}/{}",
|
||||||
|
version.lowercase(),
|
||||||
|
contract_type.abi_file_name(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let abi_file = File::open(&abi_path)
|
||||||
|
.map_err(|_| Error::MissingAbiFile(abi_path.clone()))?;
|
||||||
|
|
||||||
|
let abi = Contract::load(&abi_file)
|
||||||
|
.map_err(|_| Error::InvalidAbi(abi_path))?;
|
||||||
|
|
||||||
|
let contract = PoaContract { kind: contract_type, version, addr, abi };
|
||||||
|
Ok(contract)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn event(&self, event: &str) -> Event {
|
pub fn event(&self, event: &str) -> Event {
|
||||||
|
@ -124,144 +154,261 @@ impl PoaContract {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for PoaContract {
|
#[derive(Clone, Copy, Debug)]
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
pub enum StartBlock {
|
||||||
write!(f, "PoaContract({:?}, {:?})", self.kind, self.addr)
|
Earliest,
|
||||||
}
|
Latest,
|
||||||
|
Number(u64),
|
||||||
|
Tail(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for PoaContract {
|
#[derive(Clone, Debug)]
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub network: Network,
|
pub network: Network,
|
||||||
pub endpoint: String,
|
pub endpoint: String,
|
||||||
|
pub version: ContractVersion,
|
||||||
pub contracts: Vec<PoaContract>,
|
pub contracts: Vec<PoaContract>,
|
||||||
pub start_block: StartBlock,
|
pub start_block: StartBlock,
|
||||||
pub send_email_notifications: bool,
|
pub block_time: u64,
|
||||||
pub send_push_notifications: bool,
|
pub email_notifications: bool,
|
||||||
pub validators: Vec<Validator>,
|
pub email_recipients: Vec<String>,
|
||||||
pub avg_block_time: Duration,
|
pub smtp_host_domain: Option<String>,
|
||||||
pub smtp_host_domain: String,
|
pub smtp_port: Option<u16>,
|
||||||
pub smtp_port: u16,
|
pub smtp_username: Option<String>,
|
||||||
pub smtp_username: String,
|
pub smtp_password: Option<String>,
|
||||||
pub smtp_password: String,
|
pub outgoing_email_addr: Option<String>,
|
||||||
pub outgoing_email: String
|
pub notification_limit: Option<usize>,
|
||||||
|
pub log_emails: bool,
|
||||||
|
pub log_to_file: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn load() -> Self {
|
pub fn new(cli: &Cli) -> Result<Self> {
|
||||||
dotenv().ok();
|
let network = if cli.core() == cli.sokol() {
|
||||||
let cli = Cli::load();
|
return Err(Error::MustSpecifyOneCliArgument("`--core` or `--sokol`".into()));
|
||||||
|
} else if cli.core() {
|
||||||
let network = if let Some(s) = cli.value_of("network") {
|
|
||||||
s.into()
|
|
||||||
} else if cli.is_present("core") {
|
|
||||||
Network::Core
|
Network::Core
|
||||||
} else if cli.is_present("sokol") {
|
} else {
|
||||||
Network::Sokol
|
Network::Sokol
|
||||||
} else if cli.is_present("local") {
|
|
||||||
Network::Local
|
|
||||||
} else {
|
|
||||||
env::var("USE_NETWORK").unwrap().as_str().into()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let network_uppercase = network.to_uppercase();
|
let endpoint_env_var = format!("{}_RPC_ENDPOINT", network.uppercase());
|
||||||
|
let endpoint = env::var(&endpoint_env_var)
|
||||||
let endpoint = if let Some(s) = cli.value_of("rpc") {
|
.map_err(|_| Error::MissingEnvVar(endpoint_env_var))?;
|
||||||
s.into()
|
|
||||||
|
let version = if cli.v1() == cli.v2(){
|
||||||
|
return Err(Error::MustSpecifyOneCliArgument("`--v1` or `--v2`".into()));
|
||||||
|
} else if cli.v1(){
|
||||||
|
ContractVersion::V1
|
||||||
} else {
|
} else {
|
||||||
let env_var = format!("{}_RPC_ENDPOINT", network_uppercase);
|
ContractVersion::V2
|
||||||
env::var(&env_var).unwrap()
|
};
|
||||||
|
|
||||||
|
let mut contracts = vec![];
|
||||||
|
if cli.keys() {
|
||||||
|
let keys = PoaContract::read(
|
||||||
|
ContractType::Keys,
|
||||||
|
network,
|
||||||
|
version
|
||||||
|
)?;
|
||||||
|
contracts.push(keys);
|
||||||
|
}
|
||||||
|
if cli.threshold() {
|
||||||
|
let threshold = PoaContract::read(
|
||||||
|
ContractType::Threshold,
|
||||||
|
network,
|
||||||
|
version
|
||||||
|
)?;
|
||||||
|
contracts.push(threshold);
|
||||||
|
}
|
||||||
|
if cli.proxy() {
|
||||||
|
let proxy = PoaContract::read(
|
||||||
|
ContractType::Proxy,
|
||||||
|
network,
|
||||||
|
version
|
||||||
|
)?;
|
||||||
|
contracts.push(proxy);
|
||||||
|
}
|
||||||
|
if cli.emission() {
|
||||||
|
let emission_funds = PoaContract::read(
|
||||||
|
ContractType::Emission,
|
||||||
|
network,
|
||||||
|
version,
|
||||||
|
)?;
|
||||||
|
contracts.push(emission_funds);
|
||||||
|
}
|
||||||
|
if contracts.is_empty() {
|
||||||
|
return Err(Error::MustSpecifyAtLeastOneCliArgument(
|
||||||
|
"`--keys`, `--threshold`, `--proxy`, `--emission`".into()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_block = if cli.multiple_start_blocks_specified() {
|
||||||
|
return Err(Error::MustSpecifyOneCliArgument(
|
||||||
|
"`--earliest` or `--latest` or `--start-block` or `--tail`".into()
|
||||||
|
));
|
||||||
|
} else if cli.earliest() {
|
||||||
|
StartBlock::Earliest
|
||||||
|
} else if cli.latest() {
|
||||||
|
StartBlock::Latest
|
||||||
|
} else if let Some(s) = cli.start_block() {
|
||||||
|
let block_number = s.parse().map_err(|_| Error::InvalidStartBlock(s.into()))?;
|
||||||
|
StartBlock::Number(block_number)
|
||||||
|
} else if let Some(s) = cli.tail() {
|
||||||
|
let tail = s.parse().map_err(|_| Error::InvalidTail(s.into()))?;
|
||||||
|
StartBlock::Tail(tail)
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut contract_types: Vec<ContractType> = vec![];
|
let block_time = if let Some(s) = cli.block_time() {
|
||||||
if let Some(s) = cli.value_of("monitor") {
|
s.parse().map_err(|_| Error::InvalidBlockTime(s.into()))?
|
||||||
s.split(',').for_each(|s| contract_types.push(s.into()));
|
} else {
|
||||||
}
|
DEFAULT_BLOCK_TIME_SECS
|
||||||
if cli.is_present("keys") {
|
};
|
||||||
contract_types.push(ContractType::Keys);
|
|
||||||
}
|
|
||||||
if cli.is_present("threshold") {
|
|
||||||
contract_types.push(ContractType::Threshold);
|
|
||||||
}
|
|
||||||
if cli.is_present("proxy") {
|
|
||||||
contract_types.push(ContractType::Proxy);
|
|
||||||
}
|
|
||||||
if contract_types.is_empty() {
|
|
||||||
env::var("MONITOR_BALLOTS").unwrap().split(',')
|
|
||||||
.for_each(|s| contract_types.push(s.into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let contracts: Vec<PoaContract> = contract_types.iter()
|
let email_notifications = cli.email();
|
||||||
.map(|contract_type| {
|
|
||||||
let env_var = format!(
|
let email_recipients: Vec<String> = env::var("EMAIL_RECIPIENTS")
|
||||||
"{}_{}_CONTRACT_ADDRESS",
|
.map_err(|_| Error::MissingEnvVar("EMAIL_RECIPIENTS".into()))?
|
||||||
network_uppercase,
|
.split(',')
|
||||||
contract_type.to_uppercase()
|
.filter_map(|s| {
|
||||||
);
|
if s.is_empty() {
|
||||||
let hex = env::var(&env_var)
|
None
|
||||||
.expect(&format!("Contract address not found: {}", env_var));
|
} else {
|
||||||
let addr = Address::from_str(hex.trim_left_matches("0x")).unwrap();
|
Some(s.into())
|
||||||
let abi_path = format!("abis/{}/{}.json", network, contract_type);
|
}
|
||||||
let file = File::open(&abi_path)
|
|
||||||
.expect(&format!("ABI file not found: {}", abi_path));
|
|
||||||
let abi = Contract::load(&file)
|
|
||||||
.expect(&format!("Invalid ABI file: {}", abi_path));
|
|
||||||
PoaContract::new(*contract_type, addr, abi)
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let start_block = if let Some(s) = cli.value_of("start_block") {
|
let smtp_host_domain = if email_notifications {
|
||||||
s.into()
|
let host = env::var("SMTP_HOST_DOMAIN")
|
||||||
} else if cli.is_present("earliest") {
|
.map_err(|_| Error::MissingEnvVar("SMTP_HOST_DOMAIN".into()))?;
|
||||||
StartBlock::Earliest
|
Some(host)
|
||||||
} else if cli.is_present("latest") {
|
|
||||||
StartBlock::Latest
|
|
||||||
} else if let Some(s) = cli.value_of("tail") {
|
|
||||||
StartBlock::Tail(s.parse().expect("Invalid tail value"))
|
|
||||||
} else {
|
} else {
|
||||||
env::var("START_BLOCK").unwrap().as_str().into()
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let send_email_notifications = if cli.is_present("email") {
|
let smtp_port = if email_notifications {
|
||||||
true
|
if let Ok(s) = env::var("SMTP_PORT") {
|
||||||
|
let port = s.parse().map_err(|_| Error::InvalidSmtpPort(s.into()))?;
|
||||||
|
Some(port)
|
||||||
|
} else {
|
||||||
|
return Err(Error::MissingEnvVar("SMTP_PORT".into()));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
env::var("SEND_EMAIL_NOTIFICATIONS").unwrap().parse().unwrap()
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let send_push_notifications = if cli.is_present("push") {
|
let smtp_username = if email_notifications {
|
||||||
true
|
let username = env::var("SMTP_USERNAME")
|
||||||
|
.map_err(|_| Error::MissingEnvVar("SMTP_USERNAME".into()))?;
|
||||||
|
Some(username)
|
||||||
} else {
|
} else {
|
||||||
env::var("SEND_PUSH_NOTIFICATIONS").unwrap().parse().unwrap()
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let validators = env::var("VALIDATORS").unwrap().split(',')
|
let smtp_password = if email_notifications {
|
||||||
.map(|s| Validator { email: s.into(), name: "".into() })
|
let password = env::var("SMTP_PASSWORD")
|
||||||
.collect();
|
.map_err(|_| Error::MissingEnvVar("SMTP_PASSWORD".into()))?;
|
||||||
|
Some(password)
|
||||||
let avg_block_time = if let Some(s) = cli.value_of("block_time") {
|
|
||||||
Duration::from_secs(s.parse().unwrap())
|
|
||||||
} else {
|
} else {
|
||||||
let s = env::var("AVG_BLOCK_TIME_SECS").unwrap();
|
None
|
||||||
Duration::from_secs(s.parse().unwrap())
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let smtp_host_domain = env::var("SMTP_HOST_DOMAIN").unwrap();
|
let outgoing_email_addr = if email_notifications {
|
||||||
let smtp_port = env::var("SMTP_PORT").unwrap().parse().unwrap();
|
let email_addr = env::var("OUTGOING_EMAIL_ADDRESS")
|
||||||
let smtp_username = env::var("SMTP_USERNAME").unwrap();
|
.map_err(|_| Error::MissingEnvVar("OUTGOING_EMAIL_ADDRESS".into()))?;
|
||||||
let smtp_password = env::var("SMTP_PASSWORD").unwrap();
|
Some(email_addr)
|
||||||
let outgoing_email = env::var("OUTGOING_EMAIL_ADDRESS").unwrap();
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Config {
|
let notification_limit = if let Some(s) = cli.notification_limit() {
|
||||||
network, endpoint, contracts, start_block,
|
let limit = s.parse().map_err(|_| Error::InvalidNotificationLimit(s.into()))?;
|
||||||
send_email_notifications, send_push_notifications,
|
Some(limit)
|
||||||
validators, avg_block_time, smtp_host_domain,
|
} else {
|
||||||
smtp_port, smtp_username, smtp_password, outgoing_email
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let log_emails = cli.log_emails();
|
||||||
|
let log_to_file = cli.log_to_file();
|
||||||
|
|
||||||
|
let config = Config {
|
||||||
|
network,
|
||||||
|
endpoint,
|
||||||
|
version,
|
||||||
|
contracts,
|
||||||
|
start_block,
|
||||||
|
block_time,
|
||||||
|
email_notifications,
|
||||||
|
email_recipients,
|
||||||
|
smtp_host_domain,
|
||||||
|
smtp_port,
|
||||||
|
smtp_username,
|
||||||
|
smtp_password,
|
||||||
|
outgoing_email_addr,
|
||||||
|
notification_limit,
|
||||||
|
log_emails,
|
||||||
|
log_to_file,
|
||||||
|
};
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use super::super::tests::setup;
|
||||||
|
use super::{ContractType, ContractVersion, PoaContract, Network};
|
||||||
|
|
||||||
|
const CONTRACT_TYPES: [ContractType; 4] = [
|
||||||
|
ContractType::Keys,
|
||||||
|
ContractType::Threshold,
|
||||||
|
ContractType::Proxy,
|
||||||
|
ContractType::Emission,
|
||||||
|
];
|
||||||
|
const NETWORKS: [Network; 2] = [Network::Sokol, Network::Core];
|
||||||
|
const VERSIONS: [ContractVersion; 2] = [ContractVersion::V1, ContractVersion::V2];
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_env_file_integrity() {
|
||||||
|
setup();
|
||||||
|
for network in NETWORKS.iter() {
|
||||||
|
let env_var = format!("{}_RPC_ENDPOINT", network.uppercase());
|
||||||
|
assert!(env::var(&env_var).is_ok());
|
||||||
|
for contract_type in CONTRACT_TYPES.iter() {
|
||||||
|
for version in VERSIONS.iter() {
|
||||||
|
if *contract_type == ContractType::Emission && *version == ContractVersion::V1 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let env_var = format!(
|
||||||
|
"{}_CONTRACT_ADDRESS_{}_{:?}",
|
||||||
|
contract_type.uppercase(),
|
||||||
|
network.uppercase(),
|
||||||
|
version,
|
||||||
|
);
|
||||||
|
assert!(env::var(&env_var).is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load_contract_abis() {
|
||||||
|
setup();
|
||||||
|
for contract_type in CONTRACT_TYPES.iter() {
|
||||||
|
for version in VERSIONS.iter() {
|
||||||
|
for network in NETWORKS.iter() {
|
||||||
|
let res = PoaContract::read(*contract_type, *network, *version);
|
||||||
|
if *contract_type == ContractType::Emission && *version == ContractVersion::V1 {
|
||||||
|
assert!(res.is_err());
|
||||||
|
} else {
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
use ctrlc;
|
||||||
|
use jsonrpc_core;
|
||||||
|
use ethabi;
|
||||||
|
use failure;
|
||||||
|
use lettre;
|
||||||
|
use native_tls;
|
||||||
|
use reqwest;
|
||||||
|
|
||||||
|
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
CtrlcError(ctrlc::Error),
|
||||||
|
EmissionFundsV1ContractDoesNotExist,
|
||||||
|
FailedToBuildEmail(failure::Error),
|
||||||
|
FailedToBuildRequest(reqwest::Error),
|
||||||
|
FailedToBuildTls(native_tls::Error),
|
||||||
|
FailedToParseBallotCreatedLog(String),
|
||||||
|
FailedToParseRawLogToLog(ethabi::Error),
|
||||||
|
FailedToResolveSmtpHostDomain(lettre::smtp::error::Error),
|
||||||
|
FailedToSendEmail(lettre::smtp::error::Error),
|
||||||
|
InvalidAbi(String),
|
||||||
|
InvalidBlockTime(String),
|
||||||
|
InvalidContractAddr(String),
|
||||||
|
InvalidNotificationLimit(String),
|
||||||
|
InvalidSmtpPort(String),
|
||||||
|
InvalidStartBlock(String),
|
||||||
|
InvalidTail(String),
|
||||||
|
JsonRpcResponseFailure(jsonrpc_core::types::response::Failure),
|
||||||
|
MissingAbiFile(String),
|
||||||
|
MissingEnvVar(String),
|
||||||
|
MustSpecifyAtLeastOneCliArgument(String),
|
||||||
|
MustSpecifyOneCliArgument(String),
|
||||||
|
RequestFailed(reqwest::Error),
|
||||||
|
StartBlockExceedsLastBlockMined {
|
||||||
|
start_block: u64,
|
||||||
|
last_mined_block: u64,
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,245 @@
|
||||||
|
use std::fs::{self, create_dir, File, read_dir, remove_file};
|
||||||
|
use std::io::stderr;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use chrono::{DateTime, TimeZone, Utc};
|
||||||
|
use slog::{self, Drain};
|
||||||
|
use slog_term::{FullFormat, PlainSyncDecorator};
|
||||||
|
use web3::types::BlockNumber;
|
||||||
|
|
||||||
|
use config::Config;
|
||||||
|
use error::Error;
|
||||||
|
use notify::Notification;
|
||||||
|
|
||||||
|
// The date format used to name log files; e.g. "Oct-08-2018-14:09:00".
|
||||||
|
const FILE_NAME_DATE_FORMAT: &str = "%b-%d-%Y-%H:%M:%S";
|
||||||
|
// The directory (relative to Cargo.toml) to store logs.
|
||||||
|
const LOGS_DIR: &str = "logs";
|
||||||
|
const MAX_NUMBER_OF_LOG_FILES: usize = 3;
|
||||||
|
const MAX_LOG_FILE_SIZE_MB: usize = 4;
|
||||||
|
const MAX_LOG_FILE_SIZE_BYTES: usize = MAX_LOG_FILE_SIZE_MB * 1024 * 1024;
|
||||||
|
// We dont want to check the log file's size after every log that is written, this constant states
|
||||||
|
// "after this many logs have been written, check the log file's size". This value assumes an
|
||||||
|
// average log is around 100 ASCII characters (bytes) long.
|
||||||
|
const INITIAL_CHECK_FILE_SIZE_AT: usize = MAX_LOG_FILE_SIZE_BYTES / 100;
|
||||||
|
|
||||||
|
fn create_logs_dir() {
|
||||||
|
let logs_dir = Path::new(LOGS_DIR);
|
||||||
|
if !logs_dir.exists() {
|
||||||
|
create_dir(logs_dir)
|
||||||
|
.unwrap_or_else(|e| panic!("could not create ./logs directory: {:?}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_logs_dir() -> Vec<LogFile> {
|
||||||
|
let mut log_files: Vec<LogFile> = read_dir(LOGS_DIR)
|
||||||
|
.unwrap_or_else(|e| panic!("could not read ./logs directory: {:?}", e))
|
||||||
|
.filter_map(|res| {
|
||||||
|
let path = res.ok()?.path();
|
||||||
|
let file_name = path.file_name().unwrap().to_str().unwrap();
|
||||||
|
LogFile::from_file_name(file_name).ok()
|
||||||
|
}).collect();
|
||||||
|
log_files.sort_unstable();
|
||||||
|
log_files
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate_log_files(log_files: &mut Vec<LogFile>) -> File {
|
||||||
|
while log_files.len() >= MAX_NUMBER_OF_LOG_FILES {
|
||||||
|
let log_file_to_remove = log_files.remove(0);
|
||||||
|
log_file_to_remove.remove_file();
|
||||||
|
}
|
||||||
|
let log_file = LogFile::now();
|
||||||
|
let file = log_file.create_file();
|
||||||
|
log_files.push(log_file);
|
||||||
|
file
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_file_size_in_bytes(path: &str) -> usize {
|
||||||
|
fs::metadata(&path)
|
||||||
|
.unwrap_or_else(|_| panic!("log file does not exist: {}", path))
|
||||||
|
.len() as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LogLocation {
|
||||||
|
Stderr,
|
||||||
|
File(File),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_slog_logger(log_location: LogLocation) -> slog::Logger {
|
||||||
|
if let LogLocation::File(file) = log_location {
|
||||||
|
let decorator = PlainSyncDecorator::new(file);
|
||||||
|
let drain = FullFormat::new(decorator).build().fuse();
|
||||||
|
slog::Logger::root(drain, o!())
|
||||||
|
} else {
|
||||||
|
let decorator = PlainSyncDecorator::new(stderr());
|
||||||
|
let drain = FullFormat::new(decorator).build().fuse();
|
||||||
|
slog::Logger::root(drain, o!())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
struct LogFile(DateTime<Utc>);
|
||||||
|
|
||||||
|
impl LogFile {
|
||||||
|
fn now() -> Self {
|
||||||
|
LogFile(Utc::now())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_file_name(file_name: &str) -> Result<Self, ()> {
|
||||||
|
if let Ok(dt) = Utc.datetime_from_str(file_name, FILE_NAME_DATE_FORMAT) {
|
||||||
|
Ok(LogFile(dt))
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_name(&self) -> String {
|
||||||
|
self.0.format(FILE_NAME_DATE_FORMAT).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> String {
|
||||||
|
format!("{}/{}", LOGS_DIR, self.file_name())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_file(&self) -> File {
|
||||||
|
let path = self.path();
|
||||||
|
File::create(&path)
|
||||||
|
.unwrap_or_else(|_| panic!("failed to create log file: {}", path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_file(&self) {
|
||||||
|
let path = self.path();
|
||||||
|
remove_file(&path)
|
||||||
|
.unwrap_or_else(|_| panic!("failed to delete log file: {}", path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Logger {
|
||||||
|
logger: slog::Logger,
|
||||||
|
log_files: Vec<LogFile>,
|
||||||
|
log_count: usize,
|
||||||
|
check_file_size_at: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Logger {
|
||||||
|
pub fn new(config: &Config) -> Self {
|
||||||
|
let (logger, log_files) = if config.log_to_file {
|
||||||
|
create_logs_dir();
|
||||||
|
let mut log_files = read_logs_dir();
|
||||||
|
let current_log_file = rotate_log_files(&mut log_files);
|
||||||
|
let logger = create_slog_logger(LogLocation::File(current_log_file));
|
||||||
|
(logger, log_files)
|
||||||
|
} else {
|
||||||
|
let logger = create_slog_logger(LogLocation::Stderr);
|
||||||
|
(logger, vec![])
|
||||||
|
};
|
||||||
|
Logger {
|
||||||
|
logger,
|
||||||
|
log_files,
|
||||||
|
log_count: 0,
|
||||||
|
check_file_size_at: INITIAL_CHECK_FILE_SIZE_AT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn logging_to_file(&self) -> bool {
|
||||||
|
!self.log_files.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_rotate_log_file(&mut self) -> bool {
|
||||||
|
if self.logging_to_file() {
|
||||||
|
if self.log_count >= self.check_file_size_at {
|
||||||
|
let path = self.log_files.last().unwrap().path();
|
||||||
|
let file_size = get_file_size_in_bytes(&path);
|
||||||
|
if file_size >= MAX_LOG_FILE_SIZE_BYTES {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let avg_bytes_per_log = file_size / self.log_count;
|
||||||
|
let remaining_bytes = MAX_LOG_FILE_SIZE_BYTES - file_size;
|
||||||
|
let remaining_logs = remaining_bytes / avg_bytes_per_log;
|
||||||
|
self.check_file_size_at += remaining_logs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate_log_file(&mut self) {
|
||||||
|
let new_log_file = rotate_log_files(&mut self.log_files);
|
||||||
|
self.logger = create_slog_logger(LogLocation::File(new_log_file));
|
||||||
|
self.log_count = 0;
|
||||||
|
self.check_file_size_at = INITIAL_CHECK_FILE_SIZE_AT;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn increment_log_count(&mut self) {
|
||||||
|
self.log_count += 1;
|
||||||
|
if self.should_rotate_log_file() {
|
||||||
|
self.rotate_log_file();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_starting_poagov(&mut self) {
|
||||||
|
info!(&self.logger, "starting poagov...");
|
||||||
|
self.increment_log_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_ctrlc(&mut self) {
|
||||||
|
warn!(&self.logger, "recieved ctrl-c signal, gracefully shutting down...");
|
||||||
|
self.increment_log_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_no_email_recipients_configured(&mut self) {
|
||||||
|
warn!(&self.logger, "email notifications are enabled, but there are no email recipients");
|
||||||
|
self.increment_log_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_notification_email_body(&mut self, notif: &Notification) {
|
||||||
|
info!(&self.logger, "governance notification\n{}", notif.email_text());
|
||||||
|
self.increment_log_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_notification(&mut self, notif: &Notification) {
|
||||||
|
let ballot_created_log = notif.log();
|
||||||
|
info!(
|
||||||
|
&self.logger,
|
||||||
|
"governance notification";
|
||||||
|
"ballot" => format!("{:?}", ballot_created_log.ballot_type),
|
||||||
|
"ballot_id" => format!("{}", ballot_created_log.ballot_id),
|
||||||
|
"block_number" => format!("{}", ballot_created_log.block_number)
|
||||||
|
);
|
||||||
|
self.increment_log_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_failed_to_build_email(&mut self, e: Error) {
|
||||||
|
warn!(&self.logger, "failed to build email"; "error" => format!("{:?}", e));
|
||||||
|
self.increment_log_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_failed_to_send_email(&mut self, recipient: &str, e: Error) {
|
||||||
|
warn!(
|
||||||
|
&self.logger,
|
||||||
|
"failed to send email";
|
||||||
|
"recipient" => recipient,
|
||||||
|
"error" => format!("{:?}", e)
|
||||||
|
);
|
||||||
|
self.increment_log_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_email_sent(&mut self, recipient: &str) {
|
||||||
|
info!(&self.logger, "email sent"; "to" => recipient);
|
||||||
|
self.increment_log_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_reached_notification_limit(&mut self, notification_limit: usize) {
|
||||||
|
warn!(
|
||||||
|
&self.logger,
|
||||||
|
"reached notification limit, gracefully shutting down...";
|
||||||
|
"limit" => notification_limit
|
||||||
|
);
|
||||||
|
self.increment_log_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_finished_block_window(&mut self, start: BlockNumber, stop: BlockNumber) {
|
||||||
|
let block_range = format!("{:?}...{:?}", start, stop);
|
||||||
|
info!(&self.logger, "finished checking blocks"; "block_range" => block_range);
|
||||||
|
self.increment_log_count();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +0,0 @@
|
||||||
use std::io::stderr;
|
|
||||||
|
|
||||||
use lettre::smtp;
|
|
||||||
use slog::{Drain, Logger};
|
|
||||||
use slog_term::{FullFormat, PlainSyncDecorator};
|
|
||||||
|
|
||||||
use notify::Notification;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref LOGGER: Logger = {
|
|
||||||
let log_decorator = PlainSyncDecorator::new(stderr());
|
|
||||||
let drain = FullFormat::new(log_decorator).build().fuse();
|
|
||||||
Logger::root(drain, o!())
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn log_notification(notif: &Notification) {
|
|
||||||
let notif_data = match *notif {
|
|
||||||
Notification::Keys(ref inner) => format!("{:#?}", inner),
|
|
||||||
Notification::Threshold(ref inner) => format!("{:#?}", inner),
|
|
||||||
Notification::Proxy(ref inner) => format!("{:#?}", inner)
|
|
||||||
};
|
|
||||||
info!(LOGGER, "notification"; "data" => notif_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn log_email_sent(email: &str) {
|
|
||||||
info!(LOGGER, "email sent"; "to" => email);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn log_email_failed(email: &str, error: smtp::error::Error) {
|
|
||||||
warn!(
|
|
||||||
LOGGER,
|
|
||||||
"email failed";
|
|
||||||
"to" => email,
|
|
||||||
"error" => format!("{}", error)
|
|
||||||
);
|
|
||||||
}
|
|
123
src/main.rs
123
src/main.rs
|
@ -1,51 +1,124 @@
|
||||||
#![feature(try_from)]
|
|
||||||
|
|
||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
|
extern crate ctrlc;
|
||||||
extern crate dotenv;
|
extern crate dotenv;
|
||||||
extern crate ethabi;
|
extern crate ethabi;
|
||||||
extern crate ethereum_types;
|
extern crate ethereum_types;
|
||||||
|
extern crate failure;
|
||||||
extern crate hex;
|
extern crate hex;
|
||||||
extern crate jsonrpc_core;
|
extern crate jsonrpc_core;
|
||||||
#[macro_use] extern crate lazy_static;
|
|
||||||
extern crate lettre;
|
extern crate lettre;
|
||||||
extern crate lettre_email;
|
extern crate lettre_email;
|
||||||
extern crate native_tls;
|
extern crate native_tls;
|
||||||
extern crate reqwest;
|
extern crate reqwest;
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use] extern crate slog;
|
#[macro_use]
|
||||||
|
extern crate slog;
|
||||||
extern crate slog_term;
|
extern crate slog_term;
|
||||||
extern crate web3;
|
extern crate web3;
|
||||||
|
|
||||||
|
mod blockchain;
|
||||||
mod cli;
|
mod cli;
|
||||||
|
mod client;
|
||||||
mod config;
|
mod config;
|
||||||
mod logging;
|
mod error;
|
||||||
|
mod logger;
|
||||||
mod notify;
|
mod notify;
|
||||||
mod rpc;
|
mod response;
|
||||||
mod utils;
|
|
||||||
|
|
||||||
use config::Config;
|
use std::sync::{Arc, Mutex};
|
||||||
use notify::Notifier;
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use rpc::{BlockchainIter, RpcClient};
|
|
||||||
|
|
||||||
fn main() {
|
use blockchain::BlockchainIter;
|
||||||
let config = Config::load();
|
use cli::Cli;
|
||||||
let client = RpcClient::new(&config.endpoint);
|
use client::RpcClient;
|
||||||
let mut notifier = Notifier::new(&config).unwrap();
|
use config::{Config, ContractVersion};
|
||||||
|
use error::{Error, Result};
|
||||||
|
use logger::Logger;
|
||||||
|
use notify::{Notification, Notifier};
|
||||||
|
|
||||||
|
fn load_env_file() {
|
||||||
|
if let Err(e) = dotenv::dotenv() {
|
||||||
|
match e {
|
||||||
|
dotenv::Error::Io(_) => panic!("could not find .env file"),
|
||||||
|
_ => panic!("coule not parse .env file"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_ctrlc_handler(logger: Arc<Mutex<Logger>>) -> Result<Arc<AtomicBool>> {
|
||||||
|
let running = Arc::new(AtomicBool::new(true));
|
||||||
|
let result = Ok(running.clone());
|
||||||
|
ctrlc::set_handler(move || {
|
||||||
|
logger.lock().unwrap().log_ctrlc();
|
||||||
|
running.store(false, Ordering::SeqCst);
|
||||||
|
}).map_err(|e| Error::CtrlcError(e))?;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
load_env_file();
|
||||||
|
|
||||||
for (start_block, stop_block) in BlockchainIter::new(&client, &config) {
|
let cli = Cli::parse();
|
||||||
for contract in &config.contracts {
|
let config = Config::new(&cli)?;
|
||||||
let ballot_created_logs = client
|
let logger = Arc::new(Mutex::new(Logger::new(&config)));
|
||||||
.get_ballot_created_logs(contract, start_block, stop_block)
|
if config.email_notifications && config.email_recipients.is_empty() {
|
||||||
.unwrap();
|
logger.lock().unwrap().log_no_email_recipients_configured();
|
||||||
|
}
|
||||||
|
let running = set_ctrlc_handler(logger.clone())?;
|
||||||
|
let client = RpcClient::new(config.endpoint.clone());
|
||||||
|
let mut notifier = Notifier::new(&config, logger.clone())?;
|
||||||
|
logger.lock().unwrap().log_starting_poagov();
|
||||||
|
|
||||||
for log in &ballot_created_logs {
|
'main_loop: for iter_res in BlockchainIter::new(&client, &config, running)? {
|
||||||
let voting_data = client.get_voting_state(contract, log.ballot_id).unwrap();
|
let (start_block, stop_block) = iter_res?;
|
||||||
let notif = notifier.build_notification(log, &voting_data);
|
let mut notifications = vec![];
|
||||||
notifier.notify_validators(¬if);
|
for contract in config.contracts.iter() {
|
||||||
|
let ballot_created_logs = client.get_ballot_created_logs(
|
||||||
|
contract,
|
||||||
|
start_block,
|
||||||
|
stop_block,
|
||||||
|
)?;
|
||||||
|
for log in ballot_created_logs.into_iter() {
|
||||||
|
let notification = if contract.version == ContractVersion::V1 {
|
||||||
|
let voting_state = client.get_voting_state(contract, log.ballot_id)?;
|
||||||
|
Notification::from_voting_state(&config, log, voting_state)
|
||||||
|
} else {
|
||||||
|
let ballot_info = client.get_ballot_info(contract, log.ballot_id)?;
|
||||||
|
Notification::from_ballot_info(&config, log, ballot_info)
|
||||||
|
};
|
||||||
|
notifications.push(notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notifications.sort_unstable_by(|notif1, notif2| {
|
||||||
|
notif1.log().block_number.cmp(¬if2.log().block_number)
|
||||||
|
});
|
||||||
|
for notification in notifications.iter() {
|
||||||
|
notifier.notify(notification);
|
||||||
|
if notifier.reached_limit() {
|
||||||
|
let limit = config.notification_limit.unwrap();
|
||||||
|
logger.lock().unwrap().log_reached_notification_limit(limit);
|
||||||
|
break 'main_loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.lock().unwrap().log_finished_block_window(start_block, stop_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use super::load_env_file;
|
||||||
|
|
||||||
|
static mut LOADED_ENV_FILE: bool = false;
|
||||||
|
|
||||||
|
pub fn setup() {
|
||||||
|
unsafe {
|
||||||
|
if !LOADED_ENV_FILE {
|
||||||
|
load_env_file();
|
||||||
|
LOADED_ENV_FILE = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
306
src/notify.rs
306
src/notify.rs
|
@ -1,199 +1,187 @@
|
||||||
use chrono::{DateTime, Utc};
|
use std::sync::{Arc, Mutex};
|
||||||
use ethereum_types::Address;
|
|
||||||
use lettre::{EmailTransport, SmtpTransport};
|
use lettre::{SendableEmail, Transport};
|
||||||
use lettre::smtp::{ClientSecurity, ConnectionReuseParameters, SmtpTransportBuilder};
|
use lettre::smtp::{ClientSecurity, ConnectionReuseParameters, SmtpClient, SmtpTransport};
|
||||||
use lettre::smtp::authentication::{Credentials, Mechanism};
|
use lettre::smtp::authentication::{Credentials, Mechanism};
|
||||||
use lettre::smtp::client::net::{ClientTlsParameters, DEFAULT_TLS_PROTOCOLS};
|
use lettre::smtp::client::net::ClientTlsParameters;
|
||||||
use lettre::smtp::error::Error as BuildSmtpError;
|
|
||||||
use lettre_email::{Email, EmailBuilder};
|
use lettre_email::{Email, EmailBuilder};
|
||||||
use lettre_email::error::Error as BuildEmailError;
|
|
||||||
use native_tls::TlsConnector;
|
use native_tls::TlsConnector;
|
||||||
|
|
||||||
use config::{Config, ContractType, Network, Validator};
|
use config::Config;
|
||||||
use logging::{log_email_failed, log_email_sent, log_notification};
|
use error::{Error, Result};
|
||||||
use rpc::{BallotCreatedLog, BallotType, KeyType, VotingData};
|
use logger::Logger;
|
||||||
|
use response::common::BallotCreatedLog;
|
||||||
|
use response::v1::VotingState;
|
||||||
|
use response::v2::BallotInfo;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Notification {
|
pub enum Notification<'a> {
|
||||||
Keys(KeysNotification),
|
VotingState {
|
||||||
Threshold(ThresholdNotification),
|
config: &'a Config,
|
||||||
Proxy(ProxyNotification)
|
log: BallotCreatedLog,
|
||||||
|
voting_state: VotingState,
|
||||||
|
},
|
||||||
|
BallotInfo {
|
||||||
|
config: &'a Config,
|
||||||
|
log: BallotCreatedLog,
|
||||||
|
ballot_info: BallotInfo,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl<'a> Notification<'a> {
|
||||||
pub struct KeysNotification {
|
pub fn from_voting_state(
|
||||||
pub network: Network,
|
config: &'a Config,
|
||||||
pub endpoint: String,
|
log: BallotCreatedLog,
|
||||||
pub block_number: u64,
|
voting_state: VotingState,
|
||||||
pub contract_type: ContractType,
|
) -> Self
|
||||||
pub ballot_type: BallotType,
|
{
|
||||||
pub ballot_id: u64,
|
Notification::VotingState { config, log, voting_state }
|
||||||
pub start_time: DateTime<Utc>,
|
}
|
||||||
pub end_time: DateTime<Utc>,
|
|
||||||
pub memo: String,
|
pub fn from_ballot_info(
|
||||||
pub affected_key: Address,
|
config: &'a Config,
|
||||||
pub affected_key_type: KeyType
|
log: BallotCreatedLog,
|
||||||
}
|
ballot_info: BallotInfo,
|
||||||
|
) -> Self
|
||||||
|
{
|
||||||
|
Notification::BallotInfo { config, log, ballot_info }
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub fn email_text(&self) -> String {
|
||||||
pub struct ThresholdNotification {
|
format!(
|
||||||
pub network: Network,
|
"Network: {:?}\n\
|
||||||
pub endpoint: String,
|
RPC Endpoint: {}\n\
|
||||||
pub block_number: u64,
|
Block Number: {}\n\
|
||||||
pub contract_type: ContractType,
|
Contract: {}\n\
|
||||||
pub ballot_type: BallotType,
|
Version: {:?}\n\
|
||||||
pub ballot_id: u64,
|
Ballot ID: {}\n\
|
||||||
pub start_time: DateTime<Utc>,
|
{}\n",
|
||||||
pub end_time: DateTime<Utc>,
|
self.config().network,
|
||||||
pub memo: String,
|
self.config().endpoint,
|
||||||
pub proposed_value: u64
|
self.log().block_number,
|
||||||
}
|
self.contract_name(),
|
||||||
|
self.config().version,
|
||||||
|
self.log().ballot_id,
|
||||||
|
self.email_body(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
fn config(&self) -> &Config {
|
||||||
pub struct ProxyNotification {
|
match self {
|
||||||
pub network: Network,
|
Notification::VotingState { config, .. } => config,
|
||||||
pub endpoint: String,
|
Notification::BallotInfo { config, .. } => config,
|
||||||
pub block_number: u64,
|
}
|
||||||
pub contract_type: ContractType,
|
}
|
||||||
pub ballot_type: BallotType,
|
|
||||||
pub ballot_id: u64,
|
|
||||||
pub start_time: DateTime<Utc>,
|
|
||||||
pub end_time: DateTime<Utc>,
|
|
||||||
pub memo: String,
|
|
||||||
pub proposed_value: Address
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Notification {
|
pub fn log(&self) -> &BallotCreatedLog {
|
||||||
fn new(config: &Config, log: &BallotCreatedLog, voting_data: &VotingData) -> Self {
|
match self {
|
||||||
let network = config.network;
|
Notification::VotingState { log, .. } => log,
|
||||||
let endpoint = config.endpoint.clone();
|
Notification::BallotInfo { log, .. } => log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contract_name(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Notification::VotingState { voting_state, .. } => voting_state.contract_name(),
|
||||||
|
Notification::BallotInfo { ballot_info, .. } => ballot_info.contract_name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let block_number = log.block_number;
|
fn email_body(&self) -> String {
|
||||||
let ballot_type = log.ballot_type;
|
match self {
|
||||||
let ballot_id = log.ballot_id;
|
Notification::VotingState { voting_state, .. } => voting_state.email_text(),
|
||||||
|
Notification::BallotInfo { ballot_info, .. } => ballot_info.email_text(),
|
||||||
let start_time = voting_data.start_time();
|
|
||||||
let end_time = voting_data.end_time();
|
|
||||||
let memo = voting_data.memo();
|
|
||||||
|
|
||||||
match *voting_data {
|
|
||||||
VotingData::Keys(ref data) => {
|
|
||||||
let contract_type = ContractType::Keys;
|
|
||||||
let affected_key = data.affected_key;
|
|
||||||
let affected_key_type = data.affected_key_type;
|
|
||||||
let notification = KeysNotification {
|
|
||||||
network, endpoint, block_number,
|
|
||||||
contract_type, ballot_type, ballot_id,
|
|
||||||
start_time, end_time, memo,
|
|
||||||
affected_key, affected_key_type
|
|
||||||
};
|
|
||||||
Notification::Keys(notification)
|
|
||||||
},
|
|
||||||
VotingData::Threshold(ref data) => {
|
|
||||||
let contract_type = ContractType::Threshold;
|
|
||||||
let proposed_value = data.proposed_value;
|
|
||||||
let notification = ThresholdNotification {
|
|
||||||
network, endpoint, block_number,
|
|
||||||
contract_type, ballot_type,
|
|
||||||
ballot_id, start_time, end_time,
|
|
||||||
memo, proposed_value
|
|
||||||
};
|
|
||||||
Notification::Threshold(notification)
|
|
||||||
},
|
|
||||||
VotingData::Proxy(ref data) => {
|
|
||||||
let contract_type = ContractType::Proxy;
|
|
||||||
let proposed_value = data.proposed_value;
|
|
||||||
let notification = ProxyNotification {
|
|
||||||
network, endpoint, block_number,
|
|
||||||
contract_type, ballot_type,
|
|
||||||
ballot_id, start_time, end_time,
|
|
||||||
memo, proposed_value
|
|
||||||
};
|
|
||||||
Notification::Proxy(notification)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Notifier<'a> {
|
pub struct Notifier<'a> {
|
||||||
config: &'a Config,
|
config: &'a Config,
|
||||||
mailer: Option<SmtpTransport>
|
emailer: Option<SmtpTransport>,
|
||||||
|
logger: Arc<Mutex<Logger>>,
|
||||||
|
notification_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Notifier<'a> {
|
impl<'a> Notifier<'a> {
|
||||||
pub fn new(config: &'a Config) -> Result<Self, BuildSmtpError> {
|
pub fn new(config: &'a Config, logger: Arc<Mutex<Logger>>) -> Result<Self> {
|
||||||
let mut notifier = Notifier { config, mailer: None };
|
let emailer = if config.email_notifications {
|
||||||
|
let domain = config.smtp_host_domain.clone().unwrap();
|
||||||
if config.send_email_notifications {
|
let port = config.smtp_port.unwrap();
|
||||||
let smtp_addr = (config.smtp_host_domain.as_str(), config.smtp_port);
|
let addr = (domain.as_str(), port);
|
||||||
|
let security = {
|
||||||
let smtp_tls = {
|
let tls = TlsConnector::new().map_err(|e| Error::FailedToBuildTls(e))?;
|
||||||
let mut tls_builder = TlsConnector::builder().unwrap();
|
let smtp_security_setup = ClientTlsParameters::new(domain.clone(), tls);
|
||||||
tls_builder.supported_protocols(DEFAULT_TLS_PROTOCOLS).unwrap();
|
ClientSecurity::Required(smtp_security_setup)
|
||||||
let tls = tls_builder.build().unwrap();
|
|
||||||
let tls_params = ClientTlsParameters::new(config.smtp_host_domain.clone(), tls);
|
|
||||||
ClientSecurity::Required(tls_params)
|
|
||||||
};
|
};
|
||||||
|
let creds = Credentials::new(
|
||||||
let smtp_creds = Credentials::new(
|
config.smtp_username.clone().unwrap(),
|
||||||
config.smtp_username.clone(),
|
config.smtp_password.clone().unwrap(),
|
||||||
config.smtp_password.clone()
|
|
||||||
);
|
);
|
||||||
|
let smtp = SmtpClient::new(addr, security)
|
||||||
let mailer = SmtpTransportBuilder::new(smtp_addr, smtp_tls)?
|
.map_err(|e| Error::FailedToResolveSmtpHostDomain(e))?
|
||||||
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited)
|
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited)
|
||||||
.authentication_mechanism(Mechanism::Plain)
|
.authentication_mechanism(Mechanism::Plain)
|
||||||
.credentials(smtp_creds)
|
.credentials(creds)
|
||||||
.build();
|
.transport();
|
||||||
|
Some(smtp)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Ok(Notifier { config, emailer, logger, notification_count: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
notifier.mailer = Some(mailer);
|
pub fn notify(&mut self, notif: &Notification) {
|
||||||
|
if self.config.log_emails {
|
||||||
|
self.logger.lock().unwrap().log_notification_email_body(notif);
|
||||||
|
} else {
|
||||||
|
self.logger.lock().unwrap().log_notification(notif);
|
||||||
}
|
}
|
||||||
|
if self.config.email_notifications {
|
||||||
Ok(notifier)
|
for recipient in self.config.email_recipients.iter() {
|
||||||
}
|
let email: SendableEmail = match self.build_email(notif, recipient) {
|
||||||
|
Ok(email) => email.into(),
|
||||||
pub fn build_notification(&self, log: &BallotCreatedLog, voting_data: &VotingData) -> Notification {
|
Err(e) => {
|
||||||
Notification::new(&self.config, log, voting_data)
|
self.logger.lock().unwrap().log_failed_to_build_email(e);
|
||||||
}
|
continue;
|
||||||
|
},
|
||||||
pub fn notify_validators(&mut self, notif: &Notification) {
|
};
|
||||||
log_notification(notif);
|
if let Err(e) = self.send_email(email) {
|
||||||
for validator in &self.config.validators {
|
self.logger.lock().unwrap().log_failed_to_send_email(recipient, e);
|
||||||
if self.config.send_email_notifications {
|
} else {
|
||||||
let email = self.build_email(validator, notif).unwrap();
|
self.logger.lock().unwrap().log_email_sent(recipient);
|
||||||
if let Some(ref mut mailer) = self.mailer {
|
|
||||||
match mailer.send(&email) {
|
|
||||||
Ok(_) => log_email_sent(&validator.email),
|
|
||||||
Err(e) => log_email_failed(&validator.email, e)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
self.notification_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
if self.config.send_push_notifications {
|
pub fn reached_limit(&self) -> bool {
|
||||||
println!("Push Notifications not yet implemented.");
|
if let Some(limit) = self.config.notification_limit {
|
||||||
}
|
self.notification_count >= limit
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_email(&self, validator: &Validator, notif: &Notification) -> Result<Email, BuildEmailError> {
|
fn build_email(&self, notif: &Notification, recipient: &str) -> Result<Email> {
|
||||||
let body = match *notif {
|
let outgoing_email = self.config.outgoing_email_addr.clone().unwrap();
|
||||||
Notification::Keys(ref inner) => format!("{:#?}\n", inner),
|
|
||||||
Notification::Threshold(ref inner) => format!("{:#?}\n", inner),
|
|
||||||
Notification::Proxy(ref inner) => format!("{:#?}\n", inner)
|
|
||||||
};
|
|
||||||
EmailBuilder::new()
|
EmailBuilder::new()
|
||||||
.to(validator.email.as_str())
|
.to(recipient)
|
||||||
.from(self.config.outgoing_email.as_str())
|
.from(outgoing_email.as_str())
|
||||||
.subject("POA Network Governance Notification")
|
.subject("POA Network Governance Notification")
|
||||||
.text(body)
|
.text(notif.email_text())
|
||||||
.build()
|
.build()
|
||||||
|
.map_err(|e| Error::FailedToBuildEmail(e))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Drop for Notifier<'a> {
|
fn send_email(&mut self, email: SendableEmail) -> Result<()> {
|
||||||
fn drop(&mut self) {
|
if let Some(ref mut emailer) = self.emailer {
|
||||||
if let Some(ref mut mailer) = self.mailer {
|
match emailer.send(email) {
|
||||||
mailer.close();
|
Ok(_response) => Ok(()),
|
||||||
|
Err(e) => Err(Error::FailedToSendEmail(e)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!("Attempted to send email without SMTP client setup");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
|
use ethabi;
|
||||||
|
use web3::types::{Address, H256, U256};
|
||||||
|
|
||||||
|
use error::{Error, Result};
|
||||||
|
|
||||||
|
/// Converts a `U256` timestamp to a UTC `DateTime`.
|
||||||
|
pub fn u256_to_datetime(uint: U256) -> DateTime<Utc> {
|
||||||
|
let timestamp = uint.low_u64() as i64;
|
||||||
|
let naive = NaiveDateTime::from_timestamp(timestamp, 0);
|
||||||
|
DateTime::from_utc(naive, Utc)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Identifies what type of key is being voted on by the `votingToChangeKeys.sol` contract. This
|
||||||
|
/// enum is used in the V1 and V2 Keys contracts.
|
||||||
|
///
|
||||||
|
/// V1 Keys Contract (`KeyType` is used within the contract's `votingState`):
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/aa45e19ca50f7cae308c1281d950245b0c65182a/contracts/VotingToChangeKeys.sol#L11
|
||||||
|
///
|
||||||
|
/// V2 `KeyTypes` enum (used by the V2 Keys Contract's `ballotInfo`):
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/ec307069302fdf6647e8b1bdc13093960913b266/contracts/abstracts/EnumKeyTypes.sol#L5
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum KeyType {
|
||||||
|
InvalidKey,
|
||||||
|
MiningKey,
|
||||||
|
VotingKey,
|
||||||
|
PayoutKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<U256> for KeyType {
|
||||||
|
fn from(key_type: U256) -> Self {
|
||||||
|
match key_type.low_u64() {
|
||||||
|
0 => KeyType::InvalidKey,
|
||||||
|
1 => KeyType::MiningKey,
|
||||||
|
2 => KeyType::VotingKey,
|
||||||
|
3 => KeyType::PayoutKey,
|
||||||
|
n => unreachable!("unrecognized `KeyType`: {}", n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// V1 Keys Contract (used in `BallotCreated` event and within the `votingState`):
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/aa45e19ca50f7cae308c1281d950245b0c65182a/contracts/VotingToChangeKeys.sol#L10
|
||||||
|
///
|
||||||
|
/// V1 Threshold Contract (used in `BallotCreated` event):
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/aa45e19ca50f7cae308c1281d950245b0c65182a/contracts/VotingToChangeMinThreshold.sol#L89
|
||||||
|
///
|
||||||
|
/// V1 Proxy Contract (used in `BallotCreated` event):
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/aa45e19ca50f7cae308c1281d950245b0c65182a/contracts/VotingToChangeProxyAddress.sol#L85
|
||||||
|
///
|
||||||
|
/// Note: V1 contracts do not use the `Emission` variant.
|
||||||
|
///
|
||||||
|
/// V2 - all contracts use the same enum:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/ec307069302fdf6647e8b1bdc13093960913b266/contracts/abstracts/EnumBallotTypes.sol#L5
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum BallotType {
|
||||||
|
InvalidKey,
|
||||||
|
AddKey,
|
||||||
|
RemoveKey,
|
||||||
|
SwapKey,
|
||||||
|
Threshold,
|
||||||
|
Proxy,
|
||||||
|
Emission,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a `U256` (from a V2 keys contract's voting-state) into a `BallotType`.
|
||||||
|
impl From<U256> for BallotType {
|
||||||
|
fn from(uint: U256) -> Self {
|
||||||
|
BallotType::from(H256::from(uint))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts an `H256` from a `web3::types::Log`'s `topics` vector to a `BallotType`.
|
||||||
|
impl From<H256> for BallotType {
|
||||||
|
fn from(topic: H256) -> Self {
|
||||||
|
match topic.low_u64() {
|
||||||
|
0 => BallotType::InvalidKey,
|
||||||
|
1 => BallotType::AddKey,
|
||||||
|
2 => BallotType::RemoveKey,
|
||||||
|
3 => BallotType::SwapKey,
|
||||||
|
4 => BallotType::Threshold,
|
||||||
|
5 => BallotType::Proxy,
|
||||||
|
6 => BallotType::Emission,
|
||||||
|
n => unreachable!("unrecognized `BallotType`: {}", n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A parsed `BallotCreated` event log. All V1 and V2 contracts use the same `BallotCreated` event.
|
||||||
|
///
|
||||||
|
/// V1 Keys Contract's `BallotCreated` event:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/aa45e19ca50f7cae308c1281d950245b0c65182a/contracts/VotingToChangeKeys.sol#L45
|
||||||
|
///
|
||||||
|
/// V1 Threshold Contract's `BallotCreated` event:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/aa45e19ca50f7cae308c1281d950245b0c65182a/contracts/VotingToChangeMinThreshold.sol#L40
|
||||||
|
///
|
||||||
|
/// V1 Proxy Contract's `BallotCreated` event:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/aa45e19ca50f7cae308c1281d950245b0c65182a/contracts/VotingToChangeMinThreshold.sol#L40
|
||||||
|
///
|
||||||
|
/// V2 - all contracts use the same `BallotCreated` event:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/ec307069302fdf6647e8b1bdc13093960913b266/contracts/abstracts/VotingTo.sol#L30
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct BallotCreatedLog {
|
||||||
|
pub block_number: U256,
|
||||||
|
pub ballot_id: U256,
|
||||||
|
pub ballot_type: BallotType,
|
||||||
|
pub creator: Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BallotCreatedLog {
|
||||||
|
pub fn from_ethabi_log(log: ethabi::Log, block_number: U256) -> Result<Self> {
|
||||||
|
let mut ballot_id: Option<U256> = None;
|
||||||
|
let mut ballot_type: Option<BallotType> = None;
|
||||||
|
let mut creator: Option<Address> = None;
|
||||||
|
for ethabi::LogParam { name, value } in log.params {
|
||||||
|
match name.as_ref() {
|
||||||
|
"id" => ballot_id = value.to_uint(),
|
||||||
|
"ballotType" => ballot_type = value.to_uint().map(BallotType::from),
|
||||||
|
"creator" => creator = value.to_address(),
|
||||||
|
name => unreachable!("Found unknown `BallotCreated` event log field: {}", name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let ballot_id = match ballot_id {
|
||||||
|
Some(id) => id,
|
||||||
|
None => return Err(Error::FailedToParseBallotCreatedLog("missing `id`".into())),
|
||||||
|
};
|
||||||
|
let ballot_type = match ballot_type {
|
||||||
|
Some(ballot_type) => ballot_type,
|
||||||
|
None => return Err(Error::FailedToParseBallotCreatedLog("missing `ballot_type`".into())),
|
||||||
|
};
|
||||||
|
let creator = match creator {
|
||||||
|
Some(creator) => creator,
|
||||||
|
None => return Err(Error::FailedToParseBallotCreatedLog("missing `creator`".into())),
|
||||||
|
};
|
||||||
|
Ok(BallotCreatedLog { ballot_id, ballot_type, creator, block_number })
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod common;
|
||||||
|
pub mod v1;
|
||||||
|
pub mod v2;
|
|
@ -0,0 +1,323 @@
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use ethabi;
|
||||||
|
use web3::types::{Address, U256};
|
||||||
|
|
||||||
|
use response::common::{u256_to_datetime, BallotType, KeyType};
|
||||||
|
|
||||||
|
/// Describes the current state of a given ballot.
|
||||||
|
///
|
||||||
|
/// The same `QuorumStates` enum is used by all V1 contracts.
|
||||||
|
///
|
||||||
|
/// V1 Keys Contract:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/aa45e19ca50f7cae308c1281d950245b0c65182a/contracts/VotingToChangeKeys.sol#L12
|
||||||
|
///
|
||||||
|
/// V1 Threshold Contract:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/aa45e19ca50f7cae308c1281d950245b0c65182a/contracts/VotingToChangeMinThreshold.sol#L10
|
||||||
|
///
|
||||||
|
/// V1 Proxy Contract:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/aa45e19ca50f7cae308c1281d950245b0c65182a/contracts/VotingToChangeProxyAddress.sol#L10
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum QuorumState {
|
||||||
|
Invalid,
|
||||||
|
InProgress,
|
||||||
|
Accepted,
|
||||||
|
Rejected,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<U256> for QuorumState {
|
||||||
|
fn from(uint: U256) -> Self {
|
||||||
|
match uint.low_u64() {
|
||||||
|
0 => QuorumState::Invalid,
|
||||||
|
1 => QuorumState::InProgress,
|
||||||
|
2 => QuorumState::Accepted,
|
||||||
|
3 => QuorumState::Rejected,
|
||||||
|
_ => unreachable!("unrecognized `QuorumState`: {}", uint),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum VotingState {
|
||||||
|
Keys(KeysVotingState),
|
||||||
|
Threshold(ThresholdVotingState),
|
||||||
|
Proxy(ProxyVotingState),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<KeysVotingState> for VotingState {
|
||||||
|
fn from(keys_voting_state: KeysVotingState) -> Self {
|
||||||
|
VotingState::Keys(keys_voting_state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ThresholdVotingState> for VotingState {
|
||||||
|
fn from(threshold_voting_state: ThresholdVotingState) -> Self {
|
||||||
|
VotingState::Threshold(threshold_voting_state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ProxyVotingState> for VotingState {
|
||||||
|
fn from(proxy_voting_state: ProxyVotingState) -> Self {
|
||||||
|
VotingState::Proxy(proxy_voting_state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VotingState {
|
||||||
|
pub fn contract_name(&self) -> String {
|
||||||
|
match self {
|
||||||
|
VotingState::Keys(_) => "VotingToChangeKeys.sol".into(),
|
||||||
|
VotingState::Threshold(_) => "VotingToChangeMinThreshold.sol".into(),
|
||||||
|
VotingState::Proxy(_) => "VotingToChangeProxyAddress.sol".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn email_text(&self) -> String {
|
||||||
|
match self {
|
||||||
|
VotingState::Keys(state) => state.email_text(),
|
||||||
|
VotingState::Threshold(state) => state.email_text(),
|
||||||
|
VotingState::Proxy(state) => state.email_text(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// V1 Key's Contract:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/aa45e19ca50f7cae308c1281d950245b0c65182a/contracts/VotingToChangeKeys.sol#L22
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct KeysVotingState {
|
||||||
|
pub start_time: DateTime<Utc>,
|
||||||
|
pub end_time: DateTime<Utc>,
|
||||||
|
pub affected_key: Address,
|
||||||
|
pub affected_key_type: KeyType,
|
||||||
|
pub mining_key: Address,
|
||||||
|
pub total_voters: U256,
|
||||||
|
pub progress: U256,
|
||||||
|
pub is_finalized: bool,
|
||||||
|
pub quorum_state: QuorumState,
|
||||||
|
pub ballot_type: BallotType,
|
||||||
|
pub index: U256,
|
||||||
|
pub min_threshold_of_voters: U256,
|
||||||
|
pub creator: Address,
|
||||||
|
pub memo: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<ethabi::Token>> for KeysVotingState {
|
||||||
|
fn from(tokens: Vec<ethabi::Token>) -> Self {
|
||||||
|
let start_time = {
|
||||||
|
let uint = tokens[0].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let end_time = {
|
||||||
|
let uint = tokens[1].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let affected_key = tokens[2].clone().to_address().unwrap();
|
||||||
|
let affected_key_type = tokens[3].clone().to_uint().unwrap().into();
|
||||||
|
let mining_key = tokens[4].clone().to_address().unwrap();
|
||||||
|
let total_voters = tokens[5].clone().to_uint().unwrap();
|
||||||
|
let progress = tokens[6].clone().to_int().unwrap();
|
||||||
|
let is_finalized = tokens[7].clone().to_bool().unwrap();
|
||||||
|
let quorum_state = tokens[8].clone().to_uint().unwrap().into();
|
||||||
|
let ballot_type = tokens[9].clone().to_uint().unwrap().into();
|
||||||
|
let index = tokens[10].clone().to_uint().unwrap();
|
||||||
|
let min_threshold_of_voters = tokens[11].clone().to_uint().unwrap();
|
||||||
|
let creator = tokens[12].clone().to_address().unwrap();
|
||||||
|
let memo = tokens[13].clone().to_string().unwrap();
|
||||||
|
KeysVotingState {
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
affected_key,
|
||||||
|
affected_key_type,
|
||||||
|
mining_key,
|
||||||
|
total_voters,
|
||||||
|
progress,
|
||||||
|
is_finalized,
|
||||||
|
quorum_state,
|
||||||
|
ballot_type,
|
||||||
|
index,
|
||||||
|
min_threshold_of_voters,
|
||||||
|
creator,
|
||||||
|
memo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeysVotingState {
|
||||||
|
fn email_text(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Voting Start Time: {}\n\
|
||||||
|
Voting End Time: {}\n\
|
||||||
|
Ballot Type: {:?}\n\
|
||||||
|
Affected Key: {:?}\n\
|
||||||
|
Affected Key Type: {:?}\n\
|
||||||
|
Voting has Finished: {}\n\
|
||||||
|
Number of Votes Made: {}\n\
|
||||||
|
Number of Votes Required to Make Change: {}\n\
|
||||||
|
Mining Key: {:?}\n\
|
||||||
|
Ballot Creator: {:?}\n\
|
||||||
|
Memo: {}\n",
|
||||||
|
self.start_time,
|
||||||
|
self.end_time,
|
||||||
|
self.ballot_type,
|
||||||
|
self.affected_key,
|
||||||
|
self.affected_key_type,
|
||||||
|
self.is_finalized,
|
||||||
|
self.total_voters,
|
||||||
|
self.min_threshold_of_voters,
|
||||||
|
self.mining_key,
|
||||||
|
self.creator,
|
||||||
|
self.memo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// V1 Threshold Contract:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/aa45e19ca50f7cae308c1281d950245b0c65182a/contracts/VotingToChangeMinThreshold.sol#L20
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ThresholdVotingState {
|
||||||
|
pub start_time: DateTime<Utc>,
|
||||||
|
pub end_time: DateTime<Utc>,
|
||||||
|
pub total_voters: U256,
|
||||||
|
pub progress: U256,
|
||||||
|
pub is_finalized: bool,
|
||||||
|
pub quorum_state: QuorumState,
|
||||||
|
pub index: U256,
|
||||||
|
pub min_threshold_of_voters: U256,
|
||||||
|
pub proposed_value: U256,
|
||||||
|
pub creator: Address,
|
||||||
|
pub memo: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<ethabi::Token>> for ThresholdVotingState {
|
||||||
|
fn from(tokens: Vec<ethabi::Token>) -> Self {
|
||||||
|
let start_time = {
|
||||||
|
let uint = tokens[0].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let end_time = {
|
||||||
|
let uint = tokens[1].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let total_voters = tokens[2].clone().to_uint().unwrap();
|
||||||
|
let progress = tokens[3].clone().to_int().unwrap();
|
||||||
|
let is_finalized = tokens[4].clone().to_bool().unwrap();
|
||||||
|
let quorum_state = tokens[5].clone().to_uint().unwrap().into();
|
||||||
|
let index = tokens[6].clone().to_uint().unwrap();
|
||||||
|
let min_threshold_of_voters = tokens[7].clone().to_uint().unwrap();
|
||||||
|
let proposed_value = tokens[8].clone().to_uint().unwrap();
|
||||||
|
let creator = tokens[9].clone().to_address().unwrap();
|
||||||
|
let memo = tokens[10].clone().to_string().unwrap();
|
||||||
|
ThresholdVotingState {
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
total_voters,
|
||||||
|
progress,
|
||||||
|
is_finalized,
|
||||||
|
quorum_state,
|
||||||
|
index,
|
||||||
|
min_threshold_of_voters,
|
||||||
|
proposed_value,
|
||||||
|
creator,
|
||||||
|
memo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThresholdVotingState {
|
||||||
|
fn email_text(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Voting Start Time: {}\n\
|
||||||
|
Voting End Time: {}\n\
|
||||||
|
Proposed New Min. Threshold: {}\n\
|
||||||
|
Voting has Finished: {}\n\
|
||||||
|
Number of Votes Made: {}\n\
|
||||||
|
Number of Votes Required to Make Change: {}\n\
|
||||||
|
Ballot Creator: {:?}\n\
|
||||||
|
Memo: {}\n",
|
||||||
|
self.start_time,
|
||||||
|
self.end_time,
|
||||||
|
self.proposed_value,
|
||||||
|
self.is_finalized,
|
||||||
|
self.total_voters,
|
||||||
|
self.min_threshold_of_voters,
|
||||||
|
self.creator,
|
||||||
|
self.memo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// V1 Proxy Contract:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/aa45e19ca50f7cae308c1281d950245b0c65182a/contracts/VotingToChangeProxyAddress.sol#L19
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ProxyVotingState {
|
||||||
|
pub start_time: DateTime<Utc>,
|
||||||
|
pub end_time: DateTime<Utc>,
|
||||||
|
pub total_voters: U256,
|
||||||
|
pub progress: U256,
|
||||||
|
pub is_finalized: bool,
|
||||||
|
pub quorum_state: QuorumState,
|
||||||
|
pub index: U256,
|
||||||
|
pub min_threshold_of_voters: U256,
|
||||||
|
pub proposed_value: Address,
|
||||||
|
pub contract_type: U256,
|
||||||
|
pub creator: Address,
|
||||||
|
pub memo: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<ethabi::Token>> for ProxyVotingState {
|
||||||
|
fn from(tokens: Vec<ethabi::Token>) -> Self {
|
||||||
|
let start_time = {
|
||||||
|
let uint = tokens[0].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let end_time = {
|
||||||
|
let uint = tokens[1].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let total_voters = tokens[2].clone().to_uint().unwrap();
|
||||||
|
let progress = tokens[3].clone().to_int().unwrap();
|
||||||
|
let is_finalized = tokens[4].clone().to_bool().unwrap();
|
||||||
|
let quorum_state = tokens[5].clone().to_uint().unwrap().into();
|
||||||
|
let index = tokens[6].clone().to_uint().unwrap();
|
||||||
|
let min_threshold_of_voters = tokens[7].clone().to_uint().unwrap();
|
||||||
|
let proposed_value = tokens[8].clone().to_address().unwrap();
|
||||||
|
let contract_type = tokens[9].clone().to_uint().unwrap();
|
||||||
|
let creator = tokens[10].clone().to_address().unwrap();
|
||||||
|
let memo = tokens[11].clone().to_string().unwrap();
|
||||||
|
ProxyVotingState {
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
total_voters,
|
||||||
|
progress,
|
||||||
|
is_finalized,
|
||||||
|
quorum_state,
|
||||||
|
index,
|
||||||
|
min_threshold_of_voters,
|
||||||
|
proposed_value,
|
||||||
|
contract_type,
|
||||||
|
creator,
|
||||||
|
memo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProxyVotingState {
|
||||||
|
fn email_text(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Voting Start Time: {}\n\
|
||||||
|
Voting End Time: {}\n\
|
||||||
|
Proposed New Proxy Address: {:?}\n\
|
||||||
|
Voting has Finished: {}\n\
|
||||||
|
Number of Votes Made: {}\n\
|
||||||
|
Number of Votes Required for Change: {}\n\
|
||||||
|
Ballot Creator: {:?}\n\
|
||||||
|
Memo: {}\n",
|
||||||
|
self.start_time,
|
||||||
|
self.end_time,
|
||||||
|
self.proposed_value,
|
||||||
|
self.is_finalized,
|
||||||
|
self.total_voters,
|
||||||
|
self.min_threshold_of_voters,
|
||||||
|
self.creator,
|
||||||
|
self.memo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,397 @@
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use ethabi;
|
||||||
|
use web3::types::{Address, U256};
|
||||||
|
|
||||||
|
use response::common::{u256_to_datetime, BallotType, KeyType};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum BallotInfo {
|
||||||
|
Keys(KeysBallotInfo),
|
||||||
|
Threshold(ThresholdBallotInfo),
|
||||||
|
Proxy(ProxyBallotInfo),
|
||||||
|
Emission(EmissionBallotInfo),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<KeysBallotInfo> for BallotInfo {
|
||||||
|
fn from(keys_ballot_info: KeysBallotInfo) -> Self {
|
||||||
|
BallotInfo::Keys(keys_ballot_info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ThresholdBallotInfo> for BallotInfo {
|
||||||
|
fn from(threshold_ballot_info: ThresholdBallotInfo) -> Self {
|
||||||
|
BallotInfo::Threshold(threshold_ballot_info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ProxyBallotInfo> for BallotInfo {
|
||||||
|
fn from(proxy_ballot_info: ProxyBallotInfo) -> Self {
|
||||||
|
BallotInfo::Proxy(proxy_ballot_info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EmissionBallotInfo> for BallotInfo {
|
||||||
|
fn from(emission_ballot_info: EmissionBallotInfo) -> Self {
|
||||||
|
BallotInfo::Emission(emission_ballot_info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BallotInfo {
|
||||||
|
pub fn contract_name(&self) -> String {
|
||||||
|
match self {
|
||||||
|
BallotInfo::Keys(_) => "VotingToChangeKeys.sol".into(),
|
||||||
|
BallotInfo::Threshold(_) => "VotingToChangeMinThreshold.sol".into(),
|
||||||
|
BallotInfo::Proxy(_) => "VotingToChangeProxyAddress.sol".into(),
|
||||||
|
BallotInfo::Emission(_) => "VotingToManageEmissionFunds.sol".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn email_text(&self) -> String {
|
||||||
|
match self {
|
||||||
|
BallotInfo::Keys(info) => info.email_text(),
|
||||||
|
BallotInfo::Threshold(info) => info.email_text(),
|
||||||
|
BallotInfo::Proxy(info) => info.email_text(),
|
||||||
|
BallotInfo::Emission(info) => info.email_text(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returned by the V2 Keys contract's `.getBallotInfo()` function:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/ec307069302fdf6647e8b1bdc13093960913b266/contracts/VotingToChangeKeys.sol#L7
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct KeysBallotInfo {
|
||||||
|
pub start_time: DateTime<Utc>,
|
||||||
|
pub end_time: DateTime<Utc>,
|
||||||
|
pub affected_key: Address,
|
||||||
|
pub affected_key_type: KeyType,
|
||||||
|
pub new_voting_key: Address,
|
||||||
|
pub new_payout_key: Address,
|
||||||
|
pub mining_key: Address,
|
||||||
|
pub total_voters: U256,
|
||||||
|
pub progress: U256,
|
||||||
|
pub is_finalized: bool,
|
||||||
|
pub ballot_type: BallotType,
|
||||||
|
pub creator: Address,
|
||||||
|
pub memo: String,
|
||||||
|
pub can_be_finalized_now: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<ethabi::Token>> for KeysBallotInfo {
|
||||||
|
fn from(tokens: Vec<ethabi::Token>) -> Self {
|
||||||
|
let start_time = {
|
||||||
|
let uint = tokens[0].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let end_time = {
|
||||||
|
let uint = tokens[1].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let affected_key = tokens[2].clone().to_address().unwrap();
|
||||||
|
let affected_key_type = tokens[3].clone().to_uint().unwrap().into();
|
||||||
|
let new_voting_key = tokens[4].clone().to_address().unwrap();
|
||||||
|
let new_payout_key = tokens[5].clone().to_address().unwrap();
|
||||||
|
let mining_key = tokens[6].clone().to_address().unwrap();
|
||||||
|
let total_voters = tokens[7].clone().to_uint().unwrap();
|
||||||
|
let progress = tokens[8].clone().to_int().unwrap();
|
||||||
|
let is_finalized = tokens[9].clone().to_bool().unwrap();
|
||||||
|
let ballot_type = tokens[10].clone().to_uint().unwrap().into();
|
||||||
|
let creator = tokens[11].clone().to_address().unwrap();
|
||||||
|
let memo = tokens[12].clone().to_string().unwrap();
|
||||||
|
let can_be_finalized_now = tokens[13].clone().to_bool().unwrap();
|
||||||
|
KeysBallotInfo {
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
affected_key,
|
||||||
|
affected_key_type,
|
||||||
|
new_voting_key,
|
||||||
|
new_payout_key,
|
||||||
|
mining_key,
|
||||||
|
total_voters,
|
||||||
|
progress,
|
||||||
|
is_finalized,
|
||||||
|
ballot_type,
|
||||||
|
creator,
|
||||||
|
memo,
|
||||||
|
can_be_finalized_now,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeysBallotInfo {
|
||||||
|
fn email_text(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Voting Start Time: {}\n\
|
||||||
|
Voting End Time: {}\n\
|
||||||
|
Ballot Type: {:?}\n\
|
||||||
|
Affected Key: {:?}\n\
|
||||||
|
Affected Key Type: {:?}\n\
|
||||||
|
New Voting Key: {:?}\n\
|
||||||
|
New Payout Key: {:?}\n\
|
||||||
|
Voting has Finished: {}\n\
|
||||||
|
Number of Votes Made: {}\n\
|
||||||
|
Mining Key: {:?}\n\
|
||||||
|
Ballot Creator: {:?}\n\
|
||||||
|
Memo: {}\n",
|
||||||
|
self.start_time,
|
||||||
|
self.end_time,
|
||||||
|
self.ballot_type,
|
||||||
|
self.affected_key,
|
||||||
|
self.affected_key_type,
|
||||||
|
self.new_voting_key,
|
||||||
|
self.new_payout_key,
|
||||||
|
self.is_finalized,
|
||||||
|
self.total_voters,
|
||||||
|
self.mining_key,
|
||||||
|
self.creator,
|
||||||
|
self.memo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returned by the V2 Threshold Contract's `.getBallotInfo()` function:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/ec307069302fdf6647e8b1bdc13093960913b266/contracts/VotingToChangeMinThreshold.sol#L30
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ThresholdBallotInfo {
|
||||||
|
pub start_time: DateTime<Utc>,
|
||||||
|
pub end_time: DateTime<Utc>,
|
||||||
|
pub total_voters: U256,
|
||||||
|
pub progress: U256,
|
||||||
|
pub is_finalized: bool,
|
||||||
|
pub proposed_value: U256,
|
||||||
|
pub creator: Address,
|
||||||
|
pub memo: String,
|
||||||
|
pub can_be_finalized_now: bool,
|
||||||
|
// pub already_voted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<ethabi::Token>> for ThresholdBallotInfo {
|
||||||
|
fn from(tokens: Vec<ethabi::Token>) -> Self {
|
||||||
|
let start_time = {
|
||||||
|
let uint = tokens[0].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let end_time = {
|
||||||
|
let uint = tokens[1].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let total_voters = tokens[2].clone().to_uint().unwrap();
|
||||||
|
let progress = tokens[3].clone().to_uint().unwrap();
|
||||||
|
let is_finalized = tokens[4].clone().to_bool().unwrap();
|
||||||
|
let proposed_value = tokens[5].clone().to_uint().unwrap();
|
||||||
|
let creator = tokens[6].clone().to_address().unwrap();
|
||||||
|
let memo = tokens[7].clone().to_string().unwrap();
|
||||||
|
let can_be_finalized_now = tokens[8].clone().to_bool().unwrap();
|
||||||
|
// let already_voted = tokens[9].clone().to_bool().unwrap();
|
||||||
|
ThresholdBallotInfo {
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
total_voters,
|
||||||
|
progress,
|
||||||
|
is_finalized,
|
||||||
|
proposed_value,
|
||||||
|
creator,
|
||||||
|
memo,
|
||||||
|
can_be_finalized_now,
|
||||||
|
// already_voted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThresholdBallotInfo {
|
||||||
|
fn email_text(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Voting Start Time: {}\n\
|
||||||
|
Voting End Time: {}\n\
|
||||||
|
Proposed New Min. Threshold: {}\n\
|
||||||
|
Voting has Finished: {}\n\
|
||||||
|
Number of Votes Made: {}\n\
|
||||||
|
Ballot Creator: {:?}\n\
|
||||||
|
Memo: {}\n",
|
||||||
|
self.start_time,
|
||||||
|
self.end_time,
|
||||||
|
self.proposed_value,
|
||||||
|
self.is_finalized,
|
||||||
|
self.total_voters,
|
||||||
|
self.creator,
|
||||||
|
self.memo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returned by the V2 Proxy Contract's `.getBallotInfo()` function:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/ec307069302fdf6647e8b1bdc13093960913b266/contracts/VotingToChangeProxyAddress.sol#L30
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ProxyBallotInfo {
|
||||||
|
pub start_time: DateTime<Utc>,
|
||||||
|
pub end_time: DateTime<Utc>,
|
||||||
|
pub total_voters: U256,
|
||||||
|
pub progress: U256,
|
||||||
|
pub is_finalized: bool,
|
||||||
|
pub proposed_value: Address,
|
||||||
|
pub contract_type: U256,
|
||||||
|
pub creator: Address,
|
||||||
|
pub memo: String,
|
||||||
|
pub can_be_finalized_now: bool,
|
||||||
|
// pub already_voted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<ethabi::Token>> for ProxyBallotInfo {
|
||||||
|
fn from(tokens: Vec<ethabi::Token>) -> Self {
|
||||||
|
let start_time = {
|
||||||
|
let uint = tokens[0].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let end_time = {
|
||||||
|
let uint = tokens[1].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let total_voters = tokens[2].clone().to_uint().unwrap();
|
||||||
|
let progress = tokens[3].clone().to_uint().unwrap();
|
||||||
|
let is_finalized = tokens[4].clone().to_bool().unwrap();
|
||||||
|
let proposed_value = tokens[5].clone().to_address().unwrap();
|
||||||
|
let contract_type = tokens[6].clone().to_uint().unwrap();
|
||||||
|
let creator = tokens[7].clone().to_address().unwrap();
|
||||||
|
let memo = tokens[8].clone().to_string().unwrap();
|
||||||
|
let can_be_finalized_now = tokens[9].clone().to_bool().unwrap();
|
||||||
|
// let already_voted = tokens[10].clone().to_bool().unwrap();
|
||||||
|
ProxyBallotInfo {
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
total_voters,
|
||||||
|
progress,
|
||||||
|
is_finalized,
|
||||||
|
proposed_value,
|
||||||
|
contract_type,
|
||||||
|
creator,
|
||||||
|
memo,
|
||||||
|
can_be_finalized_now,
|
||||||
|
// already_voted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProxyBallotInfo {
|
||||||
|
fn email_text(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Voting Start Time: {}\n\
|
||||||
|
Voting End Time: {}\n\
|
||||||
|
Proposed New Proxy Address: {:?}\n\
|
||||||
|
Voting has Finished: {}\n\
|
||||||
|
Number of Votes Made: {}\n\
|
||||||
|
Ballot Creator: {:?}\n\
|
||||||
|
Memo: {}\n",
|
||||||
|
self.start_time,
|
||||||
|
self.end_time,
|
||||||
|
self.proposed_value,
|
||||||
|
self.is_finalized,
|
||||||
|
self.total_voters,
|
||||||
|
self.creator,
|
||||||
|
self.memo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returned by the V2 Emission Contract's `.getBallotInfo()` function:
|
||||||
|
/// https://github.com/poanetwork/poa-network-consensus-contracts/blob/ec307069302fdf6647e8b1bdc13093960913b266/contracts/VotingToManageEmissionFunds.sol#L126
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct EmissionBallotInfo {
|
||||||
|
pub creation_time: DateTime<Utc>,
|
||||||
|
pub start_time: DateTime<Utc>,
|
||||||
|
pub end_time: DateTime<Utc>,
|
||||||
|
pub is_canceled: bool,
|
||||||
|
pub is_finalized: bool,
|
||||||
|
pub creator: Address,
|
||||||
|
pub memo: String,
|
||||||
|
pub amount: U256,
|
||||||
|
pub burn_votes: U256,
|
||||||
|
pub freeze_votes: U256,
|
||||||
|
pub send_votes: U256,
|
||||||
|
pub receiver: Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<ethabi::Token>> for EmissionBallotInfo {
|
||||||
|
fn from(tokens: Vec<ethabi::Token>) -> Self {
|
||||||
|
let creation_time = {
|
||||||
|
let uint = tokens[0].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let start_time = {
|
||||||
|
let uint = tokens[1].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let end_time = {
|
||||||
|
let uint = tokens[2].clone().to_uint().unwrap();
|
||||||
|
u256_to_datetime(uint)
|
||||||
|
};
|
||||||
|
let is_canceled = tokens[3].clone().to_bool().unwrap();
|
||||||
|
let is_finalized = tokens[4].clone().to_bool().unwrap();
|
||||||
|
let creator = tokens[5].clone().to_address().unwrap();
|
||||||
|
let memo = tokens[6].clone().to_string().unwrap();
|
||||||
|
let amount = tokens[7].clone().to_uint().unwrap();
|
||||||
|
let burn_votes = tokens[8].clone().to_uint().unwrap();
|
||||||
|
let freeze_votes = tokens[9].clone().to_uint().unwrap();
|
||||||
|
let send_votes = tokens[10].clone().to_uint().unwrap();
|
||||||
|
let receiver = tokens[11].clone().to_address().unwrap();
|
||||||
|
EmissionBallotInfo {
|
||||||
|
creation_time,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
is_canceled,
|
||||||
|
is_finalized,
|
||||||
|
creator,
|
||||||
|
memo,
|
||||||
|
amount,
|
||||||
|
burn_votes,
|
||||||
|
freeze_votes,
|
||||||
|
send_votes,
|
||||||
|
receiver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EmissionBallotInfo {
|
||||||
|
fn email_text(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Creation Time: {}\n\
|
||||||
|
Voting Start Time: {}\n\
|
||||||
|
Voting End Time: {}\n\
|
||||||
|
Amount: {}\n\
|
||||||
|
Burn Votes: {}\n\
|
||||||
|
Freeze Votes: {}\n\
|
||||||
|
Send Votes: {}\n\
|
||||||
|
Receiver: {:?}\n\
|
||||||
|
Voting was Canceled: {}\n\
|
||||||
|
Voting has Finished: {}\n\
|
||||||
|
Ballot Creator: {:?}\n\
|
||||||
|
Memo: {}\n",
|
||||||
|
self.creation_time,
|
||||||
|
self.start_time,
|
||||||
|
self.end_time,
|
||||||
|
convert_wei_to_poa(self.amount),
|
||||||
|
self.burn_votes,
|
||||||
|
self.freeze_votes,
|
||||||
|
self.send_votes,
|
||||||
|
self.receiver,
|
||||||
|
self.is_canceled,
|
||||||
|
self.is_finalized,
|
||||||
|
self.creator,
|
||||||
|
self.memo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the `amount` field found in the `VotingToManageEmissionFunds` contract from Wei to
|
||||||
|
/// POA.
|
||||||
|
fn convert_wei_to_poa(amount_in_wei: U256) -> f64 {
|
||||||
|
let whole_poa = amount_in_wei / U256::exp10(18);
|
||||||
|
let remaining_poa = {
|
||||||
|
let whole_with_padding = whole_poa * U256::exp10(18);
|
||||||
|
(amount_in_wei - whole_with_padding).low_u64() as f64
|
||||||
|
};
|
||||||
|
let fraction_of_a_poa = {
|
||||||
|
let eighteen_zeros: String = (0..18).map(|_| '0').collect();
|
||||||
|
let max_fract: f64 = format!("1{}", eighteen_zeros).parse().unwrap();
|
||||||
|
remaining_poa / max_fract
|
||||||
|
};
|
||||||
|
whole_poa.low_u64() as f64 + fraction_of_a_poa
|
||||||
|
}
|
513
src/rpc.rs
513
src/rpc.rs
|
@ -1,513 +0,0 @@
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::i64;
|
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::u64;
|
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use ethabi::{Event, Token};
|
|
||||||
use ethereum_types::{Address, H256, U256};
|
|
||||||
use hex;
|
|
||||||
use jsonrpc_core as rpc;
|
|
||||||
use reqwest;
|
|
||||||
use serde_json as json;
|
|
||||||
use web3::types::{BlockNumber, Bytes, CallRequest, Filter, FilterBuilder, Log};
|
|
||||||
|
|
||||||
use config::{Config, ContractType, PoaContract, StartBlock};
|
|
||||||
use utils::{hex_string_to_u64, u256_to_datetime};
|
|
||||||
|
|
||||||
const JSONRPC_VERSION: rpc::Version = rpc::Version::V2;
|
|
||||||
const REQUEST_ID: rpc::Id = rpc::Id::Num(1);
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum BallotType {
|
|
||||||
InvalidKey,
|
|
||||||
AddKey,
|
|
||||||
RemoveKey,
|
|
||||||
SwapKey,
|
|
||||||
ChangeMinThreshold,
|
|
||||||
ChangeProxyAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used when converting from an element in a Log's "topics" vector.
|
|
||||||
impl From<H256> for BallotType {
|
|
||||||
fn from(topic: H256) -> Self {
|
|
||||||
match topic.low_u64() {
|
|
||||||
0 => BallotType::InvalidKey,
|
|
||||||
1 => BallotType::AddKey,
|
|
||||||
2 => BallotType::RemoveKey,
|
|
||||||
3 => BallotType::SwapKey,
|
|
||||||
4 => BallotType::ChangeMinThreshold,
|
|
||||||
5 => BallotType::ChangeProxyAddress,
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used when converting from an output-token returned from a contract's
|
|
||||||
// `votingState` function.
|
|
||||||
impl From<U256> for BallotType {
|
|
||||||
fn from(output: U256) -> Self {
|
|
||||||
match output.as_u64() {
|
|
||||||
0 => BallotType::InvalidKey,
|
|
||||||
1 => BallotType::AddKey,
|
|
||||||
2 => BallotType::RemoveKey,
|
|
||||||
3 => BallotType::SwapKey,
|
|
||||||
4 => BallotType::ChangeMinThreshold,
|
|
||||||
5 => BallotType::ChangeProxyAddress,
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum KeyType {
|
|
||||||
Invalid,
|
|
||||||
MiningKey,
|
|
||||||
VotingKey,
|
|
||||||
PayoutKey
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<U256> for KeyType {
|
|
||||||
fn from(key_type: U256) -> Self {
|
|
||||||
match key_type.as_u64() {
|
|
||||||
0 => KeyType::Invalid,
|
|
||||||
1 => KeyType::MiningKey,
|
|
||||||
2 => KeyType::VotingKey,
|
|
||||||
3 => KeyType::PayoutKey,
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum QuorumState {
|
|
||||||
Invalid,
|
|
||||||
InProgress,
|
|
||||||
Accepted,
|
|
||||||
Rejected
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<U256> for QuorumState {
|
|
||||||
fn from(quorum_state: U256) -> Self {
|
|
||||||
match quorum_state.as_u64() {
|
|
||||||
0 => QuorumState::Invalid,
|
|
||||||
1 => QuorumState::InProgress,
|
|
||||||
2 => QuorumState::Accepted,
|
|
||||||
3 => QuorumState::Rejected,
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BallotCreatedLog {
|
|
||||||
pub block_number: u64,
|
|
||||||
pub ballot_id: u64,
|
|
||||||
pub ballot_type: BallotType
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Log> for BallotCreatedLog {
|
|
||||||
fn from(log: Log) -> Self {
|
|
||||||
BallotCreatedLog {
|
|
||||||
block_number: log.block_number.unwrap().as_u64(),
|
|
||||||
ballot_id: log.topics[1].low_u64(),
|
|
||||||
ballot_type: log.topics[2].into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum VotingData {
|
|
||||||
Keys(KeysVotingData),
|
|
||||||
Threshold(ThresholdVotingData),
|
|
||||||
Proxy(ProxyVotingData)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VotingData {
|
|
||||||
pub fn keys(tokens: Vec<Token>) -> Self {
|
|
||||||
let voting_data: KeysVotingData = tokens.into();
|
|
||||||
VotingData::Keys(voting_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn threshold(tokens: Vec<Token>) -> Self {
|
|
||||||
let voting_data: ThresholdVotingData = tokens.into();
|
|
||||||
VotingData::Threshold(voting_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn proxy(tokens: Vec<Token>) -> Self {
|
|
||||||
let voting_data: ProxyVotingData = tokens.into();
|
|
||||||
VotingData::Proxy(voting_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_time(&self) -> DateTime<Utc> {
|
|
||||||
match *self {
|
|
||||||
VotingData::Keys(ref inner) => inner.start_time,
|
|
||||||
VotingData::Threshold(ref inner) => inner.start_time,
|
|
||||||
VotingData::Proxy(ref inner) => inner.start_time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn end_time(&self) -> DateTime<Utc> {
|
|
||||||
match *self {
|
|
||||||
VotingData::Keys(ref inner) => inner.end_time,
|
|
||||||
VotingData::Threshold(ref inner) => inner.end_time,
|
|
||||||
VotingData::Proxy(ref inner) => inner.end_time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn memo(&self) -> String {
|
|
||||||
match *self {
|
|
||||||
VotingData::Keys(ref inner) => inner.memo.clone(),
|
|
||||||
VotingData::Threshold(ref inner) => inner.memo.clone(),
|
|
||||||
VotingData::Proxy(ref inner) => inner.memo.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct KeysVotingData {
|
|
||||||
pub start_time: DateTime<Utc>,
|
|
||||||
pub end_time: DateTime<Utc>,
|
|
||||||
pub affected_key: Address,
|
|
||||||
pub affected_key_type: KeyType,
|
|
||||||
pub mining_key: Address,
|
|
||||||
pub total_voters: u64,
|
|
||||||
pub progress: i64,
|
|
||||||
pub is_finalized: bool,
|
|
||||||
pub quorum_state: QuorumState,
|
|
||||||
pub ballot_type: BallotType,
|
|
||||||
pub index: u64,
|
|
||||||
pub min_threshold_of_voters: u64,
|
|
||||||
pub creator: Address,
|
|
||||||
pub memo: String
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<Token>> for KeysVotingData {
|
|
||||||
fn from(tokens: Vec<Token>) -> Self {
|
|
||||||
let start_time = tokens[0].clone().to_uint()
|
|
||||||
.map(|uint| u256_to_datetime(uint))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let end_time = tokens[1].clone().to_uint()
|
|
||||||
.map(|uint| u256_to_datetime(uint))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let affected_key = tokens[2].clone().to_address().unwrap();
|
|
||||||
let affected_key_type = tokens[3].clone().to_uint().unwrap().into();
|
|
||||||
let mining_key = tokens[4].clone().to_address().unwrap();
|
|
||||||
let total_voters = tokens[5].clone().to_uint().unwrap().as_u64();
|
|
||||||
let progress = match tokens[6].clone().to_int().unwrap().low_u64() {
|
|
||||||
lsb if lsb <= total_voters => lsb as i64,
|
|
||||||
lsb => i64::try_from(u64::MAX - lsb + 1).unwrap()
|
|
||||||
};
|
|
||||||
let is_finalized = tokens[7].clone().to_bool().unwrap();
|
|
||||||
let quorum_state = tokens[8].clone().to_uint().unwrap().into();
|
|
||||||
let ballot_type = tokens[9].clone().to_uint().unwrap().into();
|
|
||||||
let index = tokens[10].clone().to_uint().unwrap().as_u64();
|
|
||||||
let min_threshold_of_voters = tokens[11].clone().to_uint().unwrap().as_u64();
|
|
||||||
let creator = tokens[12].clone().to_address().unwrap();
|
|
||||||
let memo = tokens[13].clone().to_string().unwrap();
|
|
||||||
|
|
||||||
KeysVotingData {
|
|
||||||
start_time, end_time,
|
|
||||||
affected_key, affected_key_type,
|
|
||||||
mining_key, total_voters,
|
|
||||||
progress, is_finalized,
|
|
||||||
quorum_state, ballot_type,
|
|
||||||
index, min_threshold_of_voters,
|
|
||||||
creator, memo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ThresholdVotingData {
|
|
||||||
pub start_time: DateTime<Utc>,
|
|
||||||
pub end_time: DateTime<Utc>,
|
|
||||||
pub total_voters: u64,
|
|
||||||
pub progress: i64,
|
|
||||||
pub is_finalized: bool,
|
|
||||||
pub quorum_state: QuorumState,
|
|
||||||
pub index: u64,
|
|
||||||
pub proposed_value: u64,
|
|
||||||
pub min_threshold_of_voters: u64,
|
|
||||||
pub creator: Address,
|
|
||||||
pub memo: String
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<Token>> for ThresholdVotingData {
|
|
||||||
fn from(tokens: Vec<Token>) -> Self {
|
|
||||||
let start_time = tokens[0].clone().to_uint()
|
|
||||||
.map(|uint| u256_to_datetime(uint))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let end_time = tokens[1].clone().to_uint()
|
|
||||||
.map(|uint| u256_to_datetime(uint))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let total_voters = tokens[2].clone().to_uint().unwrap().as_u64();
|
|
||||||
let progress = match tokens[3].clone().to_int().unwrap().low_u64() {
|
|
||||||
lsb if lsb <= total_voters => lsb as i64,
|
|
||||||
lsb => i64::try_from(u64::MAX - lsb + 1).unwrap()
|
|
||||||
};
|
|
||||||
let is_finalized = tokens[4].clone().to_bool().unwrap();
|
|
||||||
let quorum_state = tokens[5].clone().to_uint().unwrap().into();
|
|
||||||
let index = tokens[6].clone().to_uint().unwrap().as_u64();
|
|
||||||
let proposed_value = tokens[7].clone().to_uint().unwrap().as_u64();
|
|
||||||
let min_threshold_of_voters = tokens[8].clone().to_uint().unwrap().as_u64();
|
|
||||||
let creator = tokens[9].clone().to_address().unwrap();
|
|
||||||
let memo = tokens[10].clone().to_string().unwrap();
|
|
||||||
|
|
||||||
ThresholdVotingData {
|
|
||||||
start_time, end_time,
|
|
||||||
total_voters, progress,
|
|
||||||
is_finalized, quorum_state,
|
|
||||||
index, proposed_value,
|
|
||||||
min_threshold_of_voters, creator,
|
|
||||||
memo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ProxyVotingData {
|
|
||||||
pub start_time: DateTime<Utc>,
|
|
||||||
pub end_time: DateTime<Utc>,
|
|
||||||
pub total_voters: u64,
|
|
||||||
pub progress: i64,
|
|
||||||
pub is_finalized: bool,
|
|
||||||
pub quorum_state: QuorumState,
|
|
||||||
pub index: u64,
|
|
||||||
pub min_threshold_of_voters: u64,
|
|
||||||
pub proposed_value: Address,
|
|
||||||
pub contract_type: u64,
|
|
||||||
pub creator: Address,
|
|
||||||
pub memo: String
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<Token>> for ProxyVotingData {
|
|
||||||
fn from(tokens: Vec<Token>) -> Self {
|
|
||||||
let start_time = tokens[0].clone().to_uint()
|
|
||||||
.map(|uint| u256_to_datetime(uint))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let end_time = tokens[1].clone().to_uint()
|
|
||||||
.map(|uint| u256_to_datetime(uint))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let total_voters = tokens[2].clone().to_uint().unwrap().as_u64();
|
|
||||||
let progress = match tokens[3].clone().to_int().unwrap().low_u64() {
|
|
||||||
lsb if lsb <= total_voters => lsb as i64,
|
|
||||||
lsb => i64::try_from(u64::MAX - lsb + 1).unwrap()
|
|
||||||
};
|
|
||||||
let is_finalized = tokens[4].clone().to_bool().unwrap();
|
|
||||||
let quorum_state = tokens[5].clone().to_uint().unwrap().into();
|
|
||||||
let index = tokens[6].clone().to_uint().unwrap().as_u64();
|
|
||||||
let min_threshold_of_voters = tokens[7].clone().to_uint().unwrap().as_u64();
|
|
||||||
let proposed_value = tokens[8].clone().to_address().unwrap();
|
|
||||||
let contract_type = tokens[9].clone().to_uint().unwrap().as_u64();
|
|
||||||
let creator = tokens[10].clone().to_address().unwrap();
|
|
||||||
let memo = tokens[11].clone().to_string().unwrap();
|
|
||||||
|
|
||||||
ProxyVotingData {
|
|
||||||
start_time, end_time,
|
|
||||||
total_voters, progress,
|
|
||||||
is_finalized, quorum_state,
|
|
||||||
index, min_threshold_of_voters,
|
|
||||||
proposed_value, contract_type,
|
|
||||||
creator, memo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BlockchainIter<'a> {
|
|
||||||
client: &'a RpcClient,
|
|
||||||
start_block: u64,
|
|
||||||
stop_block: u64,
|
|
||||||
on_first_iteration: bool,
|
|
||||||
avg_block_time: Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> BlockchainIter<'a> {
|
|
||||||
pub fn new(client: &'a RpcClient, config: &Config) -> Self {
|
|
||||||
let latest = client.latest_block_number().unwrap();
|
|
||||||
|
|
||||||
let start_block = match config.start_block {
|
|
||||||
StartBlock::Earliest => 0,
|
|
||||||
StartBlock::Latest => latest,
|
|
||||||
StartBlock::Number(block_number) => block_number,
|
|
||||||
StartBlock::Tail(tail) => latest - tail
|
|
||||||
};
|
|
||||||
|
|
||||||
if start_block > latest {
|
|
||||||
panic!(
|
|
||||||
"Provided start-block ({}) exceeds latest mined block number ({})",
|
|
||||||
start_block,
|
|
||||||
latest
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockchainIter {
|
|
||||||
client,
|
|
||||||
start_block,
|
|
||||||
stop_block: latest,
|
|
||||||
on_first_iteration: true,
|
|
||||||
avg_block_time: config.avg_block_time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for BlockchainIter<'a> {
|
|
||||||
type Item = (BlockNumber, BlockNumber);
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if self.on_first_iteration {
|
|
||||||
self.on_first_iteration = false;
|
|
||||||
} else {
|
|
||||||
self.start_block = self.stop_block + 1;
|
|
||||||
while self.start_block >= self.stop_block {
|
|
||||||
thread::sleep(self.avg_block_time);
|
|
||||||
self.stop_block = self.client.latest_block_number().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some((
|
|
||||||
BlockNumber::Number(self.start_block),
|
|
||||||
BlockNumber::Number(self.stop_block)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Method {
|
|
||||||
CallContractFunction,
|
|
||||||
GetLogs,
|
|
||||||
LastMinedBlockNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Method> for String {
|
|
||||||
fn from(method: Method) -> Self {
|
|
||||||
let s = match method {
|
|
||||||
Method::CallContractFunction => "eth_call",
|
|
||||||
Method::GetLogs => "eth_getLogs",
|
|
||||||
Method::LastMinedBlockNumber => "eth_blockNumber"
|
|
||||||
};
|
|
||||||
s.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventFilter;
|
|
||||||
|
|
||||||
impl EventFilter {
|
|
||||||
pub fn new(ev: Event, addr: Address, from: BlockNumber, to: BlockNumber) -> Filter {
|
|
||||||
let topic = vec![ev.signature()];
|
|
||||||
FilterBuilder::default()
|
|
||||||
.topics(Some(topic), None, None, None)
|
|
||||||
.address(vec![addr])
|
|
||||||
.from_block(from)
|
|
||||||
.to_block(to)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RpcClient {
|
|
||||||
endpoint: String,
|
|
||||||
client: reqwest::Client
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RpcClient {
|
|
||||||
pub fn new(endpoint: &str) -> Self {
|
|
||||||
let endpoint = endpoint.into();
|
|
||||||
let client = reqwest::Client::new();
|
|
||||||
RpcClient { endpoint, client }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_request(&self, method: Method, params: Vec<json::Value>) -> reqwest::Request {
|
|
||||||
let jsonrpc = Some(JSONRPC_VERSION);
|
|
||||||
let id = REQUEST_ID.clone();
|
|
||||||
let method = method.into();
|
|
||||||
let params = Some(rpc::Params::Array(params));
|
|
||||||
let method_call = rpc::MethodCall { jsonrpc, method, params, id };
|
|
||||||
let body = rpc::Call::MethodCall(method_call);
|
|
||||||
self.client.post(&self.endpoint).json(&body).build().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send(&self, req: reqwest::Request) -> reqwest::Result<json::Value> {
|
|
||||||
let resp: rpc::Response = self.client.execute(req)?.json().unwrap();
|
|
||||||
if let rpc::Response::Single(resp_status) = resp {
|
|
||||||
if let rpc::Output::Success(resp) = resp_status {
|
|
||||||
return Ok(resp.result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn latest_block_number(&self) -> reqwest::Result<u64> {
|
|
||||||
let req = self.build_request(Method::LastMinedBlockNumber, vec![]);
|
|
||||||
if let json::Value::String(hex) = self.send(req)? {
|
|
||||||
return Ok(hex_string_to_u64(&hex).unwrap());
|
|
||||||
}
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_logs(&self, filter: Filter) -> reqwest::Result<Vec<Log>> {
|
|
||||||
let params = vec![json::to_value(filter).unwrap()];
|
|
||||||
let req = self.build_request(Method::GetLogs, params);
|
|
||||||
let result = self.send(req)?;
|
|
||||||
Ok(json::from_value(result).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_ballot_created_logs(
|
|
||||||
&self,
|
|
||||||
contract: &PoaContract,
|
|
||||||
start: BlockNumber,
|
|
||||||
stop: BlockNumber
|
|
||||||
) -> reqwest::Result<Vec<BallotCreatedLog>>
|
|
||||||
{
|
|
||||||
let event = contract.event("BallotCreated");
|
|
||||||
let filter = EventFilter::new(event, contract.addr, start, stop);
|
|
||||||
let logs = self.get_logs(filter)?;
|
|
||||||
let ballot_created_logs: Vec<BallotCreatedLog> = logs.into_iter()
|
|
||||||
.map(|log| log.into())
|
|
||||||
.collect();
|
|
||||||
Ok(ballot_created_logs)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_voting_state(&self, contract: &PoaContract, ballot_id: u64) -> reqwest::Result<VotingData> {
|
|
||||||
let function = contract.function("votingState");
|
|
||||||
let tokens = vec![Token::Uint(U256::from(ballot_id))];
|
|
||||||
let encoded_input: Bytes = function.encode_input(&tokens).unwrap().into();
|
|
||||||
|
|
||||||
let call = CallRequest {
|
|
||||||
to: contract.addr,
|
|
||||||
data: Some(encoded_input),
|
|
||||||
from: None,
|
|
||||||
gas: None,
|
|
||||||
gas_price: None,
|
|
||||||
value: None
|
|
||||||
};
|
|
||||||
|
|
||||||
let params = vec![
|
|
||||||
json::to_value(call).unwrap(),
|
|
||||||
json::to_value(BlockNumber::Latest).unwrap()
|
|
||||||
];
|
|
||||||
|
|
||||||
let req = self.build_request(Method::CallContractFunction, params);
|
|
||||||
let result = self.send(req)?;
|
|
||||||
|
|
||||||
if let json::Value::String(hex) = result {
|
|
||||||
let bytes = hex::decode(hex.trim_left_matches("0x")).unwrap();
|
|
||||||
let outputs = function.decode_output(&bytes).unwrap();
|
|
||||||
let voting_data = match contract.kind {
|
|
||||||
ContractType::Keys => VotingData::keys(outputs.into()),
|
|
||||||
ContractType::Threshold => VotingData::threshold(outputs.into()),
|
|
||||||
ContractType::Proxy => VotingData::proxy(outputs.into())
|
|
||||||
};
|
|
||||||
return Ok(voting_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
}
|
|
18
src/utils.rs
18
src/utils.rs
|
@ -1,18 +0,0 @@
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::i64;
|
|
||||||
use std::num::ParseIntError;
|
|
||||||
use std::u64;
|
|
||||||
|
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
|
||||||
use ethereum_types::U256;
|
|
||||||
|
|
||||||
pub fn hex_string_to_u64(hex: &str) -> Result<u64, ParseIntError> {
|
|
||||||
let hex = hex.trim_left_matches("0x");
|
|
||||||
u64::from_str_radix(hex, 16)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn u256_to_datetime(uint: U256) -> DateTime<Utc> {
|
|
||||||
let n_secs = i64::try_from(uint.as_u64()).unwrap();
|
|
||||||
let timestamp = NaiveDateTime::from_timestamp(n_secs, 0);
|
|
||||||
DateTime::from_utc(timestamp, Utc)
|
|
||||||
}
|
|
Loading…
Reference in New Issue