use crate::{
CallOption,
call_handler_ext::CallHandlerExt,
trade_account_deploy::{
DeployConfig,
State as TradingAccountState,
TRADE_ACCOUNT_PROXY_BYTECODE,
TradingAccountProxy0_69_1Configurables,
TradingAccountProxyConfigurables,
},
};
use anyhow::Result;
use fuels::{
prelude::*,
tx::StorageSlot,
types::{
Bits256,
Bytes32,
ContractId,
Identity,
},
};
abigen!(
Contract(
name = "TradeAccountRegistry",
abi = "artifacts/trade-account-registry/trade-account-registry-abi.json"
),
Contract(
name = "TradeAccountRegistryProxy",
abi = "artifacts/trade-account-registry-proxy/trade-account-registry-proxy-abi.json"
)
);
pub const TRADE_ACCOUNT_REGISTER_BYTECODE: &[u8] =
include_bytes!("../artifacts/trade-account-registry/trade-account-registry.bin");
pub const TRADE_ACCOUNT_REGISTER_STORAGE: &[u8] = include_bytes!(
"../artifacts/trade-account-registry/trade-account-registry-storage_slots.json"
);
pub const TRADE_ACCOUNT_REGISTER_PROXY_BYTECODE: &[u8] = include_bytes!(
"../artifacts/trade-account-registry-proxy/trade-account-registry-proxy.bin"
);
pub const TRADE_ACCOUNT_REGISTER_PROXY_STORAGE: &[u8] = include_bytes!(
"../artifacts/trade-account-registry-proxy/trade-account-registry-proxy-storage_slots.json"
);
#[derive(Clone)]
pub struct TradeAccountRegistryDeployConfig {
pub registry_bytecode: Vec<u8>,
pub registry_storage_slots: Vec<StorageSlot>,
pub registry_config: TradeAccountRegistryConfigurables,
pub registry_proxy_bytecode: Vec<u8>,
pub registry_proxy_storage_slots: Vec<StorageSlot>,
pub registry_proxy_config: TradeAccountRegistryProxyConfigurables,
pub max_words_per_blob: usize,
pub trade_account_proxy_bytecode: Vec<u8>,
pub trade_account_proxy_root: Bytes32,
pub trade_account_proxy_storage_slots: Vec<StorageSlot>,
pub proxy_owner: Option<Identity>,
pub registry_owner: Option<Identity>,
pub salt: Salt,
}
pub struct TradeAccountRegistryBlob {
pub id: BlobId,
pub exists: bool,
pub blob: Blob,
}
impl Default for TradeAccountRegistryDeployConfig {
fn default() -> Self {
let trade_account_proxy_storage_slots: Vec<StorageSlot> =
serde_json::from_slice(TRADE_ACCOUNT_REGISTER_PROXY_STORAGE).unwrap();
let trade_account_proxy_bytecode = Contract::regular(
TRADE_ACCOUNT_PROXY_BYTECODE.to_vec(),
Salt::zeroed(),
trade_account_proxy_storage_slots.clone(),
);
let trade_account_proxy_root = trade_account_proxy_bytecode.code_root();
Self {
registry_bytecode: TRADE_ACCOUNT_REGISTER_BYTECODE.to_vec(),
registry_storage_slots: serde_json::from_slice(
TRADE_ACCOUNT_REGISTER_STORAGE,
)
.unwrap(),
registry_config: TradeAccountRegistryConfigurables::default(),
registry_proxy_bytecode: TRADE_ACCOUNT_REGISTER_PROXY_BYTECODE.to_vec(),
registry_proxy_storage_slots: serde_json::from_slice(
TRADE_ACCOUNT_REGISTER_PROXY_STORAGE,
)
.unwrap(),
registry_proxy_config: TradeAccountRegistryProxyConfigurables::default(),
max_words_per_blob: 10_000,
trade_account_proxy_bytecode: TRADE_ACCOUNT_PROXY_BYTECODE.to_vec(),
trade_account_proxy_root,
trade_account_proxy_storage_slots,
proxy_owner: None,
registry_owner: None,
salt: Salt::default(),
}
}
}
#[derive(Clone)]
pub struct TradeAccountRegistryManager<W> {
pub registry_proxy: TradeAccountRegistryProxy<W>,
pub registry: TradeAccountRegistry<W>,
pub contract_id: ContractId,
pub wallet: W,
}
impl<W> TradeAccountRegistryManager<W>
where
W: Account + Clone,
{
pub fn change_wallet(mut self, wallet: &W) -> Self {
self.wallet = wallet.clone();
self.registry = self.registry.with_account(wallet.clone());
self
}
pub fn new(deployer_wallet: W, contract_id: ContractId) -> Self {
let proxy = TradeAccountRegistryProxy::new(contract_id, deployer_wallet.clone());
let registry = TradeAccountRegistry::new(contract_id, deployer_wallet.clone());
Self {
registry_proxy: proxy,
registry,
contract_id,
wallet: deployer_wallet,
}
}
pub async fn register_blob(
deployer_wallet: &W,
trade_account_oracle_id: ContractId,
trade_account_proxy_blob_id: BlobId,
config: &TradeAccountRegistryDeployConfig,
) -> Result<TradeAccountRegistryBlob> {
let registry_owner = config
.registry_owner
.unwrap_or(Identity::Address(deployer_wallet.address()));
let configurables = config
.registry_config
.clone()
.with_ORACLE_CONTRACT_ID(trade_account_oracle_id)?
.with_TRADE_ACCOUNT_PROXY_ROOT(Bits256(*config.trade_account_proxy_root))?
.with_DEFAULT_TRADE_ACCOUNT_PROXY(ContractId::from(
trade_account_proxy_blob_id,
))?
.with_INITIAL_OWNER(State::Initialized(registry_owner))?;
let blobs = Contract::regular(
config.registry_bytecode.clone(),
config.salt,
config.registry_storage_slots.clone(),
)
.with_configurables(configurables.clone())
.convert_to_loader(config.max_words_per_blob)?
.blobs()
.to_vec();
let blob = blobs[0].clone();
let blob_id = blob.id();
let blob_exists = deployer_wallet.try_provider()?.blob_exists(blob_id).await?;
Ok(TradeAccountRegistryBlob {
id: blob_id,
exists: blob_exists,
blob: blob.clone(),
})
}
pub async fn register_proxy_blob(
deployer_wallet: &W,
config: &TradeAccountRegistryDeployConfig,
) -> Result<TradeAccountRegistryBlob> {
let blobs = Contract::regular(
config.trade_account_proxy_bytecode.clone(),
Salt::zeroed(),
config.trade_account_proxy_storage_slots.clone(),
)
.convert_to_loader(config.max_words_per_blob)?
.blobs()
.to_vec();
let blob = blobs[0].clone();
let blob_id = blob.id();
let blob_exists = deployer_wallet.try_provider()?.blob_exists(blob_id).await?;
Ok(TradeAccountRegistryBlob {
id: blob_id,
exists: blob_exists,
blob: blob.clone(),
})
}
pub async fn deploy_register_blob(
deployer_wallet: &W,
trade_account_oracle_id: ContractId,
config: &TradeAccountRegistryDeployConfig,
) -> Result<BlobId> {
let proxy_blob = Self::register_proxy_blob(deployer_wallet, config).await?;
if !proxy_blob.exists {
let mut builder =
BlobTransactionBuilder::default().with_blob(proxy_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)?;
}
let register_blob = Self::register_blob(
deployer_wallet,
trade_account_oracle_id,
proxy_blob.id,
config,
)
.await?;
if !register_blob.exists {
let mut builder =
BlobTransactionBuilder::default().with_blob(register_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(register_blob.id)
}
pub async fn deploy_register_proxy(
deployer_wallet: &W,
registry_blob_id: &BlobId,
config: &TradeAccountRegistryDeployConfig,
) -> Result<(TradeAccountRegistryProxy<W>, bool)> {
let proxy_owner = config
.proxy_owner
.unwrap_or(Identity::Address(deployer_wallet.address()));
let configurables = config
.registry_proxy_config
.clone()
.with_INITIAL_OWNER(State::Initialized(proxy_owner))?
.with_INITIAL_TARGET(ContractId::new(*registry_blob_id))?;
let contract = Contract::regular(
config.registry_proxy_bytecode.clone(),
config.salt,
config.registry_proxy_storage_slots.clone(),
)
.with_configurables(configurables);
let contract_id = contract.contract_id();
let already_deployed = deployer_wallet
.try_provider()?
.contract_exists(&contract_id)
.await?;
let proxy = TradeAccountRegistryProxy::new(contract_id, deployer_wallet.clone());
if !already_deployed {
contract
.deploy(deployer_wallet, TxPolicies::default())
.await?;
}
let requires_initialization = !already_deployed;
Ok((proxy, requires_initialization))
}
pub async fn deploy(
deployer_wallet: &W,
trade_account_oracle_id: ContractId,
config: &TradeAccountRegistryDeployConfig,
) -> Result<TradeAccountRegistryManager<W>> {
let register_blob_id =
Self::deploy_register_blob(deployer_wallet, trade_account_oracle_id, config)
.await?;
let (proxy, requires_initialization) =
TradeAccountRegistryManager::deploy_register_proxy(
deployer_wallet,
®ister_blob_id,
config,
)
.await?;
let register_deploy = TradeAccountRegistryManager::new(
deployer_wallet.clone(),
proxy.contract_id(),
);
if requires_initialization {
register_deploy
.registry_proxy
.methods()
.initialize_proxy()
.call()
.await?;
register_deploy
.registry
.methods()
.initialize()
.call()
.await?;
}
Ok(register_deploy)
}
pub async fn upgrade(
&self,
trade_account_oracle_id: ContractId,
config: &TradeAccountRegistryDeployConfig,
) -> Result<BlobId> {
let new_blob_id =
Self::deploy_register_blob(&self.wallet, trade_account_oracle_id, config)
.await?;
self.registry_proxy
.methods()
.set_proxy_target(ContractId::new(new_blob_id))
.call()
.await?;
Ok(new_blob_id)
}
pub async fn get_trade_account(&self, owner: Identity) -> Result<Option<ContractId>> {
Ok(self
.registry
.methods()
.get_trade_account(owner)
.simulate(Execution::state_read_only())
.await?
.value)
}
pub async fn oracle_id(&self) -> Result<ContractId> {
Ok(self
.registry
.methods()
.get_oracle_id()
.simulate(Execution::state_read_only())
.await?
.value)
}
}
impl TradeAccountRegistryManager<Wallet> {
pub async fn register_trade_account(
&self,
owner_identity: Identity,
trade_account_id: ContractId,
trade_account_oracle_id: ContractId,
call_option: &CallOption,
deploy_config: &DeployConfig,
) -> Result<(ContractId, u64)> {
let configurables: Vec<fuels::core::Configurable> = match deploy_config {
DeployConfig::Old0_69_1(_) => {
TradingAccountProxy0_69_1Configurables::default()
.with_ORACLE_CONTRACT_ID(trade_account_oracle_id)?
.with_INITIAL_OWNER(TradingAccountState::Initialized(owner_identity))?
.into()
}
DeployConfig::Latest(_) => TradingAccountProxyConfigurables::default()
.with_ORACLE_CONTRACT_ID(trade_account_oracle_id)?
.with_INITIAL_OWNER(TradingAccountState::Initialized(owner_identity))?
.into(),
};
let call_handler = self
.registry
.methods()
.register_contract(
trade_account_id,
Some(
configurables
.into_iter()
.map(|c| (c.offset, c.data))
.collect(),
),
)
.with_contract_ids(&[trade_account_id, trade_account_oracle_id]);
let result = match call_option {
CallOption::AwaitBlock => call_handler.call().await?,
CallOption::AwaitPreconfirmation(ops) => {
call_handler
.almost_sync_call(
&ops.data_builder,
&ops.utxo_manager,
&ops.tx_config,
)
.await?
.tx_status?
}
};
match result.value {
Ok(_) => Ok((trade_account_id, result.tx_status.total_gas)),
Err(e) => Err(anyhow::anyhow!("Failed to register trade account: {:?}", e)),
}
}
}
#[cfg(test)]
mod tests_trade_account_registry {
use super::*;
use crate::{
trade_account::TradeAccountManager,
trade_account_deploy::{
DeployConfig,
TradeAccountDeploy,
TradeAccountDeployConfig,
},
};
use fuels::test_helpers::{
WalletsConfig,
launch_custom_provider_and_get_wallets,
};
#[tokio::test]
async fn test_trade_account_registry_upgrade() {
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(2), Some(1), Some(1_000_000_000)),
None,
None,
)
.await
.unwrap();
let deployer_wallet = wallets.pop().unwrap();
let user1_wallet = wallets.pop().unwrap();
let deploy_config = DeployConfig::Latest(TradeAccountDeployConfig::default());
let trade_account_deploy =
TradeAccountDeploy::deploy(&deployer_wallet, &deploy_config)
.await
.unwrap();
let user1_identity = Identity::Address(user1_wallet.address());
let user1_trade_account_deployed = trade_account_deploy
.deploy_with_account(&user1_identity, &deploy_config, &CallOption::AwaitBlock)
.await
.unwrap();
let user1_trade_account = TradeAccountManager::new(
&user1_wallet,
user1_trade_account_deployed.proxy_id.unwrap(),
)
.await
.unwrap();
let trade_account_registry = TradeAccountRegistryManager::deploy(
&deployer_wallet,
trade_account_deploy.oracle_id,
&Default::default(),
)
.await
.unwrap();
trade_account_registry
.register_trade_account(
user1_identity,
user1_trade_account.contract_id(),
trade_account_deploy.oracle_id,
&CallOption::AwaitBlock,
&deploy_config,
)
.await
.unwrap();
let result = trade_account_registry
.get_trade_account(user1_identity)
.await
.unwrap();
assert_eq!(
result,
Some(user1_trade_account.contract_id()),
"Trade account should register successfully"
);
let current_blob_id = trade_account_registry
.registry_proxy
.methods()
.proxy_target()
.simulate(Execution::state_read_only())
.await
.unwrap()
.value;
trade_account_registry
.upgrade(
trade_account_deploy.oracle_id,
&TradeAccountRegistryDeployConfig {
trade_account_proxy_root: Bytes32::new([1; 32]),
..Default::default()
},
)
.await
.unwrap();
let upgraded_blob_id = trade_account_registry
.registry_proxy
.methods()
.proxy_target()
.simulate(Execution::state_read_only())
.await
.unwrap()
.value;
assert_ne!(
current_blob_id, upgraded_blob_id,
"Blob id should be updated after upgrade"
);
let result = trade_account_registry
.get_trade_account(user1_identity)
.await
.unwrap();
assert_eq!(
result,
Some(user1_trade_account.contract_id()),
"Trade account registered should keep working after upgrade"
);
}
#[tokio::test]
async fn test_trade_account_registration_on_registry() {
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(2), Some(1), Some(1_000_000_000)),
None,
None,
)
.await
.unwrap();
let deployer_wallet = wallets.pop().unwrap();
let user_wallet = wallets.pop().unwrap();
let deploy_config = DeployConfig::Latest(TradeAccountDeployConfig::default());
let trade_account_deploy =
TradeAccountDeploy::deploy(&deployer_wallet, &deploy_config)
.await
.unwrap();
let owner_identity = Identity::Address(user_wallet.address());
let trade_account_deployed = trade_account_deploy
.deploy_with_account(&owner_identity, &deploy_config, &CallOption::AwaitBlock)
.await
.unwrap();
let trade_account = TradeAccountManager::new(
&user_wallet,
trade_account_deployed.proxy_id.unwrap(),
)
.await
.unwrap();
let trade_account_registry = TradeAccountRegistryManager::deploy(
&deployer_wallet,
trade_account_deploy.oracle_id,
&Default::default(),
)
.await
.unwrap();
trade_account_registry
.register_trade_account(
owner_identity,
trade_account.contract_id(),
trade_account_deploy.oracle_id,
&CallOption::AwaitBlock,
&deploy_config,
)
.await
.unwrap();
let result = trade_account_registry
.get_trade_account(owner_identity)
.await
.unwrap();
assert_eq!(result, Some(trade_account.contract_id()));
trade_account_registry
.register_trade_account(
owner_identity,
trade_account.contract_id(),
trade_account_deploy.oracle_id,
&CallOption::AwaitBlock,
&deploy_config,
)
.await
.expect_err("Should fail if already register");
}
}