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