Applied a handful of cherry-picked rustfmt and clippy changes.
This commit is contained in:
parent
0150df2f8e
commit
a29b43c26c
5
build.rs
5
build.rs
|
@ -16,8 +16,9 @@ fn create_env_file_if_dne() {
|
||||||
if !sample_env_path.exists() {
|
if !sample_env_path.exists() {
|
||||||
panic!("neither the `.env` nor the `sample.env` files exist, one of these files must exist to build the `poagov`");
|
panic!("neither the `.env` nor the `sample.env` files exist, one of these files must exist to build the `poagov`");
|
||||||
}
|
}
|
||||||
fs::copy(sample_env_path, env_path)
|
if let Err(e) = fs::copy(sample_env_path, env_path) {
|
||||||
.unwrap_or_else(|e| panic!("failed to create the `.env` file from `sample.env`: {:?}", e));
|
panic!("failed to create the `.env` file from `sample.env`: {:?}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,21 @@ use crate::client::RpcClient;
|
||||||
use crate::config::{Config, StartBlock};
|
use crate::config::{Config, StartBlock};
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
fn sleep_or_ctrlc(n_secs: u64, running: Arc<AtomicBool>) -> Option<()> {
|
/// Represents the reason why the sleep cycle in `fn sleep_or_ctrlc()` ended.
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum SleepExit {
|
||||||
|
CtrlC,
|
||||||
|
FinishedSleeping,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sleeps for `n_secs` number of seconds or returns early if the user shuts down `poagov` using
|
||||||
|
/// ctrl-c.
|
||||||
|
///
|
||||||
|
/// Returns `SleepExit::CtrlC` if the user hit ctrl-c while this function was was sleeping or
|
||||||
|
/// returns `SleepExit::FinishedSleeping` if the function was able to sleep for the entire `n_secs`
|
||||||
|
/// duration.
|
||||||
|
fn sleep_or_ctrlc(n_secs: u64, running: Arc<AtomicBool>) -> SleepExit {
|
||||||
|
// This `AtomicBool` will become `true` when we have slept for `n_secs`.
|
||||||
let done_sleeping = Arc::new(AtomicBool::new(false));
|
let done_sleeping = Arc::new(AtomicBool::new(false));
|
||||||
{
|
{
|
||||||
let done_sleeping = done_sleeping.clone();
|
let done_sleeping = done_sleeping.clone();
|
||||||
|
@ -18,16 +32,16 @@ fn sleep_or_ctrlc(n_secs: u64, running: Arc<AtomicBool>) -> Option<()> {
|
||||||
done_sleeping.store(true, Ordering::SeqCst);
|
done_sleeping.store(true, Ordering::SeqCst);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
loop {
|
while !done_sleeping.load(Ordering::SeqCst) {
|
||||||
if !running.load(Ordering::SeqCst) {
|
if !running.load(Ordering::SeqCst) {
|
||||||
return None;
|
return SleepExit::CtrlC;
|
||||||
}
|
|
||||||
if done_sleeping.load(Ordering::SeqCst) {
|
|
||||||
return Some(());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SleepExit::FinishedSleeping
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A type that we use to iterate over the blocks in a blockchain in discrete block-windows (each
|
||||||
|
/// "block-window" is an inclusively bounded range of block numbers).
|
||||||
pub struct BlockchainIter<'a> {
|
pub struct BlockchainIter<'a> {
|
||||||
client: &'a RpcClient,
|
client: &'a RpcClient,
|
||||||
start_block: u64,
|
start_block: u64,
|
||||||
|
@ -38,6 +52,16 @@ pub struct BlockchainIter<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> BlockchainIter<'a> {
|
impl<'a> BlockchainIter<'a> {
|
||||||
|
/// Creates a new `BlockchainIter`.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Return an error if the HTTP-RPC server cannot be reached or if the response from the RPC
|
||||||
|
/// server cannot be parsed.
|
||||||
|
///
|
||||||
|
/// Returns an `Error::StartBlockExceedsLastBlockMined` if the `start_block` that the user
|
||||||
|
/// passed in via a CLI argument is in the future (i.e. is greater than the block number of the
|
||||||
|
/// most recently mined block).
|
||||||
pub fn new(client: &'a RpcClient, config: &Config, running: Arc<AtomicBool>) -> Result<Self> {
|
pub fn new(client: &'a RpcClient, config: &Config, running: Arc<AtomicBool>) -> Result<Self> {
|
||||||
let last_mined_block = client.get_last_mined_block_number()?;
|
let last_mined_block = client.get_last_mined_block_number()?;
|
||||||
let start_block = match config.start_block {
|
let start_block = match config.start_block {
|
||||||
|
@ -47,17 +71,19 @@ impl<'a> BlockchainIter<'a> {
|
||||||
StartBlock::Tail(tail) => last_mined_block - tail,
|
StartBlock::Tail(tail) => last_mined_block - tail,
|
||||||
};
|
};
|
||||||
if start_block > last_mined_block {
|
if start_block > last_mined_block {
|
||||||
return Err(Error::StartBlockExceedsLastBlockMined { start_block, last_mined_block });
|
return Err(Error::StartBlockExceedsLastBlockMined {
|
||||||
|
start_block,
|
||||||
|
last_mined_block,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
let bc_iter = BlockchainIter {
|
Ok(BlockchainIter {
|
||||||
client,
|
client,
|
||||||
start_block,
|
start_block,
|
||||||
stop_block: last_mined_block,
|
stop_block: last_mined_block,
|
||||||
on_first_iteration: true,
|
on_first_iteration: true,
|
||||||
block_time: config.block_time,
|
block_time: config.block_time,
|
||||||
running,
|
running,
|
||||||
};
|
})
|
||||||
Ok(bc_iter)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,9 +96,11 @@ impl<'a> Iterator for BlockchainIter<'a> {
|
||||||
} else {
|
} else {
|
||||||
self.start_block = self.stop_block + 1;
|
self.start_block = self.stop_block + 1;
|
||||||
while self.start_block >= self.stop_block {
|
while self.start_block >= self.stop_block {
|
||||||
sleep_or_ctrlc(self.block_time, self.running.clone())?;
|
if sleep_or_ctrlc(self.block_time, self.running.clone()) == SleepExit::CtrlC {
|
||||||
match self.client.get_last_mined_block_number() {
|
return None;
|
||||||
Ok(last_mined) => self.stop_block = last_mined,
|
}
|
||||||
|
self.stop_block = match self.client.get_last_mined_block_number() {
|
||||||
|
Ok(last_mined) => last_mined,
|
||||||
Err(e) => return Some(Err(e)),
|
Err(e) => return Some(Err(e)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Some of `Cli`'s methods are not currently being used.
|
// Some of `Cli`'s methods are not currently being used.
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use clap::{ArgMatches, App};
|
use clap::{App, ArgMatches};
|
||||||
|
|
||||||
pub fn parse_cli() -> Cli {
|
pub fn parse_cli() -> Cli {
|
||||||
let cli_args = App::new("poagov")
|
let cli_args = App::new("poagov")
|
||||||
|
@ -58,6 +58,10 @@ impl Cli {
|
||||||
self.0.is_present("emission")
|
self.0.is_present("emission")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn no_contracts_specified(&self) -> bool {
|
||||||
|
!self.keys() && !self.threshold() && !self.proxy() && !self.emission()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn v1(&self) -> bool {
|
pub fn v1(&self) -> bool {
|
||||||
self.0.is_present("v1")
|
self.0.is_present("v1")
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,9 @@ use web3::types::{Address, BlockNumber, Filter, FilterBuilder, U256};
|
||||||
|
|
||||||
use crate::config::{ContractType, PoaContract};
|
use crate::config::{ContractType, PoaContract};
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::response::{v1, v2};
|
|
||||||
use crate::response::common::BallotCreatedLog;
|
use crate::response::common::BallotCreatedLog;
|
||||||
|
use crate::response::v1;
|
||||||
|
use crate::response::v2;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum RpcMethod {
|
pub enum RpcMethod {
|
||||||
|
@ -43,8 +44,7 @@ impl RpcClient {
|
||||||
&self,
|
&self,
|
||||||
method: RpcMethod,
|
method: RpcMethod,
|
||||||
params: Vec<json::Value>,
|
params: Vec<json::Value>,
|
||||||
) -> Result<reqwest::Request>
|
) -> Result<reqwest::Request> {
|
||||||
{
|
|
||||||
let method_call = json_rpc::types::request::MethodCall {
|
let method_call = json_rpc::types::request::MethodCall {
|
||||||
jsonrpc: Some(json_rpc::types::version::Version::V2),
|
jsonrpc: Some(json_rpc::types::version::Version::V2),
|
||||||
method: method.into(),
|
method: method.into(),
|
||||||
|
@ -60,7 +60,8 @@ impl RpcClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send(&self, req: reqwest::Request) -> Result<json::Value> {
|
fn send(&self, req: reqwest::Request) -> Result<json::Value> {
|
||||||
let resp: json_rpc::types::response::Response = self.client
|
let resp: json_rpc::types::response::Response = self
|
||||||
|
.client
|
||||||
.execute(req)
|
.execute(req)
|
||||||
.map_err(|e| Error::RequestFailed(e))?
|
.map_err(|e| Error::RequestFailed(e))?
|
||||||
.json()
|
.json()
|
||||||
|
@ -68,7 +69,9 @@ impl RpcClient {
|
||||||
if let json_rpc::types::response::Response::Single(resp_status) = resp {
|
if let json_rpc::types::response::Response::Single(resp_status) = resp {
|
||||||
match resp_status {
|
match resp_status {
|
||||||
json_rpc::types::response::Output::Success(resp) => return Ok(resp.result),
|
json_rpc::types::response::Output::Success(resp) => return Ok(resp.result),
|
||||||
json_rpc::types::response::Output::Failure(e) => return Err(Error::JsonRpcResponseFailure(e)),
|
json_rpc::types::response::Output::Failure(e) => {
|
||||||
|
return Err(Error::JsonRpcResponseFailure(e))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
unreachable!("Recieved multiple responses for single request");
|
unreachable!("Recieved multiple responses for single request");
|
||||||
|
@ -97,8 +100,7 @@ impl RpcClient {
|
||||||
contract: &PoaContract,
|
contract: &PoaContract,
|
||||||
start: BlockNumber,
|
start: BlockNumber,
|
||||||
stop: BlockNumber,
|
stop: BlockNumber,
|
||||||
) -> Result<Vec<BallotCreatedLog>>
|
) -> Result<Vec<BallotCreatedLog>> {
|
||||||
{
|
|
||||||
let event = contract.event("BallotCreated");
|
let event = contract.event("BallotCreated");
|
||||||
let event_sig = event.signature();
|
let event_sig = event.signature();
|
||||||
let filter = FilterBuilder::default()
|
let filter = FilterBuilder::default()
|
||||||
|
@ -110,9 +112,15 @@ impl RpcClient {
|
||||||
self.get_logs(filter)?
|
self.get_logs(filter)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|web3_log| {
|
.map(|web3_log| {
|
||||||
let web3::types::Log {topics, data, block_number, .. } = web3_log;
|
let web3::types::Log {
|
||||||
|
topics,
|
||||||
|
data,
|
||||||
|
block_number,
|
||||||
|
..
|
||||||
|
} = web3_log;
|
||||||
let raw_log = ethabi::RawLog::from((topics, data.0));
|
let raw_log = ethabi::RawLog::from((topics, data.0));
|
||||||
let ethabi_log = event.parse_log(raw_log)
|
let ethabi_log = event
|
||||||
|
.parse_log(raw_log)
|
||||||
.map_err(|e| Error::FailedToParseRawLogToLog(e))?;
|
.map_err(|e| Error::FailedToParseRawLogToLog(e))?;
|
||||||
BallotCreatedLog::from_ethabi_log(ethabi_log, block_number.unwrap())
|
BallotCreatedLog::from_ethabi_log(ethabi_log, block_number.unwrap())
|
||||||
})
|
})
|
||||||
|
@ -120,7 +128,11 @@ impl RpcClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// V1
|
/// V1
|
||||||
pub fn get_voting_state(&self, contract: &PoaContract, ballot_id: U256) -> Result<v1::VotingState> {
|
pub fn get_voting_state(
|
||||||
|
&self,
|
||||||
|
contract: &PoaContract,
|
||||||
|
ballot_id: U256,
|
||||||
|
) -> Result<v1::VotingState> {
|
||||||
let function = contract.function("votingState");
|
let function = contract.function("votingState");
|
||||||
let tokens = vec![ethabi::Token::Uint(ballot_id)];
|
let tokens = vec![ethabi::Token::Uint(ballot_id)];
|
||||||
let encoded_input = function.encode_input(&tokens).unwrap();
|
let encoded_input = function.encode_input(&tokens).unwrap();
|
||||||
|
@ -156,8 +168,12 @@ impl RpcClient {
|
||||||
// TODO: When V2 contracts have been published and ballots have begun, test that calling
|
// 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
|
// `.getBallotInfo()` with `Address::zero()` for the `votingKey` works (we don't care if
|
||||||
// `votingKey` has voted yet).
|
// `votingKey` has voted yet).
|
||||||
pub fn get_ballot_info(&self, contract: &PoaContract, ballot_id: U256) -> Result<v2::BallotInfo> {
|
pub fn get_ballot_info(
|
||||||
// pub fn get_ballot_info(&self, contract: &PoaContract, ballot_id: U256, voting_key: Option<Address>) -> Result<v2::BallotInfo> {
|
&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 function = contract.function("getBallotInfo");
|
||||||
/*
|
/*
|
||||||
let mut tokens = vec![ethabi::Token::Uint(ballot_id)];
|
let mut tokens = vec![ethabi::Token::Uint(ballot_id)];
|
||||||
|
@ -208,15 +224,16 @@ mod tests {
|
||||||
|
|
||||||
use web3::types::BlockNumber;
|
use web3::types::BlockNumber;
|
||||||
|
|
||||||
use crate::tests::setup;
|
|
||||||
use crate::config::{ContractType, ContractVersion, Network, PoaContract};
|
|
||||||
use super::RpcClient;
|
use super::RpcClient;
|
||||||
|
use crate::config::{ContractType, ContractVersion, Network, PoaContract};
|
||||||
|
use crate::tests::setup;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_last_mined_block() {
|
fn test_get_last_mined_block() {
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
let sokol_url = env::var("SOKOL_RPC_ENDPOINT").expect("Missing env-var: `SOKOL_RPC_ENDPOINT`");
|
let sokol_url =
|
||||||
|
env::var("SOKOL_RPC_ENDPOINT").expect("Missing env-var: `SOKOL_RPC_ENDPOINT`");
|
||||||
let client = RpcClient::new(sokol_url);
|
let client = RpcClient::new(sokol_url);
|
||||||
let res = client.get_last_mined_block_number();
|
let res = client.get_last_mined_block_number();
|
||||||
println!("\nsokol last mined block number => {:?}", res);
|
println!("\nsokol last mined block number => {:?}", res);
|
||||||
|
@ -232,18 +249,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_ballot_created_logs() {
|
fn test_get_ballot_created_logs() {
|
||||||
setup();
|
setup();
|
||||||
let contract = PoaContract::read(
|
let contract = PoaContract::read(ContractType::Keys, Network::Sokol, ContractVersion::V1)
|
||||||
ContractType::Keys,
|
.unwrap_or_else(|e| panic!("Failed to load contract: {:?}", e));
|
||||||
Network::Sokol,
|
let endpoint =
|
||||||
ContractVersion::V1,
|
env::var("SOKOL_RPC_ENDPOINT").expect("Missing env-var: `SOKOL_RPC_ENDPOINT`");
|
||||||
).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 client = RpcClient::new(endpoint);
|
||||||
let res = client.get_ballot_created_logs(
|
let res =
|
||||||
&contract,
|
client.get_ballot_created_logs(&contract, BlockNumber::Earliest, BlockNumber::Latest);
|
||||||
BlockNumber::Earliest,
|
|
||||||
BlockNumber::Latest,
|
|
||||||
);
|
|
||||||
println!("{:#?}", res);
|
println!("{:#?}", res);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
}
|
}
|
||||||
|
@ -273,12 +285,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_voting_state() {
|
fn test_get_voting_state() {
|
||||||
setup();
|
setup();
|
||||||
let contract = PoaContract::read(
|
let contract = PoaContract::read(ContractType::Threshold, Network::Sokol, ContractVersion::V1)
|
||||||
ContractType::Threshold,
|
.unwrap_or_else(|e| panic!("Failed to load contract: {:?}", e));
|
||||||
Network::Sokol,
|
let sokol_url = env::var("SOKOL_RPC_ENDPOINT")
|
||||||
ContractVersion::V1
|
.expect("Missing env-var: `SOKOL_RPC_ENDPOINT`");
|
||||||
).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 client = RpcClient::new(sokol_url);
|
||||||
let res = client.get_voting_state(&contract, 2.into());
|
let res = client.get_voting_state(&contract, 2.into());
|
||||||
println!("{:#?}", res);
|
println!("{:#?}", res);
|
||||||
|
|
189
src/config.rs
189
src/config.rs
|
@ -50,6 +50,10 @@ impl From<BallotType> for ContractType {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContractType {
|
impl ContractType {
|
||||||
|
fn is_emission(&self) -> bool {
|
||||||
|
*self == ContractType::Emission
|
||||||
|
}
|
||||||
|
|
||||||
fn uppercase(&self) -> &str {
|
fn uppercase(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
ContractType::Keys => "KEYS",
|
ContractType::Keys => "KEYS",
|
||||||
|
@ -76,6 +80,10 @@ pub enum ContractVersion {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContractVersion {
|
impl ContractVersion {
|
||||||
|
fn is_v1(&self) -> bool {
|
||||||
|
*self == ContractVersion::V1
|
||||||
|
}
|
||||||
|
|
||||||
fn lowercase(&self) -> &str {
|
fn lowercase(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
ContractVersion::V1 => "v1",
|
ContractVersion::V1 => "v1",
|
||||||
|
@ -103,46 +111,44 @@ impl Debug for PoaContract {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PoaContract {
|
impl PoaContract {
|
||||||
|
fn new(
|
||||||
|
kind: ContractType,
|
||||||
|
version: ContractVersion,
|
||||||
|
addr: Address,
|
||||||
|
abi: ethabi::Contract,
|
||||||
|
) -> Self {
|
||||||
|
PoaContract { kind, version, addr, abi }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read(
|
pub fn read(
|
||||||
contract_type: ContractType,
|
contract_type: ContractType,
|
||||||
network: Network,
|
network: Network,
|
||||||
version: ContractVersion,
|
version: ContractVersion,
|
||||||
) -> Result<Self>
|
) -> Result<Self> {
|
||||||
{
|
// Exit quickly if we know that the contract does not exist.
|
||||||
if contract_type == ContractType::Emission && version == ContractVersion::V1 {
|
if contract_type.is_emission() && version.is_v1() {
|
||||||
return Err(Error::EmissionFundsV1ContractDoesNotExist);
|
return Err(Error::EmissionFundsV1ContractDoesNotExist);
|
||||||
}
|
}
|
||||||
|
|
||||||
let addr_env_var = format!(
|
let env_var = format!(
|
||||||
"{}_CONTRACT_ADDRESS_{}_{:?}",
|
"{}_CONTRACT_ADDRESS_{}_{:?}",
|
||||||
contract_type.uppercase(),
|
contract_type.uppercase(),
|
||||||
network.uppercase(),
|
network.uppercase(),
|
||||||
version,
|
version
|
||||||
);
|
);
|
||||||
|
let contract_addr_str = env::var(&env_var).map_err(|_| Error::MissingEnvVar(env_var))?;
|
||||||
let addr = if let Ok(s) = env::var(&addr_env_var) {
|
let contract_addr = Address::from_str(contract_addr_str.trim_left_matches("0x"))
|
||||||
match Address::from_str(s.trim_left_matches("0x")) {
|
.map_err(|_| Error::InvalidContractAddr(contract_addr_str.to_string()))?;
|
||||||
Ok(addr) => addr,
|
|
||||||
Err(_) => return Err(Error::InvalidContractAddr(s.into())),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::MissingEnvVar(addr_env_var));
|
|
||||||
};
|
|
||||||
|
|
||||||
let abi_path = format!(
|
let abi_path = format!(
|
||||||
"abis/{}/{}",
|
"abis/{}/{}",
|
||||||
version.lowercase(),
|
version.lowercase(),
|
||||||
contract_type.abi_file_name(),
|
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 abi_file = File::open(&abi_path)
|
Ok(PoaContract::new(contract_type, version, contract_addr, abi))
|
||||||
.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 {
|
||||||
|
@ -184,85 +190,78 @@ pub struct Config {
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn new(cli: &Cli) -> Result<Self> {
|
pub fn new(cli: &Cli) -> Result<Self> {
|
||||||
let network = if cli.core() == cli.sokol() {
|
if cli.core() == cli.sokol() {
|
||||||
return Err(Error::MustSpecifyOneCliArgument("`--core` or `--sokol`".into()));
|
return Err(Error::MustSpecifyOneCliArgument("--core, --sokol".to_string()));
|
||||||
} else if cli.core() {
|
}
|
||||||
|
if cli.v1() == cli.v2() {
|
||||||
|
return Err(Error::MustSpecifyOneCliArgument("--v1, --v2".to_string()));
|
||||||
|
}
|
||||||
|
if cli.no_contracts_specified() {
|
||||||
|
return Err(Error::MustSpecifyAtLeastOneCliArgument(
|
||||||
|
"--keys, --threshold, --proxy, --emission".to_string().to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if cli.multiple_start_blocks_specified() {
|
||||||
|
return Err(Error::MustSpecifyOneCliArgument(
|
||||||
|
"--earliest, --latest, --start-block, --tail".to_string()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let network = if cli.core() {
|
||||||
Network::Core
|
Network::Core
|
||||||
} else {
|
} else {
|
||||||
Network::Sokol
|
Network::Sokol
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let version = if cli.v1() {
|
||||||
|
ContractVersion::V1
|
||||||
|
} else {
|
||||||
|
ContractVersion::V2
|
||||||
|
};
|
||||||
|
|
||||||
let endpoint_env_var = format!("{}_RPC_ENDPOINT", network.uppercase());
|
let endpoint_env_var = format!("{}_RPC_ENDPOINT", network.uppercase());
|
||||||
let endpoint = env::var(&endpoint_env_var)
|
let endpoint = env::var(&endpoint_env_var)
|
||||||
.map_err(|_| Error::MissingEnvVar(endpoint_env_var))?;
|
.map_err(|_| Error::MissingEnvVar(endpoint_env_var))?;
|
||||||
|
|
||||||
let version = if cli.v1() == cli.v2(){
|
|
||||||
return Err(Error::MustSpecifyOneCliArgument("`--v1` or `--v2`".into()));
|
|
||||||
} else if cli.v1(){
|
|
||||||
ContractVersion::V1
|
|
||||||
} else {
|
|
||||||
ContractVersion::V2
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut contracts = vec![];
|
let mut contracts = vec![];
|
||||||
if cli.keys() {
|
if cli.keys() {
|
||||||
let keys = PoaContract::read(
|
let keys_contract = PoaContract::read(ContractType::Keys, network, version)?;
|
||||||
ContractType::Keys,
|
contracts.push(keys_contract);
|
||||||
network,
|
|
||||||
version
|
|
||||||
)?;
|
|
||||||
contracts.push(keys);
|
|
||||||
}
|
}
|
||||||
if cli.threshold() {
|
if cli.threshold() {
|
||||||
let threshold = PoaContract::read(
|
let threshold_contract = PoaContract::read(ContractType::Threshold, network, version)?;
|
||||||
ContractType::Threshold,
|
contracts.push(threshold_contract);
|
||||||
network,
|
|
||||||
version
|
|
||||||
)?;
|
|
||||||
contracts.push(threshold);
|
|
||||||
}
|
}
|
||||||
if cli.proxy() {
|
if cli.proxy() {
|
||||||
let proxy = PoaContract::read(
|
let proxy_contract = PoaContract::read(ContractType::Proxy, network, version)?;
|
||||||
ContractType::Proxy,
|
contracts.push(proxy_contract);
|
||||||
network,
|
|
||||||
version
|
|
||||||
)?;
|
|
||||||
contracts.push(proxy);
|
|
||||||
}
|
}
|
||||||
if cli.emission() {
|
if cli.emission() {
|
||||||
let emission_funds = PoaContract::read(
|
let emission_funds = PoaContract::read(ContractType::Emission, network, version)?;
|
||||||
ContractType::Emission,
|
|
||||||
network,
|
|
||||||
version,
|
|
||||||
)?;
|
|
||||||
contracts.push(emission_funds);
|
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() {
|
let start_block = if cli.earliest() {
|
||||||
return Err(Error::MustSpecifyOneCliArgument(
|
|
||||||
"`--earliest` or `--latest` or `--start-block` or `--tail`".into()
|
|
||||||
));
|
|
||||||
} else if cli.earliest() {
|
|
||||||
StartBlock::Earliest
|
StartBlock::Earliest
|
||||||
} else if cli.latest() {
|
} else if cli.latest() {
|
||||||
StartBlock::Latest
|
StartBlock::Latest
|
||||||
} else if let Some(s) = cli.start_block() {
|
} else if let Some(start_block_str) = cli.start_block() {
|
||||||
let block_number = s.parse().map_err(|_| Error::InvalidStartBlock(s.into()))?;
|
match start_block_str.parse::<u64>() {
|
||||||
StartBlock::Number(block_number)
|
Ok(block_number) => StartBlock::Number(block_number),
|
||||||
} else if let Some(s) = cli.tail() {
|
_ => return Err(Error::InvalidStartBlock(start_block_str.to_string())),
|
||||||
let tail = s.parse().map_err(|_| Error::InvalidTail(s.into()))?;
|
}
|
||||||
StartBlock::Tail(tail)
|
} else if let Some(tail_str) = cli.tail() {
|
||||||
|
match tail_str.parse::<u64>() {
|
||||||
|
Ok(tail) => StartBlock::Tail(tail),
|
||||||
|
_ => return Err(Error::InvalidTail(tail_str.to_string())),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: use `DEFAULT_START_BLOCK`?
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
|
|
||||||
let block_time = if let Some(s) = cli.block_time() {
|
let block_time = if let Some(n_secs_str) = cli.block_time() {
|
||||||
s.parse().map_err(|_| Error::InvalidBlockTime(s.into()))?
|
n_secs_str.parse().map_err(|_| Error::InvalidBlockTime(n_secs_str.to_string()))?
|
||||||
} else {
|
} else {
|
||||||
DEFAULT_BLOCK_TIME_SECS
|
DEFAULT_BLOCK_TIME_SECS
|
||||||
};
|
};
|
||||||
|
@ -272,13 +271,7 @@ impl Config {
|
||||||
let email_recipients: Vec<String> = env::var("EMAIL_RECIPIENTS")
|
let email_recipients: Vec<String> = env::var("EMAIL_RECIPIENTS")
|
||||||
.map_err(|_| Error::MissingEnvVar("EMAIL_RECIPIENTS".into()))?
|
.map_err(|_| Error::MissingEnvVar("EMAIL_RECIPIENTS".into()))?
|
||||||
.split(',')
|
.split(',')
|
||||||
.filter_map(|s| {
|
.filter_map(|s| if s.is_empty() { None } else { Some(s.into()) })
|
||||||
if s.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(s.into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let smtp_host_domain = if email_notifications {
|
let smtp_host_domain = if email_notifications {
|
||||||
|
@ -325,7 +318,9 @@ impl Config {
|
||||||
};
|
};
|
||||||
|
|
||||||
let notification_limit = if let Some(s) = cli.notification_limit() {
|
let notification_limit = if let Some(s) = cli.notification_limit() {
|
||||||
let limit = s.parse().map_err(|_| Error::InvalidNotificationLimit(s.into()))?;
|
let limit = s
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| Error::InvalidNotificationLimit(s.into()))?;
|
||||||
Some(limit)
|
Some(limit)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -334,7 +329,7 @@ impl Config {
|
||||||
let log_emails = cli.log_emails();
|
let log_emails = cli.log_emails();
|
||||||
let log_to_file = cli.log_to_file();
|
let log_to_file = cli.log_to_file();
|
||||||
|
|
||||||
let config = Config {
|
Ok(Config {
|
||||||
network,
|
network,
|
||||||
endpoint,
|
endpoint,
|
||||||
version,
|
version,
|
||||||
|
@ -351,8 +346,7 @@ impl Config {
|
||||||
notification_limit,
|
notification_limit,
|
||||||
log_emails,
|
log_emails,
|
||||||
log_to_file,
|
log_to_file,
|
||||||
};
|
})
|
||||||
Ok(config)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,14 +354,14 @@ impl Config {
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use super::super::tests::setup;
|
use super::{ContractType, ContractVersion, Network, PoaContract};
|
||||||
use super::{ContractType, ContractVersion, PoaContract, Network};
|
use crate::tests::setup;
|
||||||
|
|
||||||
const CONTRACT_TYPES: [ContractType; 4] = [
|
const CONTRACT_TYPES: [ContractType; 4] = [
|
||||||
ContractType::Keys,
|
ContractType::Keys,
|
||||||
ContractType::Threshold,
|
ContractType::Threshold,
|
||||||
ContractType::Proxy,
|
ContractType::Proxy,
|
||||||
ContractType::Emission,
|
ContractType::Emission,
|
||||||
];
|
];
|
||||||
const NETWORKS: [Network; 2] = [Network::Sokol, Network::Core];
|
const NETWORKS: [Network; 2] = [Network::Sokol, Network::Core];
|
||||||
const VERSIONS: [ContractVersion; 2] = [ContractVersion::V1, ContractVersion::V2];
|
const VERSIONS: [ContractVersion; 2] = [ContractVersion::V1, ContractVersion::V2];
|
||||||
|
@ -380,7 +374,7 @@ mod tests {
|
||||||
assert!(env::var(&env_var).is_ok());
|
assert!(env::var(&env_var).is_ok());
|
||||||
for contract_type in CONTRACT_TYPES.iter() {
|
for contract_type in CONTRACT_TYPES.iter() {
|
||||||
for version in VERSIONS.iter() {
|
for version in VERSIONS.iter() {
|
||||||
if *contract_type == ContractType::Emission && *version == ContractVersion::V1 {
|
if contract_type.is_emission() && version.is_v1() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let env_var = format!(
|
let env_var = format!(
|
||||||
|
@ -400,13 +394,12 @@ mod tests {
|
||||||
setup();
|
setup();
|
||||||
for contract_type in CONTRACT_TYPES.iter() {
|
for contract_type in CONTRACT_TYPES.iter() {
|
||||||
for version in VERSIONS.iter() {
|
for version in VERSIONS.iter() {
|
||||||
|
if contract_type.is_emission() && version.is_v1() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for network in NETWORKS.iter() {
|
for network in NETWORKS.iter() {
|
||||||
let res = PoaContract::read(*contract_type, *network, *version);
|
let res = PoaContract::read(*contract_type, *network, *version);
|
||||||
if *contract_type == ContractType::Emission && *version == ContractVersion::V1 {
|
assert!(res.is_ok());
|
||||||
assert!(res.is_err());
|
|
||||||
} else {
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,8 @@ fn read_logs_dir() -> Vec<LogFile> {
|
||||||
let path = res.ok()?.path();
|
let path = res.ok()?.path();
|
||||||
let file_name = path.file_name().unwrap().to_str().unwrap();
|
let file_name = path.file_name().unwrap().to_str().unwrap();
|
||||||
LogFile::from_file_name(file_name).ok()
|
LogFile::from_file_name(file_name).ok()
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
log_files.sort_unstable();
|
log_files.sort_unstable();
|
||||||
log_files
|
log_files
|
||||||
}
|
}
|
||||||
|
@ -106,14 +107,12 @@ impl LogFile {
|
||||||
|
|
||||||
fn create_file(&self) -> File {
|
fn create_file(&self) -> File {
|
||||||
let path = self.path();
|
let path = self.path();
|
||||||
File::create(&path)
|
File::create(&path).unwrap_or_else(|_| panic!("failed to create log file: {}", path))
|
||||||
.unwrap_or_else(|_| panic!("failed to create log file: {}", path))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_file(&self) {
|
fn remove_file(&self) {
|
||||||
let path = self.path();
|
let path = self.path();
|
||||||
remove_file(&path)
|
remove_file(&path).unwrap_or_else(|_| panic!("failed to delete log file: {}", path))
|
||||||
.unwrap_or_else(|_| panic!("failed to delete log file: {}", path))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,17 +184,27 @@ impl Logger {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_ctrlc_pressed(&mut self) {
|
pub fn log_ctrlc_pressed(&mut self) {
|
||||||
warn!(&self.logger, "recieved ctrl-c signal, gracefully shutting down...");
|
warn!(
|
||||||
|
&self.logger,
|
||||||
|
"received ctrl-c signal, gracefully shutting down..."
|
||||||
|
);
|
||||||
self.increment_log_count();
|
self.increment_log_count();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_no_email_recipients_configured(&mut self) {
|
pub fn log_no_email_recipients_configured(&mut self) {
|
||||||
warn!(&self.logger, "email notifications are enabled, but there are no email recipients");
|
warn!(
|
||||||
|
&self.logger,
|
||||||
|
"email notifications are enabled, but there are no email recipients"
|
||||||
|
);
|
||||||
self.increment_log_count();
|
self.increment_log_count();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_notification_email_body(&mut self, notif: &Notification) {
|
pub fn log_notification_email_body(&mut self, notif: &Notification) {
|
||||||
info!(&self.logger, "governance notification\n{}", notif.email_text());
|
info!(
|
||||||
|
&self.logger,
|
||||||
|
"governance notification\n{}",
|
||||||
|
notif.email_text()
|
||||||
|
);
|
||||||
self.increment_log_count();
|
self.increment_log_count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -33,7 +33,7 @@ fn load_env_file() {
|
||||||
match dotenv::dotenv() {
|
match dotenv::dotenv() {
|
||||||
Ok(_) => LOADED_ENV_FILE.store(true, Ordering::Relaxed),
|
Ok(_) => LOADED_ENV_FILE.store(true, Ordering::Relaxed),
|
||||||
Err(dotenv::Error::Io(_)) => panic!("could not find .env file"),
|
Err(dotenv::Error::Io(_)) => panic!("could not find .env file"),
|
||||||
_ => panic!("could not parse .env file"),
|
_ => panic!("could not parse .env file"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,13 +89,16 @@ fn main() -> Result<()> {
|
||||||
start_block,
|
start_block,
|
||||||
stop_block,
|
stop_block,
|
||||||
)?;
|
)?;
|
||||||
for log in ballot_created_logs.into_iter() {
|
for log in ballot_created_logs {
|
||||||
let notification = if contract.version == ContractVersion::V1 {
|
let notification = match contract.version {
|
||||||
let voting_state = client.get_voting_state(contract, log.ballot_id)?;
|
ContractVersion::V1 => {
|
||||||
Notification::from_voting_state(&config, log, voting_state)
|
let voting_state = client.get_voting_state(contract, log.ballot_id)?;
|
||||||
} else {
|
Notification::from_voting_state(&config, log, voting_state)
|
||||||
let ballot_info = client.get_ballot_info(contract, log.ballot_id)?;
|
}
|
||||||
Notification::from_ballot_info(&config, log, ballot_info)
|
ContractVersion::V2 => {
|
||||||
|
let ballot_info = client.get_ballot_info(contract, log.ballot_id)?;
|
||||||
|
Notification::from_ballot_info(&config, log, ballot_info)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
notifications.push(notification);
|
notifications.push(notification);
|
||||||
}
|
}
|
||||||
|
@ -116,7 +119,10 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.lock().unwrap().log_finished_block_window(start_block, stop_block);
|
logger
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.log_finished_block_window(start_block, stop_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -33,29 +33,35 @@ impl<'a> Notification<'a> {
|
||||||
config: &'a Config,
|
config: &'a Config,
|
||||||
log: BallotCreatedLog,
|
log: BallotCreatedLog,
|
||||||
voting_state: VotingState,
|
voting_state: VotingState,
|
||||||
) -> Self
|
) -> Self {
|
||||||
{
|
Notification::VotingState {
|
||||||
Notification::VotingState { config, log, voting_state }
|
config,
|
||||||
|
log,
|
||||||
|
voting_state,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_ballot_info(
|
pub fn from_ballot_info(
|
||||||
config: &'a Config,
|
config: &'a Config,
|
||||||
log: BallotCreatedLog,
|
log: BallotCreatedLog,
|
||||||
ballot_info: BallotInfo,
|
ballot_info: BallotInfo,
|
||||||
) -> Self
|
) -> Self {
|
||||||
{
|
Notification::BallotInfo {
|
||||||
Notification::BallotInfo { config, log, ballot_info }
|
config,
|
||||||
|
log,
|
||||||
|
ballot_info,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn email_text(&self) -> String {
|
pub fn email_text(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Network: {:?}\n\
|
"Network: {:?}\n\
|
||||||
RPC Endpoint: {}\n\
|
RPC Endpoint: {}\n\
|
||||||
Block Number: {}\n\
|
Block Number: {}\n\
|
||||||
Contract: {}\n\
|
Contract: {}\n\
|
||||||
Version: {:?}\n\
|
Version: {:?}\n\
|
||||||
Ballot ID: {}\n\
|
Ballot ID: {}\n\
|
||||||
{}\n",
|
{}\n",
|
||||||
self.config().network,
|
self.config().network,
|
||||||
self.config().endpoint,
|
self.config().endpoint,
|
||||||
self.log().block_number,
|
self.log().block_number,
|
||||||
|
@ -127,12 +133,20 @@ impl<'a> Notifier<'a> {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
Ok(Notifier { config, emailer, logger, notification_count: 0 })
|
Ok(Notifier {
|
||||||
|
config,
|
||||||
|
emailer,
|
||||||
|
logger,
|
||||||
|
notification_count: 0,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn notify(&mut self, notif: &Notification) {
|
pub fn notify(&mut self, notif: &Notification) {
|
||||||
if self.config.log_emails {
|
if self.config.log_emails {
|
||||||
self.logger.lock().unwrap().log_notification_email_body(notif);
|
self.logger
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.log_notification_email_body(notif);
|
||||||
} else {
|
} else {
|
||||||
self.logger.lock().unwrap().log_notification(notif);
|
self.logger.lock().unwrap().log_notification(notif);
|
||||||
}
|
}
|
||||||
|
@ -143,10 +157,13 @@ impl<'a> Notifier<'a> {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.logger.lock().unwrap().log_failed_to_build_email(e);
|
self.logger.lock().unwrap().log_failed_to_build_email(e);
|
||||||
continue;
|
continue;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
if let Err(e) = self.send_email(email) {
|
if let Err(e) = self.send_email(email) {
|
||||||
self.logger.lock().unwrap().log_failed_to_send_email(recipient, e);
|
self.logger
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.log_failed_to_send_email(recipient, e);
|
||||||
} else {
|
} else {
|
||||||
self.logger.lock().unwrap().log_email_sent(recipient);
|
self.logger.lock().unwrap().log_email_sent(recipient);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
#![allow(deprecated)]
|
#![allow(deprecated)]
|
||||||
|
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use ethabi;
|
|
||||||
use web3::types::{Address, H256, U256};
|
use web3::types::{Address, H256, U256};
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
@ -129,12 +128,25 @@ impl BallotCreatedLog {
|
||||||
};
|
};
|
||||||
let ballot_type = match ballot_type {
|
let ballot_type = match ballot_type {
|
||||||
Some(ballot_type) => ballot_type,
|
Some(ballot_type) => ballot_type,
|
||||||
None => return Err(Error::FailedToParseBallotCreatedLog("missing `ballot_type`".into())),
|
None => {
|
||||||
|
return Err(Error::FailedToParseBallotCreatedLog(
|
||||||
|
"missing `ballot_type`".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let creator = match creator {
|
let creator = match creator {
|
||||||
Some(creator) => creator,
|
Some(creator) => creator,
|
||||||
None => return Err(Error::FailedToParseBallotCreatedLog("missing `creator`".into())),
|
None => {
|
||||||
|
return Err(Error::FailedToParseBallotCreatedLog(
|
||||||
|
"missing `creator`".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok(BallotCreatedLog { ballot_id, ballot_type, creator, block_number })
|
Ok(BallotCreatedLog {
|
||||||
|
ballot_id,
|
||||||
|
ballot_type,
|
||||||
|
creator,
|
||||||
|
block_number,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ pub struct KeysVotingState {
|
||||||
pub index: U256,
|
pub index: U256,
|
||||||
pub min_threshold_of_voters: U256,
|
pub min_threshold_of_voters: U256,
|
||||||
pub creator: Address,
|
pub creator: Address,
|
||||||
pub memo: String
|
pub memo: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Vec<ethabi::Token>> for KeysVotingState {
|
impl From<Vec<ethabi::Token>> for KeysVotingState {
|
||||||
|
|
Loading…
Reference in New Issue