Skip to content

Fixes duplicate contractmetav0 custom sections when users add meta on build #2095

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

elizabethengelman
Copy link
Collaborator

@elizabethengelman elizabethengelman commented Jul 24, 2025

What

Closes #1721

Updates the logic to add new contract meta data to the contract wasm on build so that there is only one contractmetav0 custom section.

Here's a diff of the before (on the left) vs after with this branch (on the right): https://www.diffchecker.com/fshP64p8/

These are the commands that were run to get the outputs for the diff:

// before (left)
stellar contract build --manifest-path ./cmd/crates/soroban-test/tests/fixtures/workspace-with-default-members/Cargo.toml --meta key1=value1 --meta key2=value2

wasm-tools parse cmd/crates/soroban-test/tests/fixtures/workspace-with-default-members/target/wasm32v1-none/release/add.wasm -t


// with this branch (right)
cargo run contract build --manifest-path ./cmd/crates/soroban-test/tests/fixtures/workspace-with-default-members/Cargo.toml --meta key1=value1 --meta key2=value2

wasm-tools parse cmd/crates/soroban-test/tests/fixtures/workspace-with-default-members/target/wasm32v1-none/release/add.wasm -t

Output from uploading and deploying the resulting wasm:

❯ cargo run contract upload --wasm cmd/crates/soroban-test/tests/fixtures/workspace-with-default-members/target/wasm32v1-none/release/add.wasm --source ee --network testnet
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
     Running `target/debug/soroban contract upload --wasm cmd/crates/soroban-test/tests/fixtures/workspace-with-default-members/target/wasm32v1-none/release/add.wasm --source ee --network testnet`
⚠️ A new release of stellar-cli is available: 22.8.2 -> 23.0.0
ℹ️ Simulating install transaction…
ℹ️ Signing transaction: 866da95a8df01f6c63756367c383764c4a38d556268cbd7b78574aaa896849a8
🌎 Submitting install transaction…
3f7bebccdd3f43bb03551844e95b0cb3c6a53b2393bb8c2fdd268090447e5dff


❯  cargo run contract deploy --wasm-hash 3f7bebccdd3f43bb03551844e95b0cb3c6a53b2393bb8c2fdd268090447e5dff --source ee --network testnet
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
     Running `target/debug/soroban contract deploy --wasm-hash 3f7bebccdd3f43bb03551844e95b0cb3c6a53b2393bb8c2fdd268090447e5dff --source ee --network testnet`
ℹ️ Using wasm hash 3f7bebccdd3f43bb03551844e95b0cb3c6a53b2393bb8c2fdd268090447e5dff
⚠️ A new release of stellar-cli is available: 22.8.2 -> 23.0.0
ℹ️ Simulating deploy transaction…
ℹ️ Transaction hash is c93ec680d376f64ea14c65fe94c92f780777b40c97857ef34daf9c3dbcbd998f
🔗 https://stellar.expert/explorer/testnet/tx/c93ec680d376f64ea14c65fe94c92f780777b40c97857ef34daf9c3dbcbd998f
ℹ️ Signing transaction: c93ec680d376f64ea14c65fe94c92f780777b40c97857ef34daf9c3dbcbd998f
🌎 Submitting deploy transaction…
🔗 https://stellar.expert/explorer/testnet/contract/CDNDIUQLAVWBRNXEYEWPKLPNLN74I5TVC4DCROJMROX5FCCJ5GUH4WTP
✅ Deployed!
CDNDIUQLAVWBRNXEYEWPKLPNLN74I5TVC4DCROJMROX5FCCJ5GUH4WTP

Why

This will make it so the wasm isn't larger than it needs to be with duplicate contractmetav0 sections. This hopefully will also make it easier for the data team to fetch contract data.

Known limitations

This does reencode the whole wasm file, instead of just appending a new section. Given the tooling, this was the only way I could figure out how to remove the default contractmetav0 and include just the new, updated section with the user's meta.

