1use 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::{IotaAddress, ObjectID};
16use move_package::{
17 lock_file::{self, LockFile, schema::ManagedPackage},
18 resolution::resolution_graph::Package,
19 source_package::layout::SourcePackageLayout,
20};
21use move_symbol_pool::Symbol;
22
23pub mod system_package_versions;
24
25const PUBLISHED_AT_MANIFEST_FIELD: &str = "published-at";
26
27pub enum LockCommand {
28 Publish,
29 Upgrade,
30}
31
32#[derive(thiserror::Error, Debug, Clone)]
33pub enum PublishedAtError {
34 #[error("The 'published-at' field in Move.toml or Move.lock is invalid: {0:?}")]
35 Invalid(String),
36
37 #[error("The 'published-at' field is not present in Move.toml or Move.lock")]
38 NotPresent,
39
40 #[error(
41 "Conflicting 'published-at' addresses between Move.toml -- {id_manifest} -- and \
42 Move.lock -- {id_lock}"
43 )]
44 Conflict {
45 id_lock: ObjectID,
46 id_manifest: ObjectID,
47 },
48}
49
50pub async fn update_lock_file(
57 context: &WalletContext,
58 command: LockCommand,
59 install_dir: Option<PathBuf>,
60 lock_file: Option<PathBuf>,
61 response: &IotaTransactionBlockResponse,
62) -> Result<(), anyhow::Error> {
63 let object_ref = get_new_package_obj_from_response(response).context(
64 "Expected a valid published package response but didn't see \
65 one when attempting to update the `Move.lock`.",
66 )?;
67 update_lock_file_with_package_id(
68 context,
69 command,
70 install_dir,
71 lock_file,
72 object_ref.object_id,
73 object_ref.version.as_u64(),
74 )
75 .await
76}
77
78pub async fn update_lock_file_with_package_id(
87 context: &WalletContext,
88 command: LockCommand,
89 install_dir: Option<PathBuf>,
90 lock_file: Option<PathBuf>,
91 original_id: ObjectID,
92 version: u64,
93) -> Result<(), anyhow::Error> {
94 let chain_identifier = context
95 .get_client()
96 .await
97 .context("Network issue: couldn't use client to connect to chain when updating Move.lock")?
98 .read_api()
99 .get_chain_identifier()
100 .await
101 .context("Network issue: couldn't determine chain identifier for updating Move.lock")?;
102
103 let Some(lock_file) = lock_file else {
104 bail!(
105 "Expected a `Move.lock` file to exist after publishing \
106 package, but none found. Consider running `iota move build` to \
107 generate the `Move.lock` file in the package directory."
108 )
109 };
110 let install_dir = install_dir.unwrap_or(PathBuf::from("."));
111 let env = context.active_env().context(
112 "Could not resolve environment from active wallet context. \
113 Try ensure `iota client active-env` is valid.",
114 )?;
115
116 let mut lock = LockFile::from(install_dir, &lock_file)?;
117 match command {
118 LockCommand::Publish => lock_file::schema::update_managed_address(
119 &mut lock,
120 env.alias(),
121 lock_file::schema::ManagedAddressUpdate::Published {
122 chain_id: chain_identifier,
123 original_id: original_id.to_string(),
124 },
125 ),
126 LockCommand::Upgrade => lock_file::schema::update_managed_address(
127 &mut lock,
128 env.alias(),
129 lock_file::schema::ManagedAddressUpdate::Upgraded {
130 latest_id: original_id.to_string(),
131 version,
132 },
133 ),
134 }?;
135 lock.commit(lock_file)?;
136 Ok(())
137}
138
139pub fn set_package_id(
147 package_path: &Path,
148 install_dir: Option<PathBuf>,
149 chain_id: &String,
150 id: IotaAddress,
151) -> Result<Option<IotaAddress>, anyhow::Error> {
152 let lock_file_path = package_path.join(SourcePackageLayout::Lock.path());
153 let Ok(mut lock_file) = File::open(lock_file_path.clone()) else {
154 return Ok(None);
155 };
156 let managed_package = ManagedPackage::read(&mut lock_file)
157 .ok()
158 .and_then(|m| m.into_iter().find(|(_, v)| v.chain_id == *chain_id));
159 let Some((env, v)) = managed_package else {
160 return Ok(None);
161 };
162 let install_dir = install_dir.unwrap_or(PathBuf::from("."));
163 let lock_for_update = LockFile::from(install_dir, &lock_file_path);
164 let Ok(mut lock_for_update) = lock_for_update else {
165 return Ok(None);
166 };
167 lock_file::schema::set_original_id(&mut lock_for_update, &env, &id.to_canonical_string(true))?;
168 lock_for_update.commit(lock_file_path)?;
169 let id = IotaAddress::from_str(&v.original_published_id)?;
170 Ok(Some(id))
171}
172
173pub fn resolve_published_id(
180 package: &Package,
181 chain_id: Option<String>,
182) -> Result<ObjectID, PublishedAtError> {
183 let published_id_in_manifest = manifest_published_at(package);
186
187 match published_id_in_manifest {
188 Ok(_) | Err(PublishedAtError::NotPresent) => { }
189 Err(e) => {
190 return Err(e);
191 }
192 }
193
194 let lock = package.package_path.join(SourcePackageLayout::Lock.path());
195 let Ok(mut lock_file) = File::open(lock) else {
196 return published_id_in_manifest;
197 };
198
199 let id_in_lock_for_chain_id =
201 lock_published_at(ManagedPackage::read(&mut lock_file).ok(), chain_id.as_ref());
202
203 match (id_in_lock_for_chain_id, published_id_in_manifest) {
204 (Ok(id_lock), Ok(id_manifest)) if id_lock != id_manifest => {
205 Err(PublishedAtError::Conflict {
206 id_lock,
207 id_manifest,
208 })
209 }
210
211 (Ok(id), _) | (_, Ok(id)) => Ok(id),
212
213 (from_lock, Err(_)) => from_lock,
217 }
218}
219
220fn manifest_published_at(package: &Package) -> Result<ObjectID, PublishedAtError> {
221 let Some(value) = package
222 .source_package
223 .package
224 .custom_properties
225 .get(&Symbol::from(PUBLISHED_AT_MANIFEST_FIELD))
226 else {
227 return Err(PublishedAtError::NotPresent);
228 };
229
230 let id =
231 ObjectID::from_str(value.as_str()).map_err(|_| PublishedAtError::Invalid(value.clone()))?;
232
233 if id == ObjectID::ZERO {
234 Err(PublishedAtError::NotPresent)
235 } else {
236 Ok(id)
237 }
238}
239
240fn lock_published_at(
241 lock: Option<HashMap<String, ManagedPackage>>,
242 chain_id: Option<&String>,
243) -> Result<ObjectID, PublishedAtError> {
244 let (Some(lock), Some(chain_id)) = (lock, chain_id) else {
245 return Err(PublishedAtError::NotPresent);
246 };
247
248 let managed_package = lock
249 .into_values()
250 .find(|v| v.chain_id == *chain_id)
251 .ok_or(PublishedAtError::NotPresent)?;
252
253 let id = ObjectID::from_str(managed_package.latest_published_id.as_str())
254 .map_err(|_| PublishedAtError::Invalid(managed_package.latest_published_id.clone()))?;
255
256 if id == ObjectID::ZERO {
257 Err(PublishedAtError::NotPresent)
258 } else {
259 Ok(id)
260 }
261}