use anyhow::{
Context,
Result,
ensure,
};
use fuels::{
core::Configurables,
prelude::*,
tx::StorageSlot,
types::Identity,
};
abigen!(Contract(
name = "GenericProxyContract",
abi = "artifacts/generic-proxy/generic-proxy-abi.json"
));
pub const GENERIC_PROXY_BYTECODE: &[u8] =
include_bytes!("../artifacts/generic-proxy/generic-proxy.bin");
pub const GENERIC_PROXY_STORAGE: &[u8] =
include_bytes!("../artifacts/generic-proxy/generic-proxy-storage_slots.json");
#[derive(Clone, Debug)]
pub struct GenericProxyDeployConfig {
pub contract_id: Option<ContractId>,
pub salt: Salt,
pub upgrade_bytecode: bool,
pub max_words_per_blob: usize,
}
impl Default for GenericProxyDeployConfig {
fn default() -> Self {
Self {
contract_id: None,
salt: Salt::zeroed(),
upgrade_bytecode: false,
max_words_per_blob: 10_000,
}
}
}
pub struct GenericProxyDeployment<W: Account> {
pub proxy: GenericProxyContract<W>,
pub contract_id: ContractId,
pub blob_id: ContractId,
}
impl<W: Account + Clone> GenericProxyDeployment<W> {
pub async fn deploy(
wallet: &W,
config: &GenericProxyDeployConfig,
implementation_bytecode: &[u8],
implementation_storage_json: &[u8],
) -> Result<Self> {
ensure!(
config.max_words_per_blob > 0,
"GenericProxy loader blob size cannot be zero"
);
let provider = wallet.try_provider()?;
if let Some(id) = config.contract_id {
ensure!(
id != ContractId::zeroed() && provider.contract_exists(&id).await?,
"configured generic proxy does not exist: {id}"
);
}
let implementation_storage: Vec<StorageSlot> =
serde_json::from_slice(implementation_storage_json)?;
let (data, loader) = crate::blob_loader::build_loader_blobs(
implementation_bytecode.to_vec(),
config.salt,
implementation_storage.clone(),
Configurables::default(),
config.max_words_per_blob,
)?;
let desired: ContractId = loader.id().into();
let id = match config.contract_id {
Some(id) => id,
None => {
crate::blob_loader::upload_loader_blobs(
wallet,
data.clone(),
loader.clone(),
)
.await?;
let mut slots: Vec<StorageSlot> =
serde_json::from_slice(GENERIC_PROXY_STORAGE)?;
for slot in implementation_storage {
ensure!(
!slots.iter().any(|s| s.key() == slot.key()),
"generic proxy/implementation storage collision"
);
slots.push(slot);
}
let contract = Contract::regular(
GENERIC_PROXY_BYTECODE.to_vec(),
config.salt,
slots,
)
.with_configurables(
GenericProxyContractConfigurables::default()
.with_INITIAL_OWNER(State::Initialized(Identity::Address(
wallet.address(),
)))?
.with_INITIAL_TARGET(desired)?,
);
let id = contract.contract_id();
if !provider.contract_exists(&id).await? {
contract.deploy(wallet, TxPolicies::default()).await?;
}
id
}
};
let proxy = GenericProxyContract::new(id, wallet.clone());
let mut target = proxy
.methods()
.proxy_target()
.simulate(Execution::state_read_only())
.await?
.value;
if target.is_none() {
proxy
.methods()
.initialize_proxy()
.call()
.await
.context("initialize generic proxy")?;
target = proxy
.methods()
.proxy_target()
.simulate(Execution::state_read_only())
.await?
.value;
}
if config.upgrade_bytecode && target != Some(desired) {
crate::blob_loader::upload_loader_blobs(wallet, data, loader).await?;
proxy
.methods()
.set_proxy_target(desired)
.call()
.await
.context("upgrade GenericProxy implementation")?;
target = Some(desired);
}
Ok(Self {
proxy,
contract_id: id,
blob_id: target.context("proxy has no target")?,
})
}
}