Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ members = [
"util/tracing",
"util/syncador",
"util/collections",
"util/format-module-bytes",
"util/whitelist",
"networks/movement/*",
"benches/*",
Expand Down
10 changes: 10 additions & 0 deletions util/format-module-bytes/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
6 changes: 6 additions & 0 deletions util/format-module-bytes/Move.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "hello"
version = "0.0.1"

[addresses]
hello = "_"
64 changes: 64 additions & 0 deletions util/format-module-bytes/README.md
Original file line number Diff line number Diff line change
@@ -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<u8>,
code: vector<vector<u8>>
)
```

## 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<u8>)
* `arg1` (vector\<vector<u8>>)

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<u8> array (surrounded by brackets)
* **arg1**: the full vector\<vector<u8>> 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:

<img width="1205" alt="image" src="https://github.com/user-attachments/assets/312c2c17-e164-45d8-a7ca-c379ef0f21ed" />
5 changes: 5 additions & 0 deletions util/format-module-bytes/sources/hello.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module hello::hello {
public entry fun hi() {
// no-op
}
}
118 changes: 118 additions & 0 deletions util/format-module-bytes/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<u8>> {
Ok(fs::read(path)?)
}

fn format_vector_u8(data: &[u8]) -> String {
format!("[{}]", data.iter().map(|b| b.to_string()).collect::<Vec<_>>().join(","))
}

fn format_vector_vector_u8(data: &[Vec<u8>]) -> String {
serde_json::to_string(data).expect("json encode failed")
}

fn address_from_config() -> Result<String> {
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<u8>):\n{}\n", arg0)?;
writeln!(file, "arg1 (vector<vector<u8>>):\n{}\n", arg1)?;

println!("\n----- COPY INTO EXPLORER -----\n");
println!("arg0 (vector<u8>):\n{}", arg0);
println!("\narg1 (vector<vector<u8>>):\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());
}
}
3 changes: 3 additions & 0 deletions util/format-module-bytes/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() -> anyhow::Result<()> {
format_module_bytes::run()
}
Loading