@github-project-automation github-project-automation bot moved this to Backlog (Not Ready) in DevX Jul 24, 2025
@elizabethengelman elizabethengelman marked this pull request as ready for review July 24, 2025 16:02
@elizabethengelman elizabethengelman requested a review from a team as a code owner July 24, 2025 16:02
@elizabethengelman elizabethengelman changed the title Fix/meta duplicates Fixes duplicate contractmetav0 custom sections when users add meta on build Jul 24, 2025
@janewang janewang requested a review from leighmcculloch July 24, 2025 17:24
Copy link
Member

@leighmcculloch leighmcculloch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change isn't quite what I meant in #1721, but it's better!

The issue was reporting that:

the cli is writing a whole new section per entry, but it should be joining the new entries together and writing them in one new entry

The issue was that when appending N new meta after build, the file would contain N new custom sections, instead of 1 new custom section. The intent was to still append a new custom section without modifying any of the existing custom sections, but to add all the meta entries into 1 new custom section.

The change in this PR is a bit different because it is fully decoding the file and rewriting the entire file. I had avoided rewriting the file in the past out of a fear we could introduce a problem with the contract by fiddling with the existing sections. But we were likely going to end up needing to rewrite files at some point anyway someday, and this change looks good and straight forward.

Rewriting has the outcome that the resulting file should be a tiny bit smaller than if we kept with appending a 2nd section.

I like that we've shifting from wasm-gen to wasm-encoder, because the former has not had updates for years and the latter is maintained by the Bytecode Alliance.

Few issues inline.

Comment on lines +308 to +312
for payload in WasmParser::new(0).parse_all(&wasm_bytes) {
match payload.unwrap() {
Payload::CustomSection(section) => {
if section.name() == META_CUSTOM_SECTION_NAME {
let updated_meta = self.append_new_meta(&existing_meta).unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code assumes that the file contains only a single contractmetav0 custom section. While that happens to be the case today during build, it's somewhat brittle to behave unexpected if that changes about how LLVM (what rustc uses to compile and construct the wasm file) builds custom sections in the future. If that changed, this logic would rewrite the existing meta multiple times. This logic would be more resilient I think if it ensure only a single meta section was writtne.

Copy link
Member

@leighmcculloch leighmcculloch Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For how to address this, I suggest iterating over the entire file, and collecting the contents of all contractmetav0 into a Vec<u8> and not adding them into the new module. All other sections, including all other custom sections, can be added via the raw section logic that exists in the PR further down. Then, serialize the new meta appending it into the collected contents of existing meta sections, and add the contractmetav0 section with the collected + new meta into the module. Following that process should result in only a single meta section no matter how many existed prior to this code being called.

Comment on lines +347 to +348
fn append_new_meta(&self, existing_meta: &[ScMetaEntry]) -> Result<Vec<u8>, Error> {
let mut updated_meta = existing_meta.to_owned();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line makes a copy of the vec in memory, which is unnecessary because the only use of the vec being passed in is to append. So you can accept a &mut Vec<ScMetaEntry> and modify it directly to avoid the copy.

let mut wasm_bytes = fs::read(target_file_path).map_err(Error::ReadingWasmFile)?;
// get existing wasm bytes
let wasm_bytes = fs::read(target_file_path).map_err(Error::ReadingWasmFile)?;
let existing_meta: Vec<ScMetaEntry> = Spec::new(&wasm_bytes).unwrap().meta;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use the Spec class to read the meta out of the wasm_bytes? I would expect that we wouldn't need to parse the existing meta. The new meta can be encoded to XDR on its own and appended to the existing meta's bytes without decoding the existing meta.

@elizabethengelman elizabethengelman self-assigned this Jul 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Backlog (Not Ready)
Development

Successfully merging this pull request may close these issues.

stellar contract build --meta results in one new section per entry
2 participants