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