2018-04-21 09:59:11 -07:00
|
|
|
use std::env;
|
2018-09-25 06:56:44 -07:00
|
|
|
use std::fmt::{self, Debug, Formatter};
|
2018-04-21 09:59:11 -07:00
|
|
|
use std::fs::File;
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
use ethabi::{Address, Contract, Event, Function};
|
2018-04-21 09:59:11 -07:00
|
|
|
|
|
|
|
use cli::Cli;
|
2018-09-25 06:56:44 -07:00
|
|
|
use error::{Error, Result};
|
|
|
|
use response::common::BallotType;
|
2018-04-21 09:59:11 -07:00
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
const DEFAULT_BLOCK_TIME_SECS: u64 = 30;
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
|
|
pub enum Network {
|
|
|
|
Core,
|
|
|
|
Sokol,
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
impl Network {
|
|
|
|
fn uppercase(&self) -> &str {
|
|
|
|
match self {
|
|
|
|
Network::Core => "CORE",
|
|
|
|
Network::Sokol => "SOKOL",
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
/// Note that the `Emission` contract is V2 only.
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
|
|
pub enum ContractType {
|
|
|
|
Keys,
|
|
|
|
Threshold,
|
|
|
|
Proxy,
|
|
|
|
Emission,
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
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,
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
impl ContractType {
|
|
|
|
fn uppercase(&self) -> &str {
|
|
|
|
match self {
|
|
|
|
ContractType::Keys => "KEYS",
|
|
|
|
ContractType::Threshold => "THRESHOLD",
|
|
|
|
ContractType::Proxy => "PROXY",
|
|
|
|
ContractType::Emission => "EMISSION_FUNDS",
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-11 06:54:31 -08:00
|
|
|
fn abi_file_name(&self) -> &str {
|
2018-09-25 06:56:44 -07:00
|
|
|
match self {
|
|
|
|
ContractType::Keys => "VotingToChangeKeys.abi.json",
|
|
|
|
ContractType::Threshold => "VotingToChangeMinThreshold.abi.json",
|
|
|
|
ContractType::Proxy => "VotingToChangeProxyAddress.abi.json",
|
|
|
|
ContractType::Emission => "VotingToManageEmissionFunds.abi.json",
|
|
|
|
}
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
|
|
pub enum ContractVersion {
|
|
|
|
V1,
|
|
|
|
V2,
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
impl ContractVersion {
|
|
|
|
fn lowercase(&self) -> &str {
|
|
|
|
match self {
|
|
|
|
ContractVersion::V1 => "v1",
|
|
|
|
ContractVersion::V2 => "v2",
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
#[derive(Clone)]
|
2018-04-21 09:59:11 -07:00
|
|
|
pub struct PoaContract {
|
|
|
|
pub kind: ContractType,
|
2018-09-25 06:56:44 -07:00
|
|
|
pub version: ContractVersion,
|
2018-04-21 09:59:11 -07:00
|
|
|
pub addr: Address,
|
2018-09-25 06:56:44 -07:00
|
|
|
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()
|
|
|
|
}
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PoaContract {
|
2018-09-25 06:56:44 -07:00
|
|
|
pub fn read(
|
|
|
|
contract_type: ContractType,
|
|
|
|
network: Network,
|
|
|
|
version: ContractVersion,
|
|
|
|
) -> Result<Self>
|
|
|
|
{
|
|
|
|
if contract_type == ContractType::Emission && version == ContractVersion::V1 {
|
|
|
|
return Err(Error::EmissionFundsV1ContractDoesNotExist);
|
|
|
|
}
|
2018-12-11 06:54:31 -08:00
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
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(),
|
|
|
|
);
|
2018-12-11 06:54:31 -08:00
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
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)
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn event(&self, event: &str) -> Event {
|
|
|
|
self.abi.event(event).unwrap().clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn function(&self, function: &str) -> Function {
|
|
|
|
self.abi.function(function).unwrap().clone()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
|
|
pub enum StartBlock {
|
|
|
|
Earliest,
|
|
|
|
Latest,
|
|
|
|
Number(u64),
|
|
|
|
Tail(u64),
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
#[derive(Clone, Debug)]
|
2018-04-21 09:59:11 -07:00
|
|
|
pub struct Config {
|
|
|
|
pub network: Network,
|
|
|
|
pub endpoint: String,
|
2018-09-25 06:56:44 -07:00
|
|
|
pub version: ContractVersion,
|
2018-04-21 09:59:11 -07:00
|
|
|
pub contracts: Vec<PoaContract>,
|
|
|
|
pub start_block: StartBlock,
|
2018-09-25 06:56:44 -07:00
|
|
|
pub block_time: u64,
|
|
|
|
pub email_notifications: bool,
|
|
|
|
pub email_recipients: Vec<String>,
|
|
|
|
pub smtp_host_domain: Option<String>,
|
|
|
|
pub smtp_port: Option<u16>,
|
|
|
|
pub smtp_username: Option<String>,
|
|
|
|
pub smtp_password: Option<String>,
|
|
|
|
pub outgoing_email_addr: Option<String>,
|
2018-10-10 07:46:20 -07:00
|
|
|
pub notification_limit: Option<usize>,
|
|
|
|
pub log_emails: bool,
|
|
|
|
pub log_to_file: bool,
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Config {
|
2018-09-25 06:56:44 -07:00
|
|
|
pub fn new(cli: &Cli) -> Result<Self> {
|
|
|
|
let network = if cli.core() == cli.sokol() {
|
|
|
|
return Err(Error::MustSpecifyOneCliArgument("`--core` or `--sokol`".into()));
|
|
|
|
} else if cli.core() {
|
2018-04-21 09:59:11 -07:00
|
|
|
Network::Core
|
|
|
|
} else {
|
2018-09-25 06:56:44 -07:00
|
|
|
Network::Sokol
|
2018-04-21 09:59:11 -07:00
|
|
|
};
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
let endpoint_env_var = format!("{}_RPC_ENDPOINT", network.uppercase());
|
|
|
|
let endpoint = env::var(&endpoint_env_var)
|
|
|
|
.map_err(|_| Error::MissingEnvVar(endpoint_env_var))?;
|
2018-12-11 06:54:31 -08:00
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
let version = if cli.v1() == cli.v2(){
|
|
|
|
return Err(Error::MustSpecifyOneCliArgument("`--v1` or `--v2`".into()));
|
|
|
|
} else if cli.v1(){
|
|
|
|
ContractVersion::V1
|
2018-04-21 09:59:11 -07:00
|
|
|
} else {
|
2018-09-25 06:56:44 -07:00
|
|
|
ContractVersion::V2
|
2018-04-21 09:59:11 -07:00
|
|
|
};
|
2018-12-11 06:54:31 -08:00
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
let mut contracts = vec![];
|
|
|
|
if cli.keys() {
|
|
|
|
let keys = PoaContract::read(
|
|
|
|
ContractType::Keys,
|
|
|
|
network,
|
|
|
|
version
|
|
|
|
)?;
|
|
|
|
contracts.push(keys);
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
2018-09-25 06:56:44 -07:00
|
|
|
if cli.threshold() {
|
|
|
|
let threshold = PoaContract::read(
|
|
|
|
ContractType::Threshold,
|
|
|
|
network,
|
|
|
|
version
|
|
|
|
)?;
|
|
|
|
contracts.push(threshold);
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
2018-09-25 06:56:44 -07:00
|
|
|
if cli.proxy() {
|
|
|
|
let proxy = PoaContract::read(
|
|
|
|
ContractType::Proxy,
|
|
|
|
network,
|
|
|
|
version
|
|
|
|
)?;
|
|
|
|
contracts.push(proxy);
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
2018-09-25 06:56:44 -07:00
|
|
|
if cli.emission() {
|
|
|
|
let emission_funds = PoaContract::read(
|
|
|
|
ContractType::Emission,
|
|
|
|
network,
|
|
|
|
version,
|
|
|
|
)?;
|
|
|
|
contracts.push(emission_funds);
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
2018-09-25 06:56:44 -07:00
|
|
|
if contracts.is_empty() {
|
|
|
|
return Err(Error::MustSpecifyAtLeastOneCliArgument(
|
|
|
|
"`--keys`, `--threshold`, `--proxy`, `--emission`".into()
|
|
|
|
));
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
|
2018-12-11 06:54:31 -08:00
|
|
|
let start_block = if cli.multiple_start_blocks_specified() {
|
2018-09-25 06:56:44 -07:00
|
|
|
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 block_time = if let Some(s) = cli.block_time() {
|
|
|
|
s.parse().map_err(|_| Error::InvalidBlockTime(s.into()))?
|
|
|
|
} else {
|
|
|
|
DEFAULT_BLOCK_TIME_SECS
|
|
|
|
};
|
|
|
|
|
|
|
|
let email_notifications = cli.email();
|
2018-12-11 06:54:31 -08:00
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
let email_recipients: Vec<String> = env::var("EMAIL_RECIPIENTS")
|
|
|
|
.map_err(|_| Error::MissingEnvVar("EMAIL_RECIPIENTS".into()))?
|
|
|
|
.split(',')
|
|
|
|
.filter_map(|s| {
|
|
|
|
if s.is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(s.into())
|
|
|
|
}
|
2018-04-21 09:59:11 -07:00
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
let smtp_host_domain = if email_notifications {
|
|
|
|
let host = env::var("SMTP_HOST_DOMAIN")
|
|
|
|
.map_err(|_| Error::MissingEnvVar("SMTP_HOST_DOMAIN".into()))?;
|
|
|
|
Some(host)
|
2018-04-21 09:59:11 -07:00
|
|
|
} else {
|
2018-09-25 06:56:44 -07:00
|
|
|
None
|
2018-04-21 09:59:11 -07:00
|
|
|
};
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
let smtp_port = if email_notifications {
|
|
|
|
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()));
|
|
|
|
}
|
2018-04-21 09:59:11 -07:00
|
|
|
} else {
|
2018-09-25 06:56:44 -07:00
|
|
|
None
|
2018-04-21 09:59:11 -07:00
|
|
|
};
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
let smtp_username = if email_notifications {
|
|
|
|
let username = env::var("SMTP_USERNAME")
|
|
|
|
.map_err(|_| Error::MissingEnvVar("SMTP_USERNAME".into()))?;
|
|
|
|
Some(username)
|
2018-04-21 09:59:11 -07:00
|
|
|
} else {
|
2018-09-25 06:56:44 -07:00
|
|
|
None
|
2018-04-21 09:59:11 -07:00
|
|
|
};
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
let smtp_password = if email_notifications {
|
|
|
|
let password = env::var("SMTP_PASSWORD")
|
|
|
|
.map_err(|_| Error::MissingEnvVar("SMTP_PASSWORD".into()))?;
|
|
|
|
Some(password)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2018-04-21 09:59:11 -07:00
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
let outgoing_email_addr = if email_notifications {
|
|
|
|
let email_addr = env::var("OUTGOING_EMAIL_ADDRESS")
|
|
|
|
.map_err(|_| Error::MissingEnvVar("OUTGOING_EMAIL_ADDRESS".into()))?;
|
|
|
|
Some(email_addr)
|
2018-04-21 09:59:11 -07:00
|
|
|
} else {
|
2018-09-25 06:56:44 -07:00
|
|
|
None
|
2018-04-21 09:59:11 -07:00
|
|
|
};
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
let notification_limit = if let Some(s) = cli.notification_limit() {
|
|
|
|
let limit = s.parse().map_err(|_| Error::InvalidNotificationLimit(s.into()))?;
|
|
|
|
Some(limit)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2018-10-10 07:46:20 -07:00
|
|
|
let log_emails = cli.log_emails();
|
|
|
|
let log_to_file = cli.log_to_file();
|
2018-09-25 06:56:44 -07:00
|
|
|
|
|
|
|
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,
|
2018-10-10 07:46:20 -07:00
|
|
|
log_emails,
|
|
|
|
log_to_file,
|
2018-09-25 06:56:44 -07:00
|
|
|
};
|
|
|
|
Ok(config)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use std::env;
|
|
|
|
|
2018-10-10 07:46:20 -07:00
|
|
|
use super::super::tests::setup;
|
|
|
|
use super::{ContractType, ContractVersion, PoaContract, Network};
|
2018-09-25 06:56:44 -07:00
|
|
|
|
2018-12-11 06:54:31 -08:00
|
|
|
const CONTRACT_TYPES: [ContractType; 4] = [
|
2018-09-25 06:56:44 -07:00
|
|
|
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]
|
2018-10-10 07:46:20 -07:00
|
|
|
fn test_env_file_integrity() {
|
|
|
|
setup();
|
2018-09-25 06:56:44 -07:00
|
|
|
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() {
|
2018-10-10 07:46:20 -07:00
|
|
|
setup();
|
2018-09-25 06:56:44 -07:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|