o2_tools/blob_loader.rs
1//! Shared loader-blob upload primitive.
2//!
3//! Sway contract bytecode bigger than one blob-upload transaction
4//! can't be deployed by uploading a single blob and pointing a proxy
5//! at it: the proxy's `run_external_blob` LDCs exactly one blob. The
6//! workaround the SDK gives us is `convert_to_loader(max_words)`,
7//! which splits the bytecode into `data_blobs` and generates a small
8//! loader stub that LDCs each data blob in order before jumping into
9//! the reconstituted code. We upload the data blobs first, then
10//! upload the loader stub *as its own blob*, and the proxy points at
11//! the loader blob.
12//!
13//! Both `OrderBookDeploy` / `TradeAccountDeploy` and the contract
14//! integration tests go through this module so the upload flow stays
15//! in one place. Adding it here, instead of inlining it in each
16//! deployer, avoids the silent-bug class where one site forgets to
17//! upload the data blobs (the loader blob would still upload fine,
18//! the proxy would point at it, and the first call would fail with
19//! `TransactionSizeLimitExceeded` or a missing-blob LDC trap).
20
21use anyhow::Result;
22use fuels::{
23 core::Configurables,
24 prelude::*,
25 tx::StorageSlot,
26 types::transaction_builders::Blob,
27};
28
29/// Build the `(data_blobs, loader_blob)` pair for `bytecode`. The
30/// loader blob's id is the value to use as the proxy's
31/// `INITIAL_TARGET`; `data_blobs` are the chunked code pieces it
32/// references and must be uploaded first.
33pub fn build_loader_blobs(
34 bytecode: Vec<u8>,
35 salt: Salt,
36 storage_slots: Vec<StorageSlot>,
37 configurables: impl Into<Configurables>,
38 max_words_per_blob: usize,
39) -> Result<(Vec<Blob>, Blob)> {
40 let loader = Contract::regular(bytecode, salt, storage_slots)
41 .with_configurables(configurables)
42 .convert_to_loader(max_words_per_blob)?;
43 let data_blobs = loader.blobs().to_vec();
44 let loader_blob = Blob::new(loader.code());
45 Ok((data_blobs, loader_blob))
46}
47
48/// Upload `data_blobs` then `loader_blob`. Blobs already on chain are
49/// skipped (`blob_exists` check). Returns the loader blob's id.
50pub async fn upload_loader_blobs<W>(
51 deployer_wallet: &W,
52 data_blobs: Vec<Blob>,
53 loader_blob: Blob,
54) -> Result<BlobId>
55where
56 W: Account,
57{
58 let provider = deployer_wallet.try_provider()?;
59 for data_blob in data_blobs {
60 if provider.blob_exists(data_blob.id()).await? {
61 continue;
62 }
63 upload_single_blob(deployer_wallet, data_blob).await?;
64 }
65 let loader_blob_id = loader_blob.id();
66 if !provider.blob_exists(loader_blob_id).await? {
67 upload_single_blob(deployer_wallet, loader_blob).await?;
68 }
69 Ok(loader_blob_id)
70}
71
72/// Build, fund, sign and submit a single `BlobTransactionBuilder`
73/// for `blob`, waiting until it commits.
74async fn upload_single_blob<W>(deployer_wallet: &W, blob: Blob) -> Result<()>
75where
76 W: Account,
77{
78 let mut builder = BlobTransactionBuilder::default().with_blob(blob);
79 deployer_wallet.adjust_for_fee(&mut builder, 0).await?;
80 deployer_wallet.add_witnesses(&mut builder)?;
81 let tx = builder.build(&deployer_wallet.try_provider()?).await?;
82 deployer_wallet
83 .try_provider()?
84 .send_transaction_and_await_commit(tx)
85 .await?
86 .check(None)?;
87 Ok(())
88}