diff --git a/Cargo.lock b/Cargo.lock index c366c981..e044c28e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2642,6 +2642,7 @@ dependencies = [ "bollard", "futures-util", "http 1.1.0", + "jstz_crypto", "octez", "rand 0.8.5", "reqwest", diff --git a/crates/jstzd/Cargo.toml b/crates/jstzd/Cargo.toml index 51c5ba08..79f794fa 100644 --- a/crates/jstzd/Cargo.toml +++ b/crates/jstzd/Cargo.toml @@ -12,6 +12,7 @@ async-trait.workspace = true bollard.workspace = true futures-util.workspace = true http.workspace = true +jstz_crypto = { path = "../jstz_crypto" } octez = { path = "../octez" } reqwest.workspace = true rust-embed.workspace = true diff --git a/crates/jstzd/src/protocol/bootstrap.rs b/crates/jstzd/src/protocol/bootstrap.rs new file mode 100644 index 00000000..fa2ccccd --- /dev/null +++ b/crates/jstzd/src/protocol/bootstrap.rs @@ -0,0 +1,80 @@ +use jstz_crypto::public_key::PublicKey; +use serde_json::Value; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct BootstrapAccount { + public_key: PublicKey, + amount_tez: u64, +} + +impl BootstrapAccount { + pub fn new(public_key: &str, amount: u64) -> anyhow::Result { + Ok(Self { + public_key: PublicKey::from_base58(public_key)?, + amount_tez: amount, + }) + } +} + +#[derive(Default)] +pub struct BootstrapAccounts { + pub accounts: Vec, +} + +impl From for Value { + fn from(value: BootstrapAccounts) -> Self { + Value::Array( + value + .accounts + .iter() + .map(|v| { + Value::Object({ + let mut map = serde_json::Map::new(); + map.insert( + "public_key".to_owned(), + Value::String(v.public_key.to_string()), + ); + map.insert( + "amount".to_owned(), + Value::Number(v.amount_tez.into()), + ); + map + }) + }) + .collect(), + ) + } +} + +#[cfg(test)] +mod tests { + use serde_json::Value; + + use super::{BootstrapAccount, BootstrapAccounts}; + + const ACCOUNT_PUBLIC_KEY: &str = + "edpkubRfnPoa6ts5vBdTB5syvjeK2AyEF3kyhG4Sx7F9pU3biku4wv"; + + #[test] + fn bootstrap_account_new() { + let account = BootstrapAccount::new(ACCOUNT_PUBLIC_KEY, 1000).unwrap(); + assert_eq!(account.public_key.to_string(), ACCOUNT_PUBLIC_KEY); + assert_eq!(account.amount_tez, 1000); + } + + #[test] + fn serde_value_from_bootstrap_accounts() { + let accounts = BootstrapAccounts { + accounts: vec![BootstrapAccount::new(ACCOUNT_PUBLIC_KEY, 1000).unwrap()], + }; + let value = Value::from(accounts); + let arr = value.as_array().unwrap(); + assert_eq!(arr.len(), 1); + let account = arr.last().unwrap().as_object().unwrap(); + assert_eq!( + account.get("public_key").unwrap().as_str().unwrap(), + ACCOUNT_PUBLIC_KEY + ); + assert_eq!(account.get("amount").unwrap().as_u64().unwrap(), 1000); + } +} diff --git a/crates/jstzd/src/protocol/mod.rs b/crates/jstzd/src/protocol/mod.rs index 555a3703..021820f8 100644 --- a/crates/jstzd/src/protocol/mod.rs +++ b/crates/jstzd/src/protocol/mod.rs @@ -1,3 +1,7 @@ +mod bootstrap; + +pub use bootstrap::BootstrapAccount; +use bootstrap::BootstrapAccounts; use rust_embed::Embed; use serde_json::Value; use std::fmt::Display; @@ -67,6 +71,7 @@ pub struct ProtocolParameterFile; pub struct ProtocolParameterBuilder { protocol: Protocol, constants: ProtocolConstants, + bootstrap_accounts: BootstrapAccounts, path: Option, } @@ -85,6 +90,14 @@ impl ProtocolParameterBuilder { self } + pub fn set_bootstrap_accounts( + &mut self, + accounts: Vec, + ) -> &mut Self { + self.bootstrap_accounts.accounts = accounts; + self + } + pub fn set_path(&mut self, path: &str) -> &mut Self { self.path = Some(PathBuf::from(path)); self @@ -102,6 +115,10 @@ impl ProtocolParameterBuilder { let json = raw_json .as_object_mut() .ok_or(anyhow::anyhow!("Failed to convert json file"))?; + json.insert( + "bootstrap_accounts".to_owned(), + Value::from(self.bootstrap_accounts), + ); drop(f); let path = self .path @@ -114,7 +131,12 @@ impl ProtocolParameterBuilder { #[cfg(test)] mod tests { - use super::{Protocol, ProtocolConstants, ProtocolParameterBuilder}; + use super::{ + BootstrapAccount, Protocol, ProtocolConstants, ProtocolParameterBuilder, + }; + + const ACCOUNT_PUBLIC_KEY: &str = + "edpktzB3sirfeX6PrgAgWvRVT8Fd28jVLbWXKJmaUrYmK2UoSHc1eJ"; #[test] fn parameter_builder() { @@ -122,13 +144,21 @@ mod tests { builder .set_constants(ProtocolConstants::TestConstants) .set_protocol(Protocol::TestVersion) - .set_path("/test/path"); + .set_path("/test/path") + .set_bootstrap_accounts( + [BootstrapAccount::new(ACCOUNT_PUBLIC_KEY, 1000).unwrap()].to_vec(), + ); assert_eq!(builder.constants, ProtocolConstants::TestConstants); assert_eq!( builder.path.unwrap().as_os_str().to_str().unwrap(), "/test/path" ); assert_eq!(builder.protocol.hash(), Protocol::TestVersion.hash()); + assert_eq!(builder.bootstrap_accounts.accounts.len(), 1); + assert_eq!( + builder.bootstrap_accounts.accounts.last().unwrap(), + &BootstrapAccount::new(ACCOUNT_PUBLIC_KEY, 1000).unwrap() + ); } #[test] @@ -140,6 +170,7 @@ mod tests { ); assert!(builder.path.is_none()); assert_eq!(builder.protocol, Protocol::Alpha); + assert!(builder.bootstrap_accounts.accounts.is_empty()); } #[tokio::test] @@ -150,7 +181,10 @@ mod tests { builder .set_path(expected_output_path.as_os_str().to_str().unwrap()) .set_protocol(Protocol::Alpha) - .set_constants(ProtocolConstants::Sandbox); + .set_constants(ProtocolConstants::Sandbox) + .set_bootstrap_accounts( + [BootstrapAccount::new(ACCOUNT_PUBLIC_KEY, 1000).unwrap()].to_vec(), + ); let output_path = builder.build().await.unwrap(); assert_eq!(expected_output_path, output_path); let file = std::fs::File::open(output_path).unwrap(); @@ -162,5 +196,15 @@ mod tests { .unwrap(), 2 ); + + // Check accounts + let accounts = json.get("bootstrap_accounts").unwrap().as_array().unwrap(); + assert_eq!(accounts.len(), 1); + let account = accounts.last().unwrap().as_object().unwrap(); + let mut keys = account.keys().collect::>(); + keys.sort(); + assert_eq!(keys, ["amount", "public_key"]); + assert_eq!(account.get("amount").unwrap(), 1000); + assert_eq!(account.get("public_key").unwrap(), ACCOUNT_PUBLIC_KEY); } }