Skip to main content

o2_tools/
order_book_registry.rs

1use anyhow::Result;
2use fuels::{
3    prelude::*,
4    tx::StorageSlot,
5    types::{
6        ContractId,
7        Identity,
8    },
9};
10
11abigen!(
12    Contract(
13        name = "OrderBookRegistry",
14        abi = "artifacts/order-book-registry/order-book-registry-abi.json"
15    ),
16    Contract(
17        name = "OrderBookRegistryProxy",
18        abi = "artifacts/order-book-registry-proxy/order-book-registry-proxy-abi.json"
19    )
20);
21
22pub const ORDER_BOOK_REGISTER_BYTECODE: &[u8] =
23    include_bytes!("../artifacts/order-book-registry/order-book-registry.bin");
24pub const ORDER_BOOK_REGISTER_STORAGE: &[u8] = include_bytes!(
25    "../artifacts/order-book-registry/order-book-registry-storage_slots.json"
26);
27pub const ORDER_BOOK_REGISTER_PROXY_BYTECODE: &[u8] = include_bytes!(
28    "../artifacts/order-book-registry-proxy/order-book-registry-proxy.bin"
29);
30pub const ORDER_BOOK_REGISTER_PROXY_STORAGE: &[u8] = include_bytes!(
31    "../artifacts/order-book-registry-proxy/order-book-registry-proxy-storage_slots.json"
32);
33
34/// Configuration for deploying OrderBookRegistry contracts.
35/// Contains bytecode and storage slot information needed for deployment.
36#[derive(Clone)]
37pub struct OrderBookRegistryDeployConfig {
38    /// Bytecode for the OrderBookRegistry contract
39    pub registry_bytecode: Vec<u8>,
40    /// Storage slots configuration for the OrderBookRegistry contract
41    pub registry_storage_slots: Vec<StorageSlot>,
42    /// Configurables for the OrderBookRegistry contract
43    pub registry_configurables: OrderBookRegistryConfigurables,
44    /// Bytecode for the OrderBookRegistryProxy contract
45    pub registry_proxy_bytecode: Vec<u8>,
46    /// Storage slots configuration for the OrderBookRegistryProxy
47    pub registry_proxy_storage_slots: Vec<StorageSlot>,
48    /// Register proxy configurations
49    pub registry_proxy_config: OrderBookRegistryProxyConfigurables,
50    /// Maximum words per blob for deployment
51    pub max_words_per_blob: usize,
52    /// Owner for the OrderBookProxyRegistry contract
53    pub proxy_owner: Option<Identity>,
54    /// Owner for the OrderBookRegistry contract
55    pub registry_owner: Option<Identity>,
56    /// Salt for contract deployment
57    pub salt: Salt,
58}
59
60impl Default for OrderBookRegistryDeployConfig {
61    fn default() -> Self {
62        Self {
63            registry_bytecode: ORDER_BOOK_REGISTER_BYTECODE.to_vec(),
64            registry_storage_slots: serde_json::from_slice(ORDER_BOOK_REGISTER_STORAGE)
65                .unwrap(),
66            registry_configurables: OrderBookRegistryConfigurables::default(),
67            registry_proxy_bytecode: ORDER_BOOK_REGISTER_PROXY_BYTECODE.to_vec(),
68            registry_proxy_storage_slots: serde_json::from_slice(
69                ORDER_BOOK_REGISTER_PROXY_STORAGE,
70            )
71            .unwrap(),
72            registry_proxy_config: OrderBookRegistryProxyConfigurables::default(),
73            max_words_per_blob: 10_000,
74            proxy_owner: None,
75            registry_owner: None,
76            salt: Salt::default(),
77        }
78    }
79}
80
81/// Result of an OrderBookRegistry deployment.
82/// Contains the deployed contract instance and configuration.
83#[derive(Clone)]
84pub struct OrderBookRegistryManager<W> {
85    /// The OrderBookRegistryProxy contract instance
86    pub registry_proxy: OrderBookRegistryProxy<W>,
87    /// The deployed OrderBookRegistry contract instance
88    pub registry: OrderBookRegistry<W>,
89    /// Contract ID of the deployed OrderBookRegistry
90    pub contract_id: ContractId,
91    /// The wallet used for deployment
92    pub deployer_wallet: W,
93}
94
95pub struct OrderBookBlob {
96    /// The ID of the deployed blob
97    pub id: BlobId,
98    /// Whether the blob already exists
99    pub exists: bool,
100    /// The blob data containing the contract bytecode
101    pub blob: Blob,
102}
103
104impl<W> OrderBookRegistryManager<W>
105where
106    W: Account + Clone,
107{
108    pub fn new(deployer_wallet: W, contract_id: ContractId) -> Self {
109        let proxy = OrderBookRegistryProxy::new(contract_id, deployer_wallet.clone());
110        let registry = OrderBookRegistry::new(contract_id, deployer_wallet.clone());
111        Self {
112            registry_proxy: proxy,
113            registry,
114            contract_id,
115            deployer_wallet,
116        }
117    }
118
119    pub async fn register_blob(
120        deployer_wallet: &W,
121        config: &OrderBookRegistryDeployConfig,
122    ) -> Result<OrderBookBlob> {
123        let registry_owner = config
124            .registry_owner
125            .unwrap_or(Identity::Address(deployer_wallet.address()));
126        let configurables = config
127            .registry_configurables
128            .clone()
129            .with_INITIAL_OWNER(State::Initialized(registry_owner))?;
130        let blobs = Contract::regular(
131            config.registry_bytecode.clone(),
132            config.salt,
133            config.registry_storage_slots.clone(),
134        )
135        .with_configurables(configurables.clone())
136        .convert_to_loader(config.max_words_per_blob)?
137        .blobs()
138        .to_vec();
139        let blob = blobs[0].clone();
140        let blob_id = blob.id();
141        let blob_exists = deployer_wallet.try_provider()?.blob_exists(blob_id).await?;
142
143        Ok(OrderBookBlob {
144            id: blob_id,
145            exists: blob_exists,
146            blob: blob.clone(),
147        })
148    }
149
150    pub async fn deploy_register_blob(
151        deployer_wallet: &W,
152        config: &OrderBookRegistryDeployConfig,
153    ) -> Result<BlobId> {
154        let register_blob = Self::register_blob(deployer_wallet, config).await?;
155
156        if !register_blob.exists {
157            let mut builder =
158                BlobTransactionBuilder::default().with_blob(register_blob.blob);
159            deployer_wallet.adjust_for_fee(&mut builder, 0).await?;
160            deployer_wallet.add_witnesses(&mut builder)?;
161            let tx = builder.build(&deployer_wallet.try_provider()?).await?;
162
163            deployer_wallet
164                .try_provider()?
165                .send_transaction_and_await_commit(tx)
166                .await?
167                .check(None)?;
168        }
169
170        Ok(register_blob.id)
171    }
172
173    pub async fn deploy_register_proxy(
174        deployer_wallet: &W,
175        registry_blob_id: &BlobId,
176        config: &OrderBookRegistryDeployConfig,
177    ) -> Result<(OrderBookRegistryProxy<W>, bool)> {
178        let proxy_owner = config
179            .proxy_owner
180            .unwrap_or(Identity::Address(deployer_wallet.address()));
181        let configurables = config
182            .registry_proxy_config
183            .clone()
184            .with_INITIAL_OWNER(State::Initialized(proxy_owner))?
185            .with_INITIAL_TARGET(ContractId::new(*registry_blob_id))?;
186        let contract = Contract::regular(
187            config.registry_proxy_bytecode.clone(),
188            config.salt,
189            config.registry_proxy_storage_slots.clone(),
190        )
191        .with_configurables(configurables);
192        let contract_id = contract.contract_id();
193        let already_deployed = deployer_wallet
194            .try_provider()?
195            .contract_exists(&contract_id)
196            .await?;
197        let proxy = OrderBookRegistryProxy::new(contract_id, deployer_wallet.clone());
198
199        if !already_deployed {
200            contract
201                .deploy(deployer_wallet, TxPolicies::default())
202                .await?;
203        }
204        let requires_initialization = !already_deployed;
205
206        Ok((proxy, requires_initialization))
207    }
208
209    /// Deploys an OrderBookRegistry contract.
210    ///
211    /// # Arguments
212    /// * `deployer_wallet` - The wallet to use for deployment (pays gas fees)
213    /// * `config` - Deployment configuration containing bytecode and settings
214    ///
215    /// # Returns
216    /// * `Ok(OrderBookRegistryDeploy)` - Complete deployment result with contract instance
217    /// * `Err(anyhow::Error)` - If deployment or initialization fails
218    ///
219    /// # Process
220    /// 1. Deploys the OrderBookRegistry contract
221    /// 2. Initializes the contract with the specified owner
222    pub async fn deploy(
223        deployer_wallet: &W,
224        config: &OrderBookRegistryDeployConfig,
225    ) -> Result<OrderBookRegistryManager<W>> {
226        let register_blob_id =
227            OrderBookRegistryManager::deploy_register_blob(deployer_wallet, config)
228                .await?;
229        let (proxy, requires_initialization) =
230            OrderBookRegistryManager::deploy_register_proxy(
231                deployer_wallet,
232                &register_blob_id,
233                config,
234            )
235            .await?;
236        let register_deploy =
237            OrderBookRegistryManager::new(deployer_wallet.clone(), proxy.contract_id());
238
239        // If the contract is already deployed, we skip deployment
240        if requires_initialization {
241            // Initialize the proxy contract
242            register_deploy
243                .registry_proxy
244                .methods()
245                .initialize_proxy()
246                .call()
247                .await?;
248            // Initialize the register with the specified owner
249            register_deploy
250                .registry
251                .methods()
252                .initialize()
253                .call()
254                .await?;
255        }
256
257        Ok(register_deploy)
258    }
259
260    pub async fn upgrade(
261        &self,
262        config: &OrderBookRegistryDeployConfig,
263    ) -> Result<BlobId> {
264        // Deploy new blob with updated bytecode
265        let new_blob_id =
266            Self::deploy_register_blob(&self.deployer_wallet, config).await?;
267
268        // Update the proxy to point to the new blob
269        self.registry_proxy
270            .methods()
271            .set_proxy_target(ContractId::new(new_blob_id))
272            .call()
273            .await?;
274
275        Ok(new_blob_id)
276    }
277
278    pub async fn get_order_book(
279        &self,
280        market_id: MarketId,
281    ) -> Result<Option<ContractId>> {
282        Ok(self
283            .registry
284            .methods()
285            .get_order_book(market_id)
286            .simulate(Execution::state_read_only())
287            .await?
288            .value)
289    }
290
291    pub async fn register_order_book(
292        &self,
293        market_id: MarketId,
294        order_book_id: ContractId,
295    ) -> Result<ContractId> {
296        let _ = self
297            .registry
298            .methods()
299            .register_order_book(order_book_id, market_id)
300            .call()
301            .await?;
302        Ok(order_book_id)
303    }
304}
305
306#[cfg(test)]
307mod tests_order_book_registry {
308    use super::*;
309    use fuels::test_helpers::{
310        WalletsConfig,
311        launch_custom_provider_and_get_wallets,
312    };
313
314    #[tokio::test]
315    async fn test_order_book_registry_deployment() {
316        // Start fuel-core
317        let mut wallets = launch_custom_provider_and_get_wallets(
318            WalletsConfig::new(Some(2), Some(1), Some(1_000_000_000)),
319            None,
320            Some(::fuels::test_helpers::ChainConfig::local_testnet()),
321        )
322        .await
323        .unwrap();
324        let deployer_wallet = wallets.pop().unwrap();
325        let owner_wallet = wallets.pop().unwrap();
326
327        // Deploy Register with separate owner
328        let config = OrderBookRegistryDeployConfig {
329            proxy_owner: Some(Identity::Address(owner_wallet.address())),
330            registry_owner: Some(Identity::Address(owner_wallet.address())),
331            ..Default::default()
332        };
333        let deployment = OrderBookRegistryManager::deploy(&deployer_wallet, &config)
334            .await
335            .unwrap();
336
337        // Check if contract exists
338        let provider = deployer_wallet.try_provider().unwrap();
339        let contract_exists = provider
340            .contract_exists(&deployment.contract_id)
341            .await
342            .unwrap();
343        assert!(contract_exists, "Register contract should exist");
344
345        // Verify owner is set correctly
346        let contract_owner = deployment
347            .registry
348            .methods()
349            .owner()
350            .simulate(Execution::state_read_only())
351            .await
352            .unwrap()
353            .value;
354
355        match contract_owner {
356            State::Initialized(identity) => match identity {
357                Identity::Address(address) => {
358                    assert_eq!(address, owner_wallet.address(), "Owner should match");
359                }
360                _ => panic!("Owner should be an address"),
361            },
362            _ => panic!("Owner should be initialized"),
363        }
364    }
365
366    #[tokio::test]
367    async fn test_order_book_registry_register_order_book() {
368        // Start fuel-core
369        let mut wallets = launch_custom_provider_and_get_wallets(
370            WalletsConfig::new(Some(2), Some(1), Some(1_000_000_000)),
371            None,
372            Some(::fuels::test_helpers::ChainConfig::local_testnet()),
373        )
374        .await
375        .unwrap();
376        let deployer_wallet = wallets.pop().unwrap();
377
378        // Deploy Register with separate owner
379        let config = OrderBookRegistryDeployConfig::default();
380        let order_book_registry_deploy =
381            OrderBookRegistryManager::deploy(&deployer_wallet, &config)
382                .await
383                .unwrap();
384        let order_book_registry_manager = OrderBookRegistryManager::new(
385            deployer_wallet.clone(),
386            order_book_registry_deploy.contract_id,
387        );
388
389        let market_id = MarketId {
390            base_asset: AssetId::new([1; 32]),
391            quote_asset: AssetId::new([2; 32]),
392        };
393        let order_book_id = ContractId::new([3; 32]);
394
395        order_book_registry_manager
396            .register_order_book(market_id.clone(), order_book_id)
397            .await
398            .unwrap();
399        let order_book_id_result = order_book_registry_manager
400            .get_order_book(market_id.clone())
401            .await
402            .unwrap();
403        assert_eq!(
404            order_book_id_result,
405            Some(order_book_id),
406            "Should return the correct order book id"
407        );
408    }
409}