diff --git a/.gitignore b/.gitignore index 7d8a35512..c2c865301 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ proc-compose.temp.log methods/guest/Cargo.lock networks/movement/movement-client/src/move-modules/build/* +util/format-module-bytes/build/* .idea/ target/ ledger_db/ diff --git a/Cargo.lock b/Cargo.lock index a58be7295..cd9f142f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7654,6 +7654,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "format-module-bytes" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "serde_json", + "serde_yaml 0.9.34+deprecated", +] + [[package]] name = "funty" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index a5242b82e..0bc4568ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "util/tracing", "util/syncador", "util/collections", + "util/format-module-bytes", "util/whitelist", "networks/movement/*", "benches/*", diff --git a/util/format-module-bytes/Cargo.toml b/util/format-module-bytes/Cargo.toml new file mode 100644 index 000000000..d080f98c6 --- /dev/null +++ b/util/format-module-bytes/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "format-module-bytes" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde_yaml = "0.9" diff --git a/util/format-module-bytes/Move.toml b/util/format-module-bytes/Move.toml new file mode 100644 index 000000000..ff6a11f2d --- /dev/null +++ b/util/format-module-bytes/Move.toml @@ -0,0 +1,6 @@ +[package] +name = "hello" +version = "0.0.1" + +[addresses] +hello = "_" diff --git a/util/format-module-bytes/README.md b/util/format-module-bytes/README.md new file mode 100644 index 000000000..11c1c60cd --- /dev/null +++ b/util/format-module-bytes/README.md @@ -0,0 +1,64 @@ +# format-module-bytes + +This tool prepares a Move package for publishing using the Movement Explorer UI or via Movement CLI with `movement multisig create-transaction` + +It compiles the embedded Move package and outputs the required `arg0` and `arg1` values for the `code::publish_package_txn` function: + +[https://explorer.movementlabs.xyz/account/0x0000000000000000000000000000000000000000000000000000000000000001/modules/run/code/publish\_package\_txn?network=mainnet](https://explorer.movementlabs.xyz/account/0x0000000000000000000000000000000000000000000000000000000000000001/modules/run/code/publish_package_txn?network=mainnet) + +## Function Signature + +```move +entry fun publish_package_txn( + owner: &signer, + metadata_serialized: vector, + code: vector> +) +``` + +## Usage + +The easiest way to use the formatted module bytes is via Movement explorer. + +1. Ensure the `movement` CLI is installed and available in your `PATH`. You also need to run `movement init` or directly add a `.movement/config.yaml` in the `util/format-module-bytes` dir. + +2. Run from the workspace root: + + ```bash + cargo run -p format-module-bytes + ``` + +3. The tool will compile the embedded Move package and output two formatted arguments: + + * `arg0` (vector) + * `arg1` (vector\>) + + It will also write these to: + + ``` + format-module-bytes/build/hello/explorer_payload.log + ``` + +## Submitting via the Explorer UI + +1. Open the following link (for mainnet publishing... you can switch to testnet if you prefer): + + [https://explorer.movementlabs.xyz/account/0x0000000000000000000000000000000000000000000000000000000000000001/modules/run/code/publish\_package\_txn?network=mainnet](https://explorer.movementlabs.xyz/account/0x0000000000000000000000000000000000000000000000000000000000000001/modules/run/code/publish_package_txn?network=mainnet) + +2. Input the args: + + * **signer**: your account address (must be funded) + * **arg0**: the full vector array (surrounded by brackets) + * **arg1**: the full vector\> array (outer and inner brackets must be present) + + Example: + + ```json + arg0: [5,104,101,108,108,111,...] + arg1: [[161,28,235,11,...]] + ``` + > [!TIP] Be sure to connect to the explorer with the same wallet that you used in your local Movement config. + + After successful publishing via explorer, you will see a successful message as follows: + + image diff --git a/util/format-module-bytes/sources/hello.move b/util/format-module-bytes/sources/hello.move new file mode 100644 index 000000000..263bd867e --- /dev/null +++ b/util/format-module-bytes/sources/hello.move @@ -0,0 +1,5 @@ +module hello::hello { + public entry fun hi() { + // no-op + } +} \ No newline at end of file diff --git a/util/format-module-bytes/src/lib.rs b/util/format-module-bytes/src/lib.rs new file mode 100644 index 000000000..c58e6f617 --- /dev/null +++ b/util/format-module-bytes/src/lib.rs @@ -0,0 +1,118 @@ +use anyhow::{Context, Result}; +use std::{ + env, + fs::{self, File}, + io::Write, + path::{Path, PathBuf}, + process::Command, +}; + +fn read_bytes(path: &Path) -> Result> { + Ok(fs::read(path)?) +} + +fn format_vector_u8(data: &[u8]) -> String { + format!("[{}]", data.iter().map(|b| b.to_string()).collect::>().join(",")) +} + +fn format_vector_vector_u8(data: &[Vec]) -> String { + serde_json::to_string(data).expect("json encode failed") +} + +fn address_from_config() -> Result { + let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let config_path = crate_dir.join(".movement/config.yaml"); + let contents = fs::read_to_string(&config_path) + .with_context(|| format!("failed to read {}", config_path.display()))?; + + let config: serde_yaml::Value = serde_yaml::from_str(&contents)?; + let default = config + .get("profiles") + .and_then(|p| p.get("default")) + .context("missing [profiles][default] in config")?; + + let addr = default + .get("account") + .or_else(|| default.get("address")) + .context("missing 'account' or 'address' under [profiles][default]")? + .as_str() + .context("address is not a string")?; + + Ok(addr.to_string()) +} + +pub fn run() -> Result<()> { + let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let address = address_from_config()?; + + let move_compile = Command::new("movement") + .args([ + "move", + "compile", + "--named-addresses", + &format!("hello={}", address), + "--save-metadata", + "--package-dir", + ".", + ]) + .current_dir(&crate_dir) + .status() + .context("failed to run movement move compile")?; + + if !move_compile.success() { + anyhow::bail!("Move compilation failed"); + } + + let build_dir = crate_dir.join("build/hello"); + let metadata_path = build_dir.join("package-metadata.bcs"); + let modules_dir = build_dir.join("bytecode_modules"); + + let metadata = read_bytes(&metadata_path)?; + let mut modules = Vec::new(); + for entry in fs::read_dir(&modules_dir)? { + let entry = entry?; + if entry.path().extension().map(|ext| ext == "mv").unwrap_or(false) { + modules.push(read_bytes(&entry.path())?); + } + } + + let arg0 = format_vector_u8(&metadata); + let arg1 = format_vector_vector_u8(&modules); + + let log_path = build_dir.join("explorer_payload.log"); + let mut file = File::create(&log_path)?; + writeln!(file, "arg0 (vector):\n{}\n", arg0)?; + writeln!(file, "arg1 (vector>):\n{}\n", arg1)?; + + println!("\n----- COPY INTO EXPLORER -----\n"); + println!("arg0 (vector):\n{}", arg0); + println!("\narg1 (vector>):\n{}", arg1); + println!("\n(Log saved to {})", log_path.display()); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_format_vector_u8() { + let input = vec![1, 2, 3, 4, 255]; + let output = format_vector_u8(&input); + assert_eq!(output, "[1,2,3,4,255]"); + } + + #[test] + fn test_format_vector_vector_u8() { + let input = vec![vec![1, 2, 3], vec![4, 5, 6]]; + let output = format_vector_vector_u8(&input); + assert_eq!(output, "[[1,2,3],[4,5,6]]"); + } + + #[test] + fn test_read_bytes_failure() { + let result = read_bytes(Path::new("nonexistent.file")); + assert!(result.is_err()); + } +} diff --git a/util/format-module-bytes/src/main.rs b/util/format-module-bytes/src/main.rs new file mode 100644 index 000000000..298499268 --- /dev/null +++ b/util/format-module-bytes/src/main.rs @@ -0,0 +1,3 @@ +fn main() -> anyhow::Result<()> { + format_module_bytes::run() +}