sui_adapter_v2/programmable_transactions/
linkage_view.rs1use std::{
5 cell::RefCell,
6 collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
7 str::FromStr,
8};
9
10use crate::execution_value::SuiResolver;
11use move_core_types::{
12 account_address::AccountAddress,
13 identifier::{IdentStr, Identifier},
14 language_storage::ModuleId,
15 resolver::{LinkageResolver, ModuleResolver},
16};
17use sui_types::storage::{get_module, PackageObject};
18use sui_types::{
19 base_types::ObjectID,
20 error::{ExecutionError, SuiError, SuiResult},
21 move_package::{MovePackage, TypeOrigin, UpgradeInfo},
22 storage::BackingPackageStore,
23};
24
25pub struct LinkageView<'state> {
28 resolver: Box<dyn SuiResolver + 'state>,
30 linkage_info: Option<LinkageInfo>,
32 type_origin_cache: RefCell<HashMap<ModuleId, HashMap<Identifier, AccountAddress>>>,
40 past_contexts: RefCell<HashSet<ObjectID>>,
43}
44
45#[derive(Debug)]
46pub struct LinkageInfo {
47 storage_id: AccountAddress,
48 runtime_id: AccountAddress,
49 link_table: BTreeMap<ObjectID, UpgradeInfo>,
50}
51
52pub struct SavedLinkage(LinkageInfo);
53
54impl<'state> LinkageView<'state> {
55 pub fn new(resolver: Box<dyn SuiResolver + 'state>) -> Self {
56 Self {
57 resolver,
58 linkage_info: None,
59 type_origin_cache: RefCell::new(HashMap::new()),
60 past_contexts: RefCell::new(HashSet::new()),
61 }
62 }
63
64 pub fn reset_linkage(&mut self) {
65 self.linkage_info = None;
66 }
67
68 pub fn has_linkage(&self, context: ObjectID) -> bool {
71 self.linkage_info
72 .as_ref()
73 .is_some_and(|l| l.storage_id == *context)
74 }
75
76 pub fn steal_linkage(&mut self) -> Option<SavedLinkage> {
78 Some(SavedLinkage(self.linkage_info.take()?))
79 }
80
81 pub fn restore_linkage(&mut self, saved: Option<SavedLinkage>) -> Result<(), ExecutionError> {
83 let Some(SavedLinkage(saved)) = saved else {
84 return Ok(());
85 };
86
87 if let Some(existing) = &self.linkage_info {
88 invariant_violation!(
89 "Attempt to overwrite linkage by restoring: {saved:#?} \
90 Existing linkage: {existing:#?}",
91 )
92 }
93
94 self.linkage_info = Some(saved);
97 Ok(())
98 }
99
100 pub fn set_linkage(&mut self, context: &MovePackage) -> Result<AccountAddress, ExecutionError> {
104 if let Some(existing) = &self.linkage_info {
105 invariant_violation!(
106 "Attempt to overwrite linkage info with context from {}. \
107 Existing linkage: {existing:#?}",
108 context.id(),
109 )
110 }
111
112 let linkage = LinkageInfo::from(context);
113 let storage_id = context.id();
114 let runtime_id = linkage.runtime_id;
115 self.linkage_info = Some(linkage);
116
117 if !self.past_contexts.borrow_mut().insert(storage_id) {
118 return Ok(runtime_id);
119 }
120
121 for TypeOrigin {
125 module_name,
126 datatype_name,
127 package: defining_id,
128 } in context.type_origin_table()
129 {
130 let Ok(module_name) = Identifier::from_str(module_name) else {
131 invariant_violation!("Module name isn't an identifier: {module_name}");
132 };
133
134 let Ok(struct_name) = Identifier::from_str(datatype_name) else {
135 invariant_violation!("Struct name isn't an identifier: {datatype_name}");
136 };
137
138 let runtime_id = ModuleId::new(runtime_id, module_name);
139 self.add_type_origin(runtime_id, struct_name, *defining_id)?;
140 }
141
142 Ok(runtime_id)
143 }
144
145 pub fn original_package_id(&self) -> Option<AccountAddress> {
146 Some(self.linkage_info.as_ref()?.runtime_id)
147 }
148
149 fn get_cached_type_origin(
150 &self,
151 runtime_id: &ModuleId,
152 struct_: &IdentStr,
153 ) -> Option<AccountAddress> {
154 self.type_origin_cache
155 .borrow()
156 .get(runtime_id)?
157 .get(struct_)
158 .cloned()
159 }
160
161 fn add_type_origin(
162 &self,
163 runtime_id: ModuleId,
164 struct_: Identifier,
165 defining_id: ObjectID,
166 ) -> Result<(), ExecutionError> {
167 let mut cache = self.type_origin_cache.borrow_mut();
168 let module_cache = cache.entry(runtime_id.clone()).or_default();
169
170 match module_cache.entry(struct_) {
171 Entry::Vacant(entry) => {
172 entry.insert(*defining_id);
173 }
174
175 Entry::Occupied(entry) => {
176 if entry.get() != &*defining_id {
177 invariant_violation!(
178 "Conflicting defining ID for {}::{}: {} and {}",
179 runtime_id,
180 entry.key(),
181 defining_id,
182 entry.get(),
183 );
184 }
185 }
186 }
187
188 Ok(())
189 }
190
191 pub(crate) fn link_context(&self) -> AccountAddress {
192 self.linkage_info
193 .as_ref()
194 .map_or(AccountAddress::ZERO, |l| l.storage_id)
195 }
196
197 pub(crate) fn relocate(&self, module_id: &ModuleId) -> Result<ModuleId, SuiError> {
198 let Some(linkage) = &self.linkage_info else {
199 invariant_violation!("No linkage context set while relocating {module_id}.")
200 };
201
202 if module_id.address() == &linkage.runtime_id {
205 return Ok(ModuleId::new(
206 linkage.storage_id,
207 module_id.name().to_owned(),
208 ));
209 }
210
211 let runtime_id = ObjectID::from_address(*module_id.address());
212 let Some(upgrade) = linkage.link_table.get(&runtime_id) else {
213 invariant_violation!(
214 "Missing linkage for {runtime_id} in context {}, runtime_id is {}",
215 linkage.storage_id,
216 linkage.runtime_id
217 );
218 };
219
220 Ok(ModuleId::new(
221 upgrade.upgraded_id.into(),
222 module_id.name().to_owned(),
223 ))
224 }
225
226 pub(crate) fn defining_module(
227 &self,
228 runtime_id: &ModuleId,
229 struct_: &IdentStr,
230 ) -> Result<ModuleId, SuiError> {
231 if self.linkage_info.is_none() {
232 invariant_violation!(
233 "No linkage context set for defining module query on {runtime_id}::{struct_}."
234 )
235 }
236
237 if let Some(cached) = self.get_cached_type_origin(runtime_id, struct_) {
238 return Ok(ModuleId::new(cached, runtime_id.name().to_owned()));
239 }
240
241 let storage_id = ObjectID::from(*self.relocate(runtime_id)?.address());
242 let Some(package) = self.resolver.get_package_object(&storage_id)? else {
243 invariant_violation!("Missing dependent package in store: {storage_id}",)
244 };
245
246 for TypeOrigin {
247 module_name,
248 datatype_name,
249 package,
250 } in package.move_package().type_origin_table()
251 {
252 if module_name == runtime_id.name().as_str() && datatype_name == struct_.as_str() {
253 self.add_type_origin(runtime_id.clone(), struct_.to_owned(), *package)?;
254 return Ok(ModuleId::new(**package, runtime_id.name().to_owned()));
255 }
256 }
257
258 invariant_violation!(
259 "{runtime_id}::{struct_} not found in type origin table in {storage_id} (v{})",
260 package.move_package().version(),
261 )
262 }
263}
264
265impl From<&MovePackage> for LinkageInfo {
266 fn from(package: &MovePackage) -> Self {
267 Self {
268 storage_id: package.id().into(),
269 runtime_id: package.original_package_id().into(),
270 link_table: package.linkage_table().clone(),
271 }
272 }
273}
274
275impl LinkageResolver for LinkageView<'_> {
276 type Error = SuiError;
277
278 fn link_context(&self) -> AccountAddress {
279 LinkageView::link_context(self)
280 }
281
282 fn relocate(&self, module_id: &ModuleId) -> Result<ModuleId, Self::Error> {
283 LinkageView::relocate(self, module_id)
284 }
285
286 fn defining_module(
287 &self,
288 runtime_id: &ModuleId,
289 struct_: &IdentStr,
290 ) -> Result<ModuleId, Self::Error> {
291 LinkageView::defining_module(self, runtime_id, struct_)
292 }
293}
294
295impl ModuleResolver for LinkageView<'_> {
298 type Error = SuiError;
299
300 fn get_module(&self, id: &ModuleId) -> Result<Option<Vec<u8>>, Self::Error> {
301 get_module(self, id)
302 }
303}
304
305impl BackingPackageStore for LinkageView<'_> {
306 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
307 self.resolver.get_package_object(package_id)
308 }
309}