Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
16 changes: 5 additions & 11 deletions crates/iota-sdk-ffi/src/transaction_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ use std::{
time::Duration,
};

use iota_transaction_builder::MovePackageData;
use iota_types::Input;
use iota_types::{Input, MovePackageData};

use crate::{
crypto::simple::SimpleKeypair,
Expand Down Expand Up @@ -266,11 +265,10 @@ impl TransactionBuilder {
) -> Arc<Self> {
self.write(|builder| {
builder
.publish(MovePackageData {
.publish(MovePackageData::new(
modules,
dependencies: dependencies.into_iter().map(|o| **o).collect(),
digest: None,
})
dependencies.into_iter().map(|o| **o).collect(),
))
.upgrade_cap(upgrade_cap_name);
});
self
Expand Down Expand Up @@ -301,11 +299,7 @@ impl TransactionBuilder {
.upgrade(
**package,
ticket,
MovePackageData {
modules,
dependencies: dependencies.into_iter().map(|o| **o).collect(),
digest: None,
},
MovePackageData::new(modules, dependencies.into_iter().map(|o| **o).collect()),
)
.name(name);
});
Expand Down
2 changes: 2 additions & 0 deletions crates/iota-sdk-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ mod gas;
pub mod iota_names;
mod object;
mod object_id;
mod package_data;
mod transaction;
mod type_tag;
mod u256;
Expand Down Expand Up @@ -159,6 +160,7 @@ pub use object::{
TypeOrigin, UpgradeInfo, Version,
};
pub use object_id::ObjectId;
pub use package_data::MovePackageData;
#[cfg(feature = "serde")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
pub(crate) use transaction::SignedTransactionWithIntentMessage;
Expand Down
131 changes: 131 additions & 0 deletions crates/iota-sdk-types/src/package_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2025 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use crate::{Digest, ObjectId};

/// Type corresponding to the output of `iota move build
/// --dump-bytecode-as-base64`
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MovePackageData {
/// The package modules as a series of bytes
#[cfg_attr(feature = "serde", serde(with = "serialization::modules"))]
pub modules: Vec<Vec<u8>>,
/// The package dependencies, specified by their object IDs.
pub dependencies: Vec<ObjectId>,
/// The package digest.
#[cfg_attr(feature = "serde", serde(with = "serialization::digest"))]
pub digest: Digest,
}

impl MovePackageData {
#[cfg(feature = "hash")]
pub fn new(modules: Vec<Vec<u8>>, dependencies: Vec<ObjectId>) -> Self {
use crate::hash::Hasher;
let mut components = dependencies
.iter()
.map(|o| o.into_inner())
.chain(modules.iter().map(|module| {
let mut hasher = Hasher::new();
hasher.update(module);
hasher.finalize().into_inner()
}))
.collect::<Vec<_>>();

// Sort so the order of the modules and the order of the dependencies
// does not matter.
components.sort();

let mut hasher = Hasher::new();
for c in components {
hasher.update(c);
}

Self {
modules,
dependencies,
digest: Digest::from(hasher.finalize().into_inner()),
}
}
}

#[cfg(feature = "serde")]
mod serialization {
use base64ct::Encoding;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use super::*;

impl MovePackageData {
pub fn to_base64(&self) -> String {
base64ct::Base64::encode_string(&bcs::to_bytes(self).expect("bcs encoding failed"))
}

pub fn from_base64(base64: &str) -> Result<Self, bcs::Error> {
use serde::de::Error;
bcs::from_bytes(&base64ct::Base64::decode_vec(base64).map_err(bcs::Error::custom)?)
}
}

pub mod modules {
use super::*;

pub fn serialize<S: Serializer>(
value: &Vec<Vec<u8>>,
serializer: S,
) -> Result<S::Ok, S::Error> {
value
.iter()
.map(|v| base64ct::Base64::encode_string(v))
.collect::<Vec<_>>()
.serialize(serializer)
}

pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
let bcs = Vec::<String>::deserialize(deserializer)?;
bcs.into_iter()
.map(|s| base64ct::Base64::decode_vec(&s).map_err(serde::de::Error::custom))
.collect()
}
}

pub mod digest {
use super::*;

pub fn serialize<S: Serializer>(value: &Digest, serializer: S) -> Result<S::Ok, S::Error> {
value.as_bytes().serialize(serializer)
}

pub fn deserialize<'de, D>(deserializer: D) -> Result<Digest, D::Error>
where
D: Deserializer<'de>,
{
let bytes = Vec::<u8>::deserialize(deserializer)?;
Digest::from_bytes(bytes).map_err(|e| serde::de::Error::custom(format!("{e}")))
}
}
}

