Directory Restructure (#1163)

* Move tests -> testing

* Directory restructure

* Update Cargo.toml during restructure

* Update Makefile during restructure

* Fix arbitrary path
This commit is contained in:
Paul Hauner
2020-05-18 21:24:23 +10:00
committed by GitHub
parent c571afb8d8
commit 4331834003
358 changed files with 217 additions and 229 deletions

1
common/deposit_contract/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
contracts/

View File

@@ -0,0 +1,17 @@
[package]
name = "deposit_contract"
version = "0.2.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
build = "build.rs"
[build-dependencies]
reqwest = { version = "0.10.4", features = ["blocking", "json"] }
serde_json = "1.0.52"
[dependencies]
types = { path = "../../consensus/types"}
eth2_ssz = "0.1.2"
tree_hash = "0.1.0"
ethabi = "12.0.0"

View File

@@ -0,0 +1,110 @@
//! Downloads the ABI and bytecode for the deposit contract from the ethereum spec repository and
//! stores them in a `contract/` directory in the crate root.
//!
//! These files are required for some `include_bytes` calls used in this crate.
use serde_json::Value;
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
const TAG: &str = "v0.11.1";
// NOTE: the version of the unsafe contract lags the main tag, but the v0.9.2.1 code is compatible
// with the unmodified v0.11.1 contract
const UNSAFE_TAG: &str = "v0.9.2.1";
fn spec_url() -> String {
format!("https://raw.githubusercontent.com/ethereum/eth2.0-specs/{}/deposit_contract/contracts/validator_registration.json", TAG)
}
fn testnet_url() -> String {
format!("https://raw.githubusercontent.com/sigp/unsafe-eth2-deposit-contract/{}/unsafe_validator_registration.json", UNSAFE_TAG)
}
fn main() {
match get_all_contracts() {
Ok(()) => (),
Err(e) => panic!(e),
}
}
/// Attempts to download the deposit contract ABI from github if a local copy is not already
/// present.
pub fn get_all_contracts() -> Result<(), String> {
download_deposit_contract(
&spec_url(),
"validator_registration.json",
"validator_registration.bytecode",
)?;
download_deposit_contract(
&testnet_url(),
"testnet_validator_registration.json",
"testnet_validator_registration.bytecode",
)
}
/// Attempts to download the deposit contract ABI from github if a local copy is not already
/// present.
pub fn download_deposit_contract(
url: &str,
abi_file: &str,
bytecode_file: &str,
) -> Result<(), String> {
let abi_file = abi_dir().join(format!("{}_{}", TAG, abi_file));
let bytecode_file = abi_dir().join(format!("{}_{}", TAG, bytecode_file));
if abi_file.exists() {
// Nothing to do.
} else {
match reqwest::blocking::get(url) {
Ok(response) => {
let mut abi_file = File::create(abi_file)
.map_err(|e| format!("Failed to create local abi file: {:?}", e))?;
let mut bytecode_file = File::create(bytecode_file)
.map_err(|e| format!("Failed to create local bytecode file: {:?}", e))?;
let contract: Value = response
.json()
.map_err(|e| format!("Respsonse is not a valid json {:?}", e))?;
let abi = contract
.get("abi")
.ok_or(format!("Response does not contain key: abi"))?
.to_string();
abi_file
.write(abi.as_bytes())
.map_err(|e| format!("Failed to write http response to abi file: {:?}", e))?;
let bytecode = contract
.get("bytecode")
.ok_or(format!("Response does not contain key: bytecode"))?
.to_string();
bytecode_file.write(bytecode.as_bytes()).map_err(|e| {
format!("Failed to write http response to bytecode file: {:?}", e)
})?;
}
Err(e) => {
return Err(format!(
"No abi file found. Failed to download from github: {:?}",
e
))
}
}
}
Ok(())
}
/// Returns the directory that will be used to store the deposit contract ABI.
fn abi_dir() -> PathBuf {
let base = env::var("CARGO_MANIFEST_DIR")
.expect("should know manifest dir")
.parse::<PathBuf>()
.expect("should parse manifest dir as path")
.join("contracts");
std::fs::create_dir_all(base.clone())
.expect("should be able to create abi directory in manifest");
base
}

View File

@@ -0,0 +1,131 @@
use ethabi::{Contract, Token};
use ssz::{Decode, DecodeError as SszDecodeError, Encode};
use tree_hash::TreeHash;
use types::{DepositData, Hash256, PublicKeyBytes, SignatureBytes};
pub use ethabi::Error;
#[derive(Debug)]
pub enum DecodeError {
EthabiError(ethabi::Error),
SszDecodeError(SszDecodeError),
MissingField,
UnableToGetBytes,
MissingToken,
InadequateBytes,
}
impl From<ethabi::Error> for DecodeError {
fn from(e: ethabi::Error) -> DecodeError {
DecodeError::EthabiError(e)
}
}
pub const CONTRACT_DEPLOY_GAS: usize = 4_000_000;
pub const DEPOSIT_GAS: usize = 400_000;
pub const ABI: &[u8] = include_bytes!("../contracts/v0.11.1_validator_registration.json");
pub const BYTECODE: &[u8] = include_bytes!("../contracts/v0.11.1_validator_registration.bytecode");
pub const DEPOSIT_DATA_LEN: usize = 420; // lol
pub mod testnet {
pub const ABI: &[u8] =
include_bytes!("../contracts/v0.11.1_testnet_validator_registration.json");
pub const BYTECODE: &[u8] =
include_bytes!("../contracts/v0.11.1_testnet_validator_registration.bytecode");
}
pub fn encode_eth1_tx_data(deposit_data: &DepositData) -> Result<Vec<u8>, Error> {
let params = vec![
Token::Bytes(deposit_data.pubkey.as_ssz_bytes()),
Token::Bytes(deposit_data.withdrawal_credentials.as_ssz_bytes()),
Token::Bytes(deposit_data.signature.as_ssz_bytes()),
Token::FixedBytes(deposit_data.tree_hash_root().as_ssz_bytes()),
];
// Here we make an assumption that the `crate::testnet::ABI` has a superset of the features of
// the crate::ABI`.
let abi = Contract::load(ABI)?;
let function = abi.function("deposit")?;
function.encode_input(&params)
}
pub fn decode_eth1_tx_data(
bytes: &[u8],
amount: u64,
) -> Result<(DepositData, Hash256), DecodeError> {
let abi = Contract::load(ABI)?;
let function = abi.function("deposit")?;
let mut tokens =
function.decode_input(bytes.get(4..).ok_or_else(|| DecodeError::InadequateBytes)?)?;
macro_rules! decode_token {
($type: ty, $to_fn: ident) => {
<$type>::from_ssz_bytes(
&tokens
.pop()
.ok_or_else(|| DecodeError::MissingToken)?
.$to_fn()
.ok_or_else(|| DecodeError::UnableToGetBytes)?,
)
.map_err(DecodeError::SszDecodeError)?
};
};
let root = decode_token!(Hash256, to_fixed_bytes);
let deposit_data = DepositData {
amount,
signature: decode_token!(SignatureBytes, to_bytes),
withdrawal_credentials: decode_token!(Hash256, to_bytes),
pubkey: decode_token!(PublicKeyBytes, to_bytes),
};
Ok((deposit_data, root))
}
#[cfg(test)]
mod tests {
use super::*;
use types::{
test_utils::generate_deterministic_keypair, ChainSpec, EthSpec, Hash256, Keypair,
MinimalEthSpec, Signature,
};
type E = MinimalEthSpec;
fn get_deposit(keypair: Keypair, spec: &ChainSpec) -> DepositData {
let mut deposit_data = DepositData {
pubkey: keypair.pk.into(),
withdrawal_credentials: Hash256::from_slice(&[42; 32]),
amount: u64::max_value(),
signature: Signature::empty_signature().into(),
};
deposit_data.signature = deposit_data.create_signature(&keypair.sk, spec);
deposit_data
}
#[test]
fn round_trip() {
let spec = &E::default_spec();
let keypair = generate_deterministic_keypair(42);
let original = get_deposit(keypair, spec);
let data = encode_eth1_tx_data(&original).expect("should produce tx data");
assert_eq!(
data.len(),
DEPOSIT_DATA_LEN,
"bytes should be correct length"
);
let (decoded, root) = decode_eth1_tx_data(&data, original.amount).expect("should decode");
assert_eq!(decoded, original, "decoded should match original");
assert_eq!(
root,
original.tree_hash_root(),
"decode root should match original root"
);
}
}