use anyhow::Result;
use fuels::{
prelude::*,
tx::StorageSlot,
types::{
AssetId,
ContractId,
Identity,
},
};
abigen!(
Contract(
name = "OrderBook",
abi = "artifacts/order-book/order-book-abi.json"
),
Contract(
name = "OrderBookProxy",
abi = "artifacts/order-book-proxy/order-book-proxy-abi.json"
),
Contract(
name = "OrderBookWhitelist",
abi = "artifacts/order-book-whitelist/order-book-whitelist-abi.json",
),
Contract(
name = "OrderBookBlacklist",
abi = "artifacts/order-book-blacklist/order-book-blacklist-abi.json",
),
);
pub const ORDER_BOOK_BYTECODE: &[u8] =
include_bytes!("../../../artifacts/order-book/order-book.bin");
pub const ORDER_BOOK_STORAGE: &[u8] =
include_bytes!("../../../artifacts/order-book/order-book-storage_slots.json");
pub const ORDER_BOOK_PROXY_BYTECODE: &[u8] =
include_bytes!("../../../artifacts/order-book-proxy/order-book-proxy.bin");
pub const ORDER_BOOK_PROXY_STORAGE: &[u8] = include_bytes!(
"../../../artifacts/order-book-proxy/order-book-proxy-storage_slots.json"
);
pub const ORDER_BOOK_WHITELIST_BYTECODE: &[u8] =
include_bytes!("../../../artifacts/order-book-whitelist/order-book-whitelist.bin");
pub const ORDER_BOOK_WHITELIST_STORAGE: &[u8] = include_bytes!(
"../../../artifacts/order-book-whitelist/order-book-whitelist-storage_slots.json"
);
pub const ORDER_BOOK_BLACKLIST_BYTECODE: &[u8] =
include_bytes!("../../../artifacts/order-book-blacklist/order-book-blacklist.bin");
pub const ORDER_BOOK_BLACKLIST_STORAGE: &[u8] = include_bytes!(
"../../../artifacts/order-book-blacklist/order-book-blacklist-storage_slots.json"
);
#[derive(Clone)]
pub struct OrderBookDeployConfig {
pub order_book_bytecode: Vec<u8>,
pub order_book_storage_slots: Vec<StorageSlot>,
pub order_book_configurables: OrderBookConfigurables,
pub order_book_proxy_bytecode: Vec<u8>,
pub order_book_proxy_storage_slots: Vec<StorageSlot>,
pub order_book_proxy_configurables: OrderBookProxyConfigurables,
pub order_book_whitelist_bytecode: Vec<u8>,
pub order_book_whitelist_storage_slots: Vec<StorageSlot>,
pub order_book_blacklist_bytecode: Vec<u8>,
pub order_book_blacklist_storage_slots: Vec<StorageSlot>,
pub max_words_per_blob: usize,
pub proxy_owner: Option<Identity>,
pub order_book_owner: Option<Identity>,
pub salt: Salt,
}
pub struct OrderBookBlob {
pub id: BlobId,
pub exists: bool,
pub blob: Blob,
}
impl Default for OrderBookDeployConfig {
fn default() -> Self {
Self {
order_book_bytecode: ORDER_BOOK_BYTECODE.to_vec(),
order_book_storage_slots: serde_json::from_slice(ORDER_BOOK_STORAGE).unwrap(),
order_book_whitelist_bytecode: ORDER_BOOK_WHITELIST_BYTECODE.to_vec(),
order_book_configurables: OrderBookConfigurables::default(),
order_book_proxy_bytecode: ORDER_BOOK_PROXY_BYTECODE.to_vec(),
order_book_proxy_storage_slots: serde_json::from_slice(
ORDER_BOOK_PROXY_STORAGE,
)
.unwrap(),
order_book_proxy_configurables: OrderBookProxyConfigurables::default(),
order_book_whitelist_storage_slots: serde_json::from_slice(
ORDER_BOOK_WHITELIST_STORAGE,
)
.unwrap(),
order_book_blacklist_bytecode: ORDER_BOOK_BLACKLIST_BYTECODE.to_vec(),
order_book_blacklist_storage_slots: serde_json::from_slice(
ORDER_BOOK_BLACKLIST_STORAGE,
)
.unwrap(),
max_words_per_blob: 100_000,
proxy_owner: None,
order_book_owner: None,
salt: Salt::default(),
}
}
}
impl OrderBookDeployConfig {
pub fn with_configurables(order_book_configurables: OrderBookConfigurables) -> Self {
Self {
order_book_configurables,
..OrderBookDeployConfig::default()
}
}
}
#[derive(Clone)]
pub struct OrderBookDeploy<W: Account + Clone> {
pub order_book_proxy: OrderBookProxy<W>,
pub order_book: OrderBook<W>,
pub contract_id: ContractId,
pub base_asset: AssetId,
pub quote_asset: AssetId,
pub whitelist: Option<OrderBookWhitelist<W>>,
pub whitelist_id: Option<ContractId>,
}
impl<W: Account + Clone> OrderBookDeploy<W> {
pub fn new(
w: W,
contract_id: ContractId,
base_asset: AssetId,
quote_asset: AssetId,
) -> Self {
let order_book = OrderBook::new(contract_id, w.clone());
let order_book_proxy = OrderBookProxy::new(contract_id, w.clone());
Self {
order_book,
order_book_proxy,
contract_id,
base_asset,
quote_asset,
whitelist: None,
whitelist_id: None,
}
}
pub async fn initialize(&self) -> Result<()> {
self.order_book_proxy
.methods()
.initialize_proxy()
.call()
.await?;
self.order_book.methods().initialize().call().await?;
Ok(())
}
pub async fn deploy_order_book_blacklist(
deployer_wallet: &W,
owner_identity: &Identity,
config: &OrderBookDeployConfig,
) -> Result<OrderBookBlacklist<W>> {
let contract = Contract::regular(
config.order_book_blacklist_bytecode.clone(),
config.salt,
config.order_book_blacklist_storage_slots.clone(),
)
.with_configurables(
OrderBookBlacklistConfigurables::default()
.with_INITIAL_OWNER(State::Initialized(*owner_identity))?,
)
.with_salt(config.salt);
let contract_id = contract.contract_id();
let instance = OrderBookBlacklist::new(contract_id, deployer_wallet.clone());
let contract_exists = deployer_wallet
.try_provider()?
.contract_exists(&contract_id)
.await?;
if !contract_exists {
contract
.deploy(deployer_wallet, TxPolicies::default())
.await?;
instance.methods().initialize().call().await?;
}
Ok(instance)
}
pub async fn deploy_order_book_whitelist(
deployer_wallet: &W,
owner_identity: &Identity,
config: &OrderBookDeployConfig,
) -> Result<OrderBookWhitelist<W>> {
let contract = Contract::regular(
config.order_book_whitelist_bytecode.clone(),
config.salt,
config.order_book_whitelist_storage_slots.clone(),
)
.with_configurables(
OrderBookWhitelistConfigurables::default()
.with_INITIAL_OWNER(State::Initialized(*owner_identity))?,
)
.with_salt(config.salt);
let contract_id = contract.contract_id();
let instance = OrderBookWhitelist::new(contract_id, deployer_wallet.clone());
let contract_exists = deployer_wallet
.try_provider()?
.contract_exists(&contract_id)
.await?;
if !contract_exists {
contract
.deploy(deployer_wallet, TxPolicies::default())
.await?;
instance.methods().initialize().call().await?;
}
Ok(instance)
}
pub async fn order_book_blob(
deployer_wallet: &W,
base_asset: AssetId,
quote_asset: AssetId,
config: &OrderBookDeployConfig,
) -> Result<OrderBookBlob> {
let order_book_owner = config
.order_book_owner
.unwrap_or(Identity::Address(deployer_wallet.address()));
let configurables = config
.order_book_configurables
.clone()
.with_BASE_ASSET(base_asset)?
.with_QUOTE_ASSET(quote_asset)?
.with_INITIAL_OWNER(State::Initialized(order_book_owner))?;
let blobs = Contract::regular(
config.order_book_bytecode.clone(),
config.salt,
config.order_book_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(OrderBookBlob {
id: blob_id,
exists: blob_exists,
blob: blob.clone(),
})
}
pub async fn deploy_order_book_blob(
deployer_wallet: &W,
base_asset: AssetId,
quote_asset: AssetId,
config: &OrderBookDeployConfig,
) -> Result<BlobId> {
let order_book_blob =
Self::order_book_blob(deployer_wallet, base_asset, quote_asset, config)
.await?;
if !order_book_blob.exists {
let mut builder =
BlobTransactionBuilder::default().with_blob(order_book_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(order_book_blob.id)
}
pub async fn deploy_order_book_proxy(
deployer_wallet: &W,
order_book_blob_id: &BlobId,
config: &OrderBookDeployConfig,
) -> Result<(OrderBookProxy<W>, bool)> {
let proxy_owner = config
.proxy_owner
.unwrap_or(Identity::Address(deployer_wallet.address()));
let blob_id = ContractId::new(*order_book_blob_id);
let configurables = config
.order_book_proxy_configurables
.clone()
.with_INITIAL_TARGET(blob_id)?
.with_INITIAL_OWNER(State::Initialized(proxy_owner))?;
let contract = Contract::regular(
config.order_book_proxy_bytecode.clone(),
config.salt,
config.order_book_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 order_book_proxy = OrderBookProxy::new(contract_id, deployer_wallet.clone());
if !already_deployed {
contract
.deploy(deployer_wallet, TxPolicies::default())
.await?;
}
let requires_initialization = !already_deployed;
Ok((order_book_proxy, requires_initialization))
}
pub async fn deploy(
deployer_wallet: &W,
base_asset: AssetId,
quote_asset: AssetId,
config: &OrderBookDeployConfig,
) -> Result<OrderBookDeploy<W>> {
let (deploy, requires_initialization) = Self::deploy_without_initialization(
deployer_wallet,
base_asset,
quote_asset,
config,
)
.await?;
if requires_initialization {
deploy.initialize().await?;
}
Ok(deploy)
}
pub async fn deploy_without_initialization(
deployer_wallet: &W,
base_asset: AssetId,
quote_asset: AssetId,
config: &OrderBookDeployConfig,
) -> Result<(OrderBookDeploy<W>, bool)> {
let order_book_blob_id = Self::deploy_order_book_blob(
deployer_wallet,
base_asset,
quote_asset,
config,
)
.await?;
let (deploy_order_book_proxy, requires_initialization) =
Self::deploy_order_book_proxy(deployer_wallet, &order_book_blob_id, config)
.await?;
let contract_id = deploy_order_book_proxy.contract_id();
let deploy = OrderBookDeploy::new(
deployer_wallet.clone(),
contract_id,
base_asset,
quote_asset,
);
Ok((deploy, requires_initialization))
}
}
#[cfg(test)]
mod tests {
use super::*;
use fuels::test_helpers::{
WalletsConfig,
launch_custom_provider_and_get_wallets,
};
#[tokio::test]
async fn test_deploy_order_book_blob() {
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 contract_owner = wallets.pop().unwrap();
let base_asset = AssetId::from([1u8; 32]);
let quote_asset = AssetId::from([2u8; 32]);
let config = OrderBookDeployConfig {
proxy_owner: Some(Identity::Address(contract_owner.address())),
order_book_owner: Some(Identity::Address(contract_owner.address())),
..Default::default()
};
let order_book_blob_id = OrderBookDeploy::deploy_order_book_blob(
&deployer_wallet,
base_asset,
quote_asset,
&config,
)
.await
.unwrap();
let (order_book_deploy, requires_initialization) =
OrderBookDeploy::deploy_without_initialization(
&deployer_wallet,
base_asset,
quote_asset,
&config,
)
.await
.unwrap();
let order_book = order_book_deploy.order_book;
let order_book_proxy = order_book_deploy.order_book_proxy;
if requires_initialization {
order_book_proxy
.methods()
.initialize_proxy()
.call()
.await
.unwrap();
order_book.methods().initialize().call().await.unwrap();
}
assert_eq!(
order_book_proxy
.methods()
.proxy_owner()
.simulate(Execution::state_read_only())
.await
.unwrap()
.value,
State::Initialized(Identity::Address(contract_owner.address()))
);
assert_eq!(
order_book_proxy
.methods()
.proxy_target()
.simulate(Execution::state_read_only())
.await
.unwrap()
.value,
Some(ContractId::new(order_book_blob_id))
);
assert_eq!(
order_book
.methods()
.owner()
.simulate(Execution::state_read_only())
.await
.unwrap()
.value,
State::Initialized(Identity::Address(contract_owner.address()))
);
assert_eq!(
order_book
.methods()
.get_base_asset()
.simulate(Execution::state_read_only())
.await
.unwrap()
.value,
base_asset,
);
assert_eq!(
order_book
.methods()
.get_quote_asset()
.simulate(Execution::state_read_only())
.await
.unwrap()
.value,
quote_asset,
);
let new_base_asset = AssetId::from([3u8; 32]);
let order_book_blob_id = OrderBookDeploy::deploy_order_book_blob(
&deployer_wallet,
new_base_asset,
quote_asset,
&config,
)
.await
.unwrap();
order_book_proxy
.with_account(contract_owner)
.methods()
.set_proxy_target(ContractId::new(order_book_blob_id))
.call()
.await
.unwrap();
assert_eq!(
order_book
.methods()
.get_base_asset()
.simulate(Execution::state_read_only())
.await
.unwrap()
.value,
new_base_asset
);
}
#[tokio::test]
async fn test_order_book_deployment() {
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 base_asset = AssetId::new([1u8; 32]);
let quote_asset = AssetId::new([2u8; 32]);
let config = OrderBookDeployConfig::default();
let deployment =
OrderBookDeploy::deploy(&deployer_wallet, base_asset, quote_asset, &config)
.await
.unwrap();
let provider = deployer_wallet.try_provider().unwrap();
let contract_exists = provider
.contract_exists(&deployment.contract_id)
.await
.unwrap();
assert!(contract_exists, "OrderBook contract should exist");
assert_eq!(deployment.base_asset, base_asset);
assert_eq!(deployment.quote_asset, quote_asset);
}
}