#[cfg(test)]
mod tests {
use super::*;

const PACKAGE: &str = r#"{"modules":["oRzrCwYAAAAKAQAIAggUAxw+BFoGBWBBB6EBwQEI4gJACqIDGgy8A5cBDdMEBgAKAQ0BEwEUAAIMAAABCAAAAAgAAQQEAAMDAgAACAABAAAJAgMAABACAwAAEgQDAAAMBQYAAAYHAQAAEQgBAAAFCQoAAQsACwACDg8BAQwCEw8BAQgDDwwNAAoOCgYJBgEHCAQAAQYIAAEDAQYIAQQHCAEDAwcIBAEIAAQDAwUHCAQDCAAFBwgEAgMHCAQBCAIBCAMBBggEAQUBCAECCQAFBkNvbmZpZwVGb3JnZQVTd29yZAlUeENvbnRleHQDVUlEDWNyZWF0ZV9jb25maWcMY3JlYXRlX3N3b3JkAmlkBGluaXQFbWFnaWMJbXlfbW9kdWxlA25ldwluZXdfc3dvcmQGb2JqZWN0D3B1YmxpY190cmFuc2ZlcgZzZW5kZXIIc3RyZW5ndGgOc3dvcmRfdHJhbnNmZXIOc3dvcmRzX2NyZWF0ZWQIdHJhbnNmZXIKdHhfY29udGV4dAV2YWx1ZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAgMHCAMJAxADAQICBwgDEgMCAgIHCAMVAwAAAAABCQoAEQgGAAAAAAAAAAASAQsALhELOAACAQEAAAEECwAQABQCAgEAAAEECwAQARQCAwEAAAEECwAQAhQCBAEAAAEOCgAQAhQGAQAAAAAAAAAWCwAPAhULAxEICwELAhIAAgUBAAABCAsDEQgLAAsBEgALAjgBAgYBAAABBAsACwE4AgIHAQAAAQULAREICwASAgIAAQACAQEA"],"dependencies":["0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000001"],"digest":[246,127,102,77,186,19,68,12,161,181,56,248,210,0,91,211,245,251,165,152,0,197,250,135,171,37,177,240,133,76,122,124]}"#;

#[test]
fn test_serialization() {
let package: MovePackageData = serde_json::from_str(PACKAGE).unwrap();
let new_json = serde_json::to_string(&package).unwrap();
assert_eq!(new_json, PACKAGE);
}

#[test]
fn test_digest() {
let json_package: MovePackageData = serde_json::from_str(PACKAGE).unwrap();
let package = MovePackageData::new(json_package.modules, json_package.dependencies);
assert_eq!(json_package.digest, package.digest);
}
}
85 changes: 40 additions & 45 deletions crates/iota-transaction-builder/src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ use iota_graphql_client::{
query_types::{ObjectFilter, ObjectRef, TransactionMetadata},
};
use iota_types::{
Address, GasPayment, Identifier, ObjectId, ObjectReference, Owner, ProgrammableTransaction,
StructTag, Transaction, TransactionEffects, TransactionExpiration, TransactionV1, TypeTag,
Address, GasPayment, Identifier, MovePackageData, ObjectId, ObjectReference, Owner,
ProgrammableTransaction, StructTag, Transaction, TransactionEffects, TransactionExpiration,
TransactionV1, TypeTag,
};
use reqwest::Url;
use serde::Serialize;
Expand All @@ -29,7 +30,6 @@ use crate::{
ptb_arguments::PTBArgumentList,
},
error::Error,
publish_type::PublishType,
types::{MoveType, MoveTypes},
unresolved::{
Argument, Command, Input, InputId, InputKind, MakeMoveVector, MergeCoins, MoveCall,
Expand Down Expand Up @@ -309,29 +309,6 @@ impl<C, L> TransactionBuilder<C, L> {
self.data.get_named_result(name)
}

/// Send IOTA to a recipient address.
pub fn send_iota<T: PTBArgument>(
&mut self,
recipient: Address,
amount: impl Into<Option<T>>,
) -> &mut TransactionBuilder<C> {
let rec_arg = self.pure(recipient);
let coin_arg = if let Some(amount) = amount.into() {
let amt_arg = self.apply_argument(amount);
self.command(Command::SplitCoins(SplitCoins {
coin: Argument::Gas,
amounts: vec![amt_arg],
}))
} else {
Argument::Gas
};
self.command(Command::TransferObjects(TransferObjects {
objects: vec![coin_arg],
address: rec_arg,
}));
self.reset()
}

/// Begin building a move call.
pub fn move_call(
&mut self,
Expand All @@ -350,18 +327,6 @@ impl<C, L> TransactionBuilder<C, L> {
})
}

