mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-07 00:42:42 +00:00
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:
1
common/deposit_contract/.gitignore
vendored
Normal file
1
common/deposit_contract/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
contracts/
|
||||
17
common/deposit_contract/Cargo.toml
Normal file
17
common/deposit_contract/Cargo.toml
Normal 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"
|
||||
110
common/deposit_contract/build.rs
Normal file
110
common/deposit_contract/build.rs
Normal 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
|
||||
}
|
||||
131
common/deposit_contract/src/lib.rs
Normal file
131
common/deposit_contract/src/lib.rs
Normal 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(¶ms)
|
||||
}
|
||||
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user