iota_package_management/
lib.rs1use std::{
6 collections::HashMap,
7 fs::File,
8 path::{Path, PathBuf},
9 str::FromStr,
10};
11
12use anyhow::{Context, bail};
13use iota_json_rpc_types::{IotaTransactionBlockResponse, get_new_package_obj_from_response};
14use iota_sdk::wallet_context::WalletContext;
15use iota_types::base_types::ObjectID;
16use move_core_types::account_address::AccountAddress;
17use move_package::{
18 lock_file::{self, LockFile, schema::ManagedPackage},
19 resolution::resolution_graph::Package,
20 source_package::layout::SourcePackageLayout,
21};
22use move_symbol_pool::Symbol;
23
24pub mod system_package_versions;
25
26const PUBLISHED_AT_MANIFEST_FIELD: &str = "published-at";
27
28pub enum LockCommand {
29 Publish,
30 Upgrade,
31}
32
33#[derive(thiserror::Error, Debug, Clone)]
34pub enum PublishedAtError {
35 #[error("The 'published-at' field in Move.toml or Move.lock is invalid: {0:?}")]
36 Invalid(String),
37
38 #[error("The 'published-at' field is not present in Move.toml or Move.lock")]
39 NotPresent,
40
41 #[error(
42 "Conflicting 'published-at' addresses between Move.toml -- {id_manifest} -- and \
43 Move.lock -- {id_lock}"
44 )]
45 Conflict {
46 id_lock: ObjectID,
47 id_manifest: ObjectID,
48 },
49}
50
51pub async fn update_lock_file(
58 context: &WalletContext,
59 command: LockCommand,
60 install_dir: Option<PathBuf>,
61 lock_file: Option<PathBuf>,
62 response: &IotaTransactionBlockResponse,
63) -> Result<(), anyhow::Error> {
64 let chain_identifier = context
65 .get_client()
66 .await
67 .context("Network issue: couldn't use client to connect to chain when updating Move.lock")?
68 .read_api()
69 .get_chain_identifier()
70 .await
71 .context("Network issue: couldn't determine chain identifier for updating Move.lock")?;
72
73 let (original_id, version, _) = get_new_package_obj_from_response(response).context(
74 "Expected a valid published package response but didn't see \
75 one when attempting to update the `Move.lock`.",
76 )?;
77 let Some(lock_file) = lock_file else {
78 bail!(
79 "Expected a `Move.lock` file to exist after publishing \
80 package, but none found. Consider running `iota move build` to \
81 generate the `Move.lock` file in the package directory."
82 )
83 };
84 let install_dir = install_dir.unwrap_or(PathBuf::from("."));
85 let env = context.active_env().context(
86 "Could not resolve environment from active wallet context. \
87 Try ensure `iota client active-env` is valid.",
88 )?;
89
90 let mut lock = LockFile::from(install_dir.clone(), &lock_file)?;
91 match command {
92 LockCommand::Publish => lock_file::schema::update_managed_address(
93 &mut lock,
94 env.alias(),
95 lock_file::schema::ManagedAddressUpdate::Published {
96 chain_id: chain_identifier,
97 original_id: original_id.to_string(),
98 },
99 ),
100 LockCommand::Upgrade => lock_file::schema::update_managed_address(
101 &mut lock,
102 env.alias(),
103 lock_file::schema::ManagedAddressUpdate::Upgraded {
104 latest_id: original_id.to_string(),
105 version: version.into(),
106 },
107 ),
108 }?;
109 lock.commit(lock_file)?;
110 Ok(())
111}
112
113pub fn set_package_id(
121 package_path: &Path,
122 install_dir: Option<PathBuf>,
123 chain_id: &String,
124 id: AccountAddress,
125) -> Result<Option<AccountAddress>, anyhow::Error> {
126 let lock_file_path = package_path.join(SourcePackageLayout::Lock.path());
127 let Ok(mut lock_file) = File::open(lock_file_path.clone()) else {
128 return Ok(None);
129 };
130 let managed_package = ManagedPackage::read(&mut lock_file)
131 .ok()
132 .and_then(|m| m.into_iter().find(|(_, v)| v.chain_id == *chain_id));
133 let Some((env, v)) = managed_package else {
134 return Ok(None);
135 };
136 let install_dir = install_dir.unwrap_or(PathBuf::from("."));
137 let lock_for_update = LockFile::from(install_dir.clone(), &lock_file_path);
138 let Ok(mut lock_for_update) = lock_for_update else {
139 return Ok(None);
140 };
141 lock_file::schema::set_original_id(&mut lock_for_update, &env, &id.to_canonical_string(true))?;
142 lock_for_update.commit(lock_file_path)?;
143 let id = AccountAddress::from_str(&v.original_published_id)?;
144 Ok(Some(id))
145}
146
147pub fn resolve_published_id(
154 package: &Package,
155 chain_id: Option<String>,
156) -> Result<ObjectID, PublishedAtError> {
157 let published_id_in_manifest = manifest_published_at(package);
160
161 match published_id_in_manifest {
162 Ok(_) | Err(PublishedAtError::NotPresent) => { }
163 Err(e) => {
164 return Err(e);
165 }
166 }
167
168 let lock = package.package_path.join(SourcePackageLayout::Lock.path());
169 let Ok(mut lock_file) = File::open(lock.clone()) else {
170 return published_id_in_manifest;
171 };
172
173 let id_in_lock_for_chain_id =
175 lock_published_at(ManagedPackage::read(&mut lock_file).ok(), chain_id.as_ref());
176
177 match (id_in_lock_for_chain_id, published_id_in_manifest) {
178 (Ok(id_lock), Ok(id_manifest)) if id_lock != id_manifest => {
179 Err(PublishedAtError::Conflict {
180 id_lock,
181 id_manifest,
182 })
183 }
184
185 (Ok(id), _) | (_, Ok(id)) => Ok(id),
186
187 (from_lock, Err(_)) => from_lock,
191 }
192}
193
194fn manifest_published_at(package: &Package) -> Result<ObjectID, PublishedAtError> {
195 let Some(value) = package
196 .source_package
197 .package
198 .custom_properties
199 .get(&Symbol::from(PUBLISHED_AT_MANIFEST_FIELD))
200 else {
201 return Err(PublishedAtError::NotPresent);
202 };
203
204 let id =
205 ObjectID::from_str(value.as_str()).map_err(|_| PublishedAtError::Invalid(value.clone()))?;
206
207 if id == ObjectID::ZERO {
208 Err(PublishedAtError::NotPresent)
209 } else {
210 Ok(id)
211 }
212}
213
214fn lock_published_at(
215 lock: Option<HashMap<String, ManagedPackage>>,
216 chain_id: Option<&String>,
217) -> Result<ObjectID, PublishedAtError> {
218 let (Some(lock), Some(chain_id)) = (lock, chain_id) else {
219 return Err(PublishedAtError::NotPresent);
220 };
221
222 let managed_package = lock
223 .into_values()
224 .find(|v| v.chain_id == *chain_id)
225 .ok_or(PublishedAtError::NotPresent)?;
226
227 let id = ObjectID::from_str(managed_package.latest_published_id.as_str())
228 .map_err(|_| PublishedAtError::Invalid(managed_package.latest_published_id.clone()))?;
229
230 if id == ObjectID::ZERO {
231 Err(PublishedAtError::NotPresent)
232 } else {
233 Ok(id)
234 }
235}