/// Publish a move package.
pub fn publish(&mut self, kind: impl Into<PublishType>) -> &mut TransactionBuilder<C, Publish> {
let module = match kind.into() {
PublishType::Path(_path) => todo!("load the package from the path"),
PublishType::Compiled(m) => m,
};
self.cmd_state_change(Publish {
modules: module.modules,
dependencies: module.dependencies,
})
}

/// Transfer objects to a recipient address.
pub fn transfer_objects<U: PTBArgumentList>(
&mut self,
Expand All @@ -377,6 +342,29 @@ impl<C, L> TransactionBuilder<C, L> {
self.reset()
}

/// Send IOTA to a recipient address.
pub fn send_iota<T: PTBArgument>(
&mut self,
recipient: Address,
amount: impl Into<Option<T>>,
) -> &mut TransactionBuilder<C> {
let rec_arg = self.pure(recipient);
let coin_arg = if let Some(amount) = amount.into() {
let amt_arg = self.apply_argument(amount);
self.command(Command::SplitCoins(SplitCoins {
coin: Argument::Gas,
amounts: vec![amt_arg],
}))
} else {
Argument::Gas
};
self.command(Command::TransferObjects(TransferObjects {
objects: vec![coin_arg],
address: rec_arg,
}));
self.reset()
}

/// Transfer some coins to a recipient address. If multiple coins are
/// provided then they will be merged.
pub fn send_coins<T: PTBArgumentList, U: PTBArgument>(
Expand Down Expand Up @@ -448,21 +436,28 @@ impl<C, L> TransactionBuilder<C, L> {
self.cmd_state_change(SplitCoins { coin, amounts })
}

/// Publish a move package.
pub fn publish(
&mut self,
package_data: MovePackageData,
) -> &mut TransactionBuilder<C, Publish> {
self.cmd_state_change(Publish {
modules: package_data.modules,
dependencies: package_data.dependencies,
})
}

/// Upgrade a move package.
pub fn upgrade<U: PTBArgument>(
&mut self,
package_id: ObjectId,
upgrade_cap: U,
kind: impl Into<PublishType>,
package_data: MovePackageData,
) -> &mut TransactionBuilder<C, Upgrade> {
let module = match kind.into() {
PublishType::Path(_path) => todo!("load the package from the path"),
PublishType::Compiled(m) => m,
};
let ticket = self.apply_argument(upgrade_cap);
self.cmd_state_change(Upgrade {
modules: module.modules,
dependencies: module.dependencies,
modules: package_data.modules,
dependencies: package_data.dependencies,
package: package_id,
ticket,
})
Expand Down
14 changes: 4 additions & 10 deletions crates/iota-transaction-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

pub mod builder;
pub mod error;
pub(crate) mod publish_type;
pub mod types;
#[allow(missing_docs)]
pub mod unresolved;
Expand All @@ -18,7 +17,6 @@ pub use self::{
TransactionBuilder,
ptb_arguments::{PTBArgument, PTBArgumentList, Receiving, Shared, SharedMut, res},
},
publish_type::MovePackageData,
types::PureBytes,
};

Expand All @@ -32,11 +30,11 @@ mod tests {
pagination::PaginationFilter,
};
use iota_types::{
Address, Digest, ExecutionStatus, IdOperation, ObjectId, ObjectReference, ObjectType,
TransactionEffects,
Address, Digest, ExecutionStatus, IdOperation, MovePackageData, ObjectId, ObjectReference,
ObjectType, TransactionEffects,
};

use crate::{TransactionBuilder, error::Error, publish_type::MovePackageData, res};
use crate::{TransactionBuilder, error::Error, res};

/// This is used to read the json file that contains the modules/deps/digest
/// generated with iota move build --dump-bytecode-as-base64 on the
Expand Down Expand Up @@ -331,11 +329,7 @@ mod tests {

// we need this ticket to authorize the upgrade
tx.move_call(Address::FRAMEWORK, "package", "authorize_upgrade")
.arguments((
upgrade_cap.unwrap(),
0u8,
updated_package.digest.as_ref().unwrap(),
))
.arguments((upgrade_cap.unwrap(), 0u8, updated_package.digest))
.name("ticket");
// now we can upgrade the package
let receipt = tx
Expand Down
Loading
Loading