From 0857f6fe32806b0c566ebbd48214fe99bdd2cce6 Mon Sep 17 00:00:00 2001 From: Huan-Cheng Chang Date: Fri, 29 Nov 2024 13:40:43 +0000 Subject: [PATCH] feat(jstzd): insert jstz bootstrap contracts --- crates/jstzd/src/config.rs | 166 +++++++++++++++++++++++++++- crates/jstzd/src/lib.rs | 2 +- crates/jstzd/tests/jstzd_test.rs | 13 +-- crates/octez/src/async/bootstrap.rs | 4 + crates/octez/src/async/protocol.rs | 7 ++ 5 files changed, 181 insertions(+), 11 deletions(-) diff --git a/crates/jstzd/src/config.rs b/crates/jstzd/src/config.rs index 2371a2015..d9fa9c1fe 100644 --- a/crates/jstzd/src/config.rs +++ b/crates/jstzd/src/config.rs @@ -1,6 +1,10 @@ #![allow(dead_code)] +use std::path::{Path, PathBuf}; + use crate::task::jstzd::JstzdConfig; +use crate::{EXCHANGER_ADDRESS, JSTZ_NATIVE_BRIDGE_ADDRESS}; use anyhow::{Context, Result}; +use octez::r#async::protocol::{BootstrapContract, ProtocolParameter}; use octez::{ r#async::{ baker::{BakerBinaryPath, OctezBakerConfig, OctezBakerConfigBuilder}, @@ -15,6 +19,10 @@ use tokio::io::AsyncReadExt; const ACTIVATOR_PUBLIC_KEY: &str = "edpkuSLWfVU1Vq7Jg9FucPyKmma6otcMHac9zG4oU1KMHSTBpJuGQ2"; +pub const BOOTSTRAP_CONTRACT_NAMES: [(&str, &str); 2] = [ + ("exchanger", EXCHANGER_ADDRESS), + ("jstz_native_bridge", JSTZ_NATIVE_BRIDGE_ADDRESS), +]; #[derive(Deserialize, Default)] struct Config { @@ -58,7 +66,7 @@ async fn build_config( &octez_client_config, )?; - let protocol_params = config.protocol.build()?; + let protocol_params = build_protocol_params(config.protocol).await?; let server_port = config.server_port.unwrap_or(unused_port()); Ok(( server_port, @@ -105,6 +113,51 @@ fn populate_baker_config( config_builder.build() } +async fn read_bootstrap_contracts() -> anyhow::Result> { + let mut contracts = vec![]; + for (contract_name, hash) in BOOTSTRAP_CONTRACT_NAMES { + let script = read_json_file( + Path::new(std::env!("CARGO_MANIFEST_DIR")) + .join(format!("resources/bootstrap_contract/{contract_name}.json")), + ) + .await + .context(format!( + "error loading bootstrap contract '{contract_name}'" + ))?; + contracts.push(BootstrapContract::new(script, 1_000_000, Some(hash)).unwrap()); + } + Ok(contracts) +} + +async fn read_json_file(path: PathBuf) -> anyhow::Result { + let mut buf = String::new(); + tokio::fs::File::open(&path) + .await? + .read_to_string(&mut buf) + .await?; + Ok(serde_json::from_str(&buf)?) +} + +async fn build_protocol_params( + mut builder: ProtocolParameterBuilder, +) -> anyhow::Result { + // User contracts whose addresses collide with those reserved for jstz contracts + // will overwrite jstz contracts. This aligns with the current implementation + // where bootstrap contracts in the base parameter file take precedence, even + // if it means that jstz won't launch in such cases. + let mut contracts = builder + .bootstrap_contracts() + .iter() + .map(|v| (*v).to_owned()) + .collect::>(); + for contract in read_bootstrap_contracts().await? { + contracts.push(contract); + } + + // TODO: insert jstz rollup + builder.set_bootstrap_contracts(contracts).build() +} + #[cfg(test)] mod tests { use std::{io::Read, io::Write, path::PathBuf, str::FromStr}; @@ -123,9 +176,26 @@ mod tests { }, }; use tempfile::{tempdir, NamedTempFile}; + use tezos_crypto_rs::hash::ContractKt1Hash; use super::Config; + async fn read_bootstrap_contracts_from_param_file( + path: PathBuf, + ) -> Vec { + let params_json = super::read_json_file(path).await.unwrap(); + params_json + .as_object() + .unwrap() + .get("bootstrap_contracts") + .unwrap() + .as_array() + .unwrap() + .iter() + .map(|v| serde_json::from_value::(v.to_owned()).unwrap()) + .collect::>() + } + #[tokio::test] async fn parse_config() { let mut tmp_file = NamedTempFile::new().unwrap(); @@ -332,6 +402,7 @@ mod tests { })) .unwrap(); tmp_file.write_all(content.as_bytes()).unwrap(); + let (_, config) = super::build_config(&Some(tmp_file.path().to_str().unwrap().to_owned())) .await @@ -340,6 +411,16 @@ mod tests { config.octez_client_config().octez_node_endpoint(), &Endpoint::localhost(9999) ); + + let contracts = read_bootstrap_contracts_from_param_file( + config + .protocol_params() + .parameter_file() + .path() + .to_path_buf(), + ) + .await; + assert_eq!(contracts.len(), 2); } #[tokio::test] @@ -391,4 +472,87 @@ mod tests { &Endpoint::localhost(8888) ); } + + #[tokio::test] + async fn read_bootstrap_contracts() { + let mut contracts = super::read_bootstrap_contracts() + .await + .unwrap() + .iter() + .map(|v| v.hash().to_owned()) + .collect::>>(); + contracts.sort(); + assert_eq!( + contracts, + vec![ + Some( + ContractKt1Hash::from_base58_check(super::EXCHANGER_ADDRESS).unwrap() + ), + Some( + ContractKt1Hash::from_base58_check(super::JSTZ_NATIVE_BRIDGE_ADDRESS) + .unwrap() + ) + ] + ) + } + + #[tokio::test] + async fn build_protocol_params() { + let mut builder = ProtocolParameterBuilder::new(); + builder.set_bootstrap_accounts([BootstrapAccount::new( + super::ACTIVATOR_PUBLIC_KEY, + 40_000_000_000, + ) + .unwrap()]); + let params = super::build_protocol_params(builder).await.unwrap(); + let mut addresses = read_bootstrap_contracts_from_param_file( + params.parameter_file().path().to_path_buf(), + ) + .await + .iter() + .map(|v| v.hash().as_ref().unwrap().clone().to_string()) + .collect::>(); + addresses.sort(); + assert_eq!( + addresses, + [super::EXCHANGER_ADDRESS, super::JSTZ_NATIVE_BRIDGE_ADDRESS] + ); + } + + #[tokio::test] + async fn build_protocol_params_contract_collision() { + let dummy_contract = BootstrapContract::new( + serde_json::json!("test-contract"), + 1, + Some(super::EXCHANGER_ADDRESS), + ) + .unwrap(); + let mut builder = ProtocolParameterBuilder::new(); + builder + .set_bootstrap_accounts([BootstrapAccount::new( + super::ACTIVATOR_PUBLIC_KEY, + 40_000_000_000, + ) + .unwrap()]) + .set_bootstrap_contracts([dummy_contract.clone()]); + let params = super::build_protocol_params(builder).await.unwrap(); + let mut contracts = read_bootstrap_contracts_from_param_file( + params.parameter_file().path().to_path_buf(), + ) + .await; + assert_eq!(contracts.len(), 2); + + contracts.sort_by_key(|v| v.hash().as_ref().unwrap().to_string()); + let addresses = contracts + .iter() + .map(|v| v.hash().to_owned().unwrap().to_string()) + .collect::>(); + assert_eq!( + addresses, + [super::EXCHANGER_ADDRESS, super::JSTZ_NATIVE_BRIDGE_ADDRESS] + ); + // the first contract should be overwritten by the dummy contract + let exchanger_contract = contracts.first().unwrap(); + assert_eq!(exchanger_contract, &dummy_contract); + } } diff --git a/crates/jstzd/src/lib.rs b/crates/jstzd/src/lib.rs index 6f4b5bc0d..47437595a 100644 --- a/crates/jstzd/src/lib.rs +++ b/crates/jstzd/src/lib.rs @@ -1,7 +1,7 @@ mod config; pub mod docker; pub mod task; - +pub use config::BOOTSTRAP_CONTRACT_NAMES; pub const EXCHANGER_ADDRESS: &str = "KT1F3MuqvT9Yz57TgCS3EkDcKNZe9HpiavUJ"; pub const JSTZ_ROLLUP_ADDRESS: &str = "sr1PuFMgaRUN12rKQ3J2ae5psNtwCxPNmGNK"; pub const JSTZ_NATIVE_BRIDGE_ADDRESS: &str = "KT1GFiPkkTjd14oHe6MrBPiRh5djzRkVWcni"; diff --git a/crates/jstzd/tests/jstzd_test.rs b/crates/jstzd/tests/jstzd_test.rs index 0bb56b1b9..3b58e3508 100644 --- a/crates/jstzd/tests/jstzd_test.rs +++ b/crates/jstzd/tests/jstzd_test.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use jstzd::task::jstzd::{JstzdConfig, JstzdServer}; use jstzd::task::utils::retry; -use jstzd::{EXCHANGER_ADDRESS, JSTZ_NATIVE_BRIDGE_ADDRESS}; +use jstzd::BOOTSTRAP_CONTRACT_NAMES; use octez::r#async::baker::{BakerBinaryPath, OctezBakerConfigBuilder}; use octez::r#async::client::{OctezClient, OctezClientConfigBuilder}; use octez::r#async::endpoint::Endpoint; @@ -14,11 +14,6 @@ use octez::r#async::protocol::{ use octez::unused_port; const CONTRACT_INIT_BALANCE: f64 = 1.0; -const CONTRACT_NAMES: [(&str, &str); 2] = [ - ("exchanger", EXCHANGER_ADDRESS), - ("jstz_native_bridge", JSTZ_NATIVE_BRIDGE_ADDRESS), -]; - #[tokio::test(flavor = "multi_thread")] async fn jstzd_test() { let rpc_endpoint = Endpoint::localhost(unused_port()); @@ -214,7 +209,7 @@ async fn fetch_config_test(jstzd_config: JstzdConfig, jstzd_port: u16) { async fn read_bootstrap_contracts() -> Vec { let mut contracts = vec![]; - for (contract_name, hash) in CONTRACT_NAMES { + for (contract_name, hash) in BOOTSTRAP_CONTRACT_NAMES { let script = utils::read_json_file( PathBuf::from(std::env!("CARGO_MANIFEST_DIR")) .join(format!("resources/bootstrap_contract/{contract_name}.json")), @@ -233,10 +228,10 @@ async fn read_bootstrap_contracts() -> Vec { } async fn check_bootstrap_contracts(octez_client: &OctezClient) { - for (contract_name, hash) in CONTRACT_NAMES { + for (contract_name, hash) in BOOTSTRAP_CONTRACT_NAMES { assert_eq!( octez_client - .get_balance(EXCHANGER_ADDRESS) + .get_balance(hash) .await .unwrap_or_else(|_| panic!( "should be able to find contract '{contract_name}' at '{hash}'" diff --git a/crates/octez/src/async/bootstrap.rs b/crates/octez/src/async/bootstrap.rs index a8888b37d..925f639c5 100644 --- a/crates/octez/src/async/bootstrap.rs +++ b/crates/octez/src/async/bootstrap.rs @@ -211,6 +211,10 @@ impl BootstrapContract { }, }) } + + pub fn hash(&self) -> &Option { + &self.hash + } } #[derive(Default, Debug, PartialEq)] diff --git a/crates/octez/src/async/protocol.rs b/crates/octez/src/async/protocol.rs index 20ce8cd98..1b3098f1c 100644 --- a/crates/octez/src/async/protocol.rs +++ b/crates/octez/src/async/protocol.rs @@ -185,6 +185,13 @@ impl ProtocolParameterBuilder { self } + pub fn bootstrap_contracts(&self) -> Vec<&BootstrapContract> { + self.bootstrap_contracts + .contracts() + .iter() + .collect::>() + } + pub fn set_bootstrap_smart_rollups( &mut self, rollups: impl IntoIterator,