Skip to content

Commit 225bf69

Browse files
committed
resolve publish issues
1 parent 368dde8 commit 225bf69

16 files changed

Lines changed: 297 additions & 114 deletions

File tree

crates/movy-replay/src/env.rs

Lines changed: 64 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,44 @@ impl<
128128
self.install_std(true)
129129
}
130130

131+
pub async fn fetch_package_at_address(
132+
&self,
133+
package_id: MoveAddress,
134+
rpc: &GraphQlDatabase,
135+
) -> Result<BTreeSet<ObjectID>, MovyError> {
136+
let mut out = BTreeSet::new();
137+
if let Some(object) = rpc.get_object(package_id.into()).await? {
138+
tracing::info!(
139+
"Fetching package {}:{} from chain",
140+
package_id,
141+
object.version()
142+
);
143+
let pkg = object
144+
.data
145+
.try_as_package()
146+
.ok_or_else(|| eyre!("Expected package data for {}", object.id()))?;
147+
148+
for (id, upgrade_info) in pkg.linkage_table() {
149+
if self.db.get_object(&upgrade_info.upgraded_id).is_none() {
150+
tracing::info!(
151+
"Fetching ugprade cap {}:{} from chain",
152+
upgrade_info.upgraded_id,
153+
upgrade_info.upgraded_version
154+
);
155+
self.deploy_object_id(upgrade_info.upgraded_id.into(), rpc)
156+
.await?;
157+
} else {
158+
tracing::debug!("Upgrade info {:?} already exists", upgrade_info);
159+
}
160+
out.insert(*id);
161+
}
162+
self.db.commit_single_object(object)?;
163+
} else {
164+
return Err(eyre!("package {} not found", package_id).into());
165+
}
166+
Ok(out)
167+
}
168+
131169
pub async fn load_local(
132170
&self,
133171
path: &Path,
@@ -154,31 +192,39 @@ impl<
154192
let compiled_result = compiled_result.movy_mock()?;
155193

156194
// Deploy onchain deps or deps used by immediate dependencies
157-
for dep in
158-
abi_result
159-
.dependencies()
160-
.iter()
161-
.copied()
162-
.chain(abi_result.all_modules_iter().flat_map(|t| {
163-
t.immediate_dependencies()
164-
.into_iter()
165-
.map(|im| (*im.address()).into())
166-
}))
167-
{
195+
let mut packages_to_deploy = abi_result
196+
.dependencies()
197+
.iter()
198+
.copied()
199+
.chain(abi_result.all_modules_iter().flat_map(|t| {
200+
t.immediate_dependencies()
201+
.into_iter()
202+
.map(|im| (*im.address()).into())
203+
}))
204+
.collect::<BTreeSet<_>>();
205+
while let Some(dep) = packages_to_deploy.pop_last() {
168206
let dep = AccountAddress::from(dep);
169207

170-
if dep != AccountAddress::ZERO && self.db.get_object(&dep.into()).is_none() {
208+
if dep != AccountAddress::ZERO
209+
&& dep != compiled_result.package_id.into()
210+
&& self.db.get_object(&dep.into()).is_none()
211+
{
171212
tracing::info!(
172213
"Dependency {} not found in our db for {}, trying to fetch it from onchain",
173214
dep,
174215
path.display()
175216
);
176-
if let Err(e) = self.deploy_package_at_address(dep.into(), rpc).await {
177-
tracing::warn!(
178-
"Fail to add the object {} due to {}, this might be fine though.",
179-
dep,
180-
e
181-
);
217+
match self.fetch_package_at_address(dep.into(), rpc).await {
218+
Ok(nexts) => {
219+
packages_to_deploy.extend(nexts.into_iter());
220+
}
221+
Err(e) => {
222+
tracing::warn!(
223+
"Fail to add the object {} due to {}, this might be fine though.",
224+
dep,
225+
e
226+
);
227+
}
182228
}
183229
}
184230
}
@@ -321,35 +367,6 @@ impl<
321367
Ok(())
322368
}
323369

324-
pub async fn deploy_package_at_address(
325-
&self,
326-
package_id: MoveAddress,
327-
rpc: &GraphQlDatabase,
328-
) -> Result<(), MovyError> {
329-
tracing::info!("Fetching package {} from chain", package_id);
330-
if let Some(object) = rpc.get_object(package_id.into()).await? {
331-
let pkg = object
332-
.data
333-
.try_as_package()
334-
.ok_or_else(|| eyre!("Expected package data for {}", object.id()))?;
335-
336-
for upgrade_info in pkg.linkage_table().values() {
337-
if self.db.get_object(&upgrade_info.upgraded_id).is_none() {
338-
tracing::info!(
339-
"Fetching ugprade cap {} from chain",
340-
upgrade_info.upgraded_id
341-
);
342-
self.deploy_object_id(upgrade_info.upgraded_id.into(), rpc)
343-
.await?;
344-
}
345-
}
346-
self.db.commit_single_object(object)?;
347-
} else {
348-
return Err(eyre!("package {} not found", package_id).into());
349-
}
350-
Ok(())
351-
}
352-
353370
pub async fn all_tys(&self) -> Result<BTreeSet<MoveStructTag>, MovyError> {
354371
let mut tags = BTreeSet::new();
355372
for obj in self.db.list_objects().await? {

crates/movy-replay/src/exec.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ where
313313
// derive id
314314
let id = ObjectID::derive_id(random_digest(), self.deploy_ids);
315315
self.deploy_ids += 1;
316+
tracing::info!("Generate a new package id: {}", id);
316317
substitute_package_id(&mut modules, id)?;
317318
}
318319

crates/movy/src/sui/env.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ impl SuiTargetArgs {
100100
let mut local_name_map = BTreeMap::new();
101101

102102
for onchain in self.onchains.iter().flatten() {
103-
env.deploy_package_at_address(*onchain, rpc).await?;
103+
env.fetch_package_at_address(*onchain, rpc).await?;
104104
target_packages.push(*onchain);
105105
}
106106

test-data/onchain/hello1/Move.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ name = "hello1"
33
edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move
44
# license = "" # e.g., "MIT", "GPL", "Apache 2.0"
55
# authors = ["..."] # e.g., ["Joe Smith (joesmith@noemail.com)", "John Snow (johnsnow@noemail.com)"]
6+
published-at = "0x18f8f4ebada51361c6558c7cfa77cb46be72fca7caacc475dc828858becfaa75"
67

78
[dependencies]
89

910
[addresses]
10-
hello1 = "0bcb4367516cc4f22b7c0265db04e9b4e83b1f24b0b0455020f26e448df6fd08"
11+
hello1 = "0x0bcb4367516cc4f22b7c0265db04e9b4e83b1f24b0b0455020f26e448df6fd08"
1112

1213
[dev-dependencies]
1314

test-data/onchain/hello1/Readme.md

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,61 @@
33

44
The package was deployed at bcb4367516cc4f22b7c0265db04e9b4e83b1f24b0b0455020f26e448df6fd08 firstly and then upgraded to 0x18f8f4ebada51361c6558c7cfa77cb46be72fca7caacc475dc828858becfaa75 with a new module hello1_upgrade.
55

6-
No published-at field is added.
6+
Published-at field is added for compatibility.
7+
8+
## Some notes
9+
10+
On Sui, a package's named address must always be its original (v1) package ID, not the upgraded version's ID. The `published-at` field correctly points to the latest version (for linking), but the address must remain the original.
11+
12+
Each package object has a `linkage_table: BTreeMap<ObjectID, UpgradeInfo>,` that maps the original package address to the (possibly upgraded) actual used package address.
13+
14+
15+
AI Explanation:
16+
17+
Yes, exactly — the `linkage_table` is at the heart of it. Let me break down how it works:
18+
19+
### Sui's On-Chain Package Model
20+
21+
Every on-chain package object has a structure roughly like:
22+
23+
```
24+
Package {
25+
id: ObjectID, // this package's own ID
26+
modules: BTreeMap<String, CompiledModule>,
27+
linkage_table: BTreeMap<ObjectID, UpgradeInfo>, // key = original ID
28+
}
29+
```
30+
31+
The `linkage_table` maps **original package IDs → `UpgradeInfo`** (which contains the upgraded/actual version ID to use at runtime). This is how Sui resolves dependencies when a depended-on package has been upgraded.
32+
33+
### Why the named address must be the original ID
34+
35+
When the Move compiler builds your bytecode, **the named address (`hello1 = "0x..."`) gets baked into the compiled module as the package address**. Every cross-module reference in the bytecode uses this address — e.g., when `hello2` calls `hello1::hello1_upgrade::hello1_upgrade()`, the bytecode literally encodes a reference to the address from `[addresses] hello1`.
36+
37+
At publish/execution time, Sui's runtime does a lookup in the **calling package's `linkage_table`** to resolve which actual on-chain package to load:
38+
39+
1. It sees bytecode referencing address `0x0bcb...` (the original ID)
40+
2. It looks up `0x0bcb...` in the `linkage_table`
41+
3. The `linkage_table` entry says: "use version at `0x18f8...`"
42+
4. It loads the modules from `0x18f8...`
43+
44+
If you set the named address to the **upgraded** ID (`0x18f8...`) instead:
45+
46+
1. The bytecode references `0x18f8...`
47+
2. Sui looks up `0x18f8...` in the `linkage_table`
48+
3. **No entry found** — because the key is always the *original* ID (`0x0bcb...`)
49+
4.`PublishUpgradeMissingDependency`
50+
51+
### The role of `published-at`
52+
53+
`published-at` serves a **different purpose** — it tells the **Move linker** (at build time) which on-chain package version to link against, so it can verify that the modules and functions you're calling actually exist in that version. It does **not** affect the address baked into bytecode.
54+
55+
So in summary:
56+
57+
| Concept | Value | Used by | Purpose |
58+
|---|---|---|---|
59+
| `[addresses] hello1` | Original ID (`0x0bcb...`) | Compiler → bytecode | Baked into bytecode; used as `linkage_table` lookup key at runtime |
60+
| `published-at` | Latest version ID (`0x18f8...`) | Build-time linker | Verifies the dependency's modules/functions exist on-chain |
61+
| `linkage_table` key | Original ID (`0x0bcb...`) | Runtime | Maps original → upgraded version for actual code loading |
62+
63+
The invariant is: **bytecode addresses must match `linkage_table` keys, which are always original package IDs**. That's why the named address must never change across upgrades.

test-data/onchain/hello1_published_at/Move.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ published-at = "0x18f8f4ebada51361c6558c7cfa77cb46be72fca7caacc475dc828858becfaa
88
[dependencies]
99

1010
[addresses]
11-
hello1 = "0x18f8f4ebada51361c6558c7cfa77cb46be72fca7caacc475dc828858becfaa75"
11+
hello1 = "0x0bcb4367516cc4f22b7c0265db04e9b4e83b1f24b0b0455020f26e448df6fd08"
1212

1313
[dev-dependencies]
1414

test-data/onchain/hello1_published_at/Readme.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,20 @@
33

44
The package was deployed at bcb4367516cc4f22b7c0265db04e9b4e83b1f24b0b0455020f26e448df6fd08 firstly and then upgraded to 0x18f8f4ebada51361c6558c7cfa77cb46be72fca7caacc475dc828858becfaa75 with a new module hello1_upgrade.
55

6-
There is a `published-at` added by design.
6+
There is a `published-at` added by design.
7+
8+
Please note the address alias:
9+
10+
```
11+
[addresses]
12+
hello1 = "0x0bcb4367516cc4f22b7c0265db04e9b4e83b1f24b0b0455020f26e448df6fd08"
13+
```
14+
15+
if we wrongly set it to:
16+
17+
```
18+
[addresses]
19+
hello1 = "0x18f8f4ebada51361c6558c7cfa77cb46be72fca7caacc475dc828858becfaa75"
20+
```
21+
22+
the deployment of `hello5` will fail because it should be the original id.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
build/*
2+
traces/*
3+
.trace
4+
.coverage*
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Generated by move; do not edit
2+
# This file should be checked in.
3+
4+
[move]
5+
version = 4
6+
7+
[pinned.mainnet.MoveStdlib]
8+
source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "868c226359ef914f1f3b080518f27eb13d8967f5" }
9+
use_environment = "mainnet"
10+
manifest_digest = "C4FE4C91DE74CBF223B2E380AE40F592177D21870DC2D7EB6227D2D694E05363"
11+
deps = {}
12+
13+
[pinned.mainnet.Sui]
14+
source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "868c226359ef914f1f3b080518f27eb13d8967f5" }
15+
use_environment = "mainnet"
16+
manifest_digest = "CD547CB1ACCE0880C835DAED2D8FFCB91D56C833AE5240D3AA5B918398263195"
17+
deps = { MoveStdlib = "MoveStdlib" }
18+
19+
[pinned.mainnet.hello1]
20+
source = { local = "../hello1" }
21+
use_environment = "mainnet"
22+
manifest_digest = "E41BBD67BE8940D26C79D78B028477EF5B33BA217A1282C78ACB344CF8A5ECF6"
23+
deps = { std = "MoveStdlib", sui = "Sui" }
24+
25+
[pinned.mainnet.hello2]
26+
source = { root = true }
27+
use_environment = "mainnet"
28+
manifest_digest = "5E41B999946BE28C1E620B4E6C5F1B8A9232B5BC411CF52F8C2E1A086A901B98"
29+
deps = { hello1 = "hello1", std = "MoveStdlib", sui = "Sui" }
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
[package]
2+
name = "hello2"
3+
edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move
4+
# license = "" # e.g., "MIT", "GPL", "Apache 2.0"
5+
# authors = ["..."] # e.g., ["Joe Smith (joesmith@noemail.com)", "John Snow (johnsnow@noemail.com)"]
6+
7+
[dependencies]
8+
9+
# For remote import, use the `{ git = "...", subdir = "...", rev = "..." }`.
10+
# Revision can be a branch, a tag, and a commit hash.
11+
# MyRemotePackage = { git = "https://some.remote/host.git", subdir = "remote/path", rev = "main" }
12+
13+
# For local dependencies use `local = path`. Path is relative to the package root
14+
# Local = { local = "../path/to" }
15+
16+
# To resolve a version conflict and force a specific version for dependency
17+
# override use `override = true`
18+
# Override = { local = "../conflicting/version", override = true }
19+
hello1 = {local = "../hello1"}
20+
21+
[addresses]
22+
hello2 = "0x0"
23+
24+
# Named addresses will be accessible in Move as `@name`. They're also exported:
25+
# for example, `std = "0x1"` is exported by the Standard Library.
26+
# alice = "0xA11CE"
27+
28+
[dev-dependencies]
29+
# The dev-dependencies section allows overriding dependencies for `--test` and
30+
# `--dev` modes. You can introduce test-only dependencies here.
31+
# Local = { local = "../path/to/dev-build" }
32+
33+
34+
[dev-addresses]
35+
# The dev-addresses section allows overwriting named addresses for the `--test`
36+
# and `--dev` modes.
37+
# alice = "0xB0B"
38+

0 commit comments

Comments
 (0)