Skip to main content

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}