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::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 (original_id, version, _) = get_new_package_obj_from_response(response).context(
65 "Expected a valid published package response but didn't see \
66 one when attempting to update the `Move.lock`.",
67 )?;
68 update_lock_file_with_package_id(
69 context,
70 command,
71 install_dir,
72 lock_file,
73 original_id,
74 version.into(),
75 )
76 .await
77}
78
79pub async fn update_lock_file_with_package_id(
88 context: &WalletContext,
89 command: LockCommand,
90 install_dir: Option<PathBuf>,
91 lock_file: Option<PathBuf>,
92 original_id: ObjectID,
93 version: u64,
94) -> Result<(), anyhow::Error> {
95 let chain_identifier = context
96 .get_client()
97 .await
98 .context("Network issue: couldn't use client to connect to chain when updating Move.lock")?
99 .read_api()
100 .get_chain_identifier()
101 .await
102 .context("Network issue: couldn't determine chain identifier for updating Move.lock")?;
103
104 let Some(lock_file) = lock_file else {
105 bail!(
106 "Expected a `Move.lock` file to exist after publishing \
107 package, but none found. Consider running `iota move build` to \
108 generate the `Move.lock` file in the package directory."
109 )
110 };
111 let install_dir = install_dir.unwrap_or(PathBuf::from("."));
112 let env = context.active_env().context(
113 "Could not resolve environment from active wallet context. \
114 Try ensure `iota client active-env` is valid.",
115 )?;
116
117 let mut lock = LockFile::from(install_dir.clone(), &lock_file)?;
118 match command {
119 LockCommand::Publish => lock_file::schema::update_managed_address(
120 &mut lock,
121 env.alias(),
122 lock_file::schema::ManagedAddressUpdate::Published {
123 chain_id: chain_identifier,
124 original_id: original_id.to_string(),
125 },
126 ),
127 LockCommand::Upgrade => lock_file::schema::update_managed_address(
128 &mut lock,
129 env.alias(),
130 lock_file::schema::ManagedAddressUpdate::Upgraded {
131 latest_id: original_id.to_string(),
132 version,
133 },
134 ),
135 }?;
136 lock.commit(lock_file)?;
137 Ok(())
138}
139
140pub fn set_package_id(
148 package_path: &Path,
149 install_dir: Option<PathBuf>,
150 chain_id: &String,
151 id: AccountAddress,
152) -> Result<Option<AccountAddress>, anyhow::Error> {
153 let lock_file_path = package_path.join(SourcePackageLayout::Lock.path());
154 let Ok(mut lock_file) = File::open(lock_file_path.clone()) else {
155 return Ok(None);
156 };
157 let managed_package = ManagedPackage::read(&mut lock_file)
158 .ok()
159 .and_then(|m| m.into_iter().find(|(_, v)| v.chain_id == *chain_id));
160 let Some((env, v)) = managed_package else {
161 return Ok(None);
162 };
163 let install_dir = install_dir.unwrap_or(PathBuf::from("."));
164 let lock_for_update = LockFile::from(install_dir.clone(), &lock_file_path);
165 let Ok(mut lock_for_update) = lock_for_update else {
166 return Ok(None);
167 };
168 lock_file::schema::set_original_id(&mut lock_for_update, &env, &id.to_canonical_string(true))?;
169 lock_for_update.commit(lock_file_path)?;
170 let id = AccountAddress::from_str(&v.original_published_id)?;
171 Ok(Some(id))
172}
173
174pub fn resolve_published_id(
181 package: &Package,
182 chain_id: Option<String>,
183) -> Result<ObjectID, PublishedAtError> {
184 let published_id_in_manifest = manifest_published_at(package);
187
188 match published_id_in_manifest {
189 Ok(_) | Err(PublishedAtError::NotPresent) => { }
190 Err(e) => {
191 return Err(e);
192 }
193 }
194
195 let lock = package.package_path.join(SourcePackageLayout::Lock.path());
196 let Ok(mut lock_file) = File::open(lock.clone()) else {
197 return published_id_in_manifest;
198 };
199
200 let id_in_lock_for_chain_id =
202 lock_published_at(ManagedPackage::read(&mut lock_file).ok(), chain_id.as_ref());
203
204 match (id_in_lock_for_chain_id, published_id_in_manifest) {
205 (Ok(id_lock), Ok(id_manifest)) if id_lock != id_manifest => {
206 Err(PublishedAtError::Conflict {
207 id_lock,
208 id_manifest,
209 })
210 }
211
212 (Ok(id), _) | (_, Ok(id)) => Ok(id),
213
214 (from_lock, Err(_)) => from_lock,
218 }
219}
220
221fn manifest_published_at(package: &Package) -> Result<ObjectID, PublishedAtError> {
222 let Some(value) = package
223 .source_package
224 .package
225 .custom_properties
226 .get(&Symbol::from(PUBLISHED_AT_MANIFEST_FIELD))
227 else {
228 return Err(PublishedAtError::NotPresent);
229 };
230
231 let id =
232 ObjectID::from_str(value.as_str()).map_err(|_| PublishedAtError::Invalid(value.clone()))?;
233
234 if id == ObjectID::ZERO {
235 Err(PublishedAtError::NotPresent)
236 } else {
237 Ok(id)
238 }
239}
240
241fn lock_published_at(
242 lock: Option<HashMap<String, ManagedPackage>>,
243 chain_id: Option<&String>,
244) -> Result<ObjectID, PublishedAtError> {
245 let (Some(lock), Some(chain_id)) = (lock, chain_id) else {
246 return Err(PublishedAtError::NotPresent);
247 };
248
249 let managed_package = lock
250 .into_values()
251 .find(|v| v.chain_id == *chain_id)
252 .ok_or(PublishedAtError::NotPresent)?;
253
254 let id = ObjectID::from_str(managed_package.latest_published_id.as_str())
255 .map_err(|_| PublishedAtError::Invalid(managed_package.latest_published_id.clone()))?;
256
257 if id == ObjectID::ZERO {
258 Err(PublishedAtError::NotPresent)
259 } else {
260 Ok(id)
261 }
262}