1use anyhow::Result;
2use fuels::{
3 prelude::*,
4 tx::StorageSlot,
5 types::{
6 AssetId,
7 ContractId,
8 Identity,
9 },
10};
11
12use crate::blob_loader;
13
14abigen!(
15 Contract(
16 name = "OrderBook",
17 abi = "artifacts/order-book/order-book-abi.json"
18 ),
19 Contract(
20 name = "OrderBookProxy",
21 abi = "artifacts/order-book-proxy/order-book-proxy-abi.json"
22 ),
23 Contract(
24 name = "OrderBookWhitelist",
25 abi = "artifacts/order-book-whitelist/order-book-whitelist-abi.json",
26 ),
27 Contract(
28 name = "OrderBookBlacklist",
29 abi = "artifacts/order-book-blacklist/order-book-blacklist-abi.json",
30 ),
31);
32pub const ORDER_BOOK_BYTECODE: &[u8] =
33 include_bytes!("../artifacts/order-book/order-book.bin");
34pub const ORDER_BOOK_STORAGE: &[u8] =
35 include_bytes!("../artifacts/order-book/order-book-storage_slots.json");
36pub const ORDER_BOOK_PROXY_BYTECODE: &[u8] =
37 include_bytes!("../artifacts/order-book-proxy/order-book-proxy.bin");
38pub const ORDER_BOOK_PROXY_STORAGE: &[u8] =
39 include_bytes!("../artifacts/order-book-proxy/order-book-proxy-storage_slots.json");
40pub const ORDER_BOOK_WHITELIST_BYTECODE: &[u8] =
41 include_bytes!("../artifacts/order-book-whitelist/order-book-whitelist.bin");
42pub const ORDER_BOOK_WHITELIST_STORAGE: &[u8] = include_bytes!(
43 "../artifacts/order-book-whitelist/order-book-whitelist-storage_slots.json"
44);
45pub const ORDER_BOOK_BLACKLIST_BYTECODE: &[u8] =
46 include_bytes!("../artifacts/order-book-blacklist/order-book-blacklist.bin");
47pub const ORDER_BOOK_BLACKLIST_STORAGE: &[u8] = include_bytes!(
48 "../artifacts/order-book-blacklist/order-book-blacklist-storage_slots.json"
49);
50
51#[derive(Clone)]
54pub struct OrderBookDeployConfig {
55 pub order_book_bytecode: Vec<u8>,
57 pub order_book_storage_slots: Vec<StorageSlot>,
59 pub order_book_configurables: OrderBookConfigurables,
61 pub order_book_proxy_bytecode: Vec<u8>,
63 pub order_book_proxy_storage_slots: Vec<StorageSlot>,
65 pub order_book_proxy_configurables: OrderBookProxyConfigurables,
67 pub order_book_whitelist_bytecode: Vec<u8>,
69 pub order_book_whitelist_storage_slots: Vec<StorageSlot>,
71 pub order_book_blacklist_bytecode: Vec<u8>,
73 pub order_book_blacklist_storage_slots: Vec<StorageSlot>,
75 pub max_words_per_blob: usize,
77 pub proxy_owner: Option<Identity>,
79 pub order_book_owner: Option<Identity>,
81 pub salt: Salt,
83}
84
85pub struct OrderBookBlob {
97 pub id: BlobId,
99 pub exists: bool,
101 pub blob: Blob,
104 pub data_blobs: Vec<Blob>,
108}
109
110impl Default for OrderBookDeployConfig {
111 fn default() -> Self {
112 Self {
113 order_book_bytecode: ORDER_BOOK_BYTECODE.to_vec(),
114 order_book_storage_slots: serde_json::from_slice(ORDER_BOOK_STORAGE).unwrap(),
115 order_book_whitelist_bytecode: ORDER_BOOK_WHITELIST_BYTECODE.to_vec(),
116 order_book_configurables: OrderBookConfigurables::default(),
117 order_book_proxy_bytecode: ORDER_BOOK_PROXY_BYTECODE.to_vec(),
118 order_book_proxy_storage_slots: serde_json::from_slice(
119 ORDER_BOOK_PROXY_STORAGE,
120 )
121 .unwrap(),
122 order_book_proxy_configurables: OrderBookProxyConfigurables::default(),
123 order_book_whitelist_storage_slots: serde_json::from_slice(
124 ORDER_BOOK_WHITELIST_STORAGE,
125 )
126 .unwrap(),
127 order_book_blacklist_bytecode: ORDER_BOOK_BLACKLIST_BYTECODE.to_vec(),
128 order_book_blacklist_storage_slots: serde_json::from_slice(
129 ORDER_BOOK_BLACKLIST_STORAGE,
130 )
131 .unwrap(),
132 max_words_per_blob: 10_000,
133 proxy_owner: None,
134 order_book_owner: None,
135 salt: Salt::default(),
136 }
137 }
138}
139
140impl OrderBookDeployConfig {
141 pub fn with_configurables(order_book_configurables: OrderBookConfigurables) -> Self {
142 Self {
143 order_book_configurables,
144 ..OrderBookDeployConfig::default()
145 }
146 }
147}
148
149#[derive(Clone)]
152pub struct OrderBookDeploy<W: Account + Clone> {
153 pub order_book_proxy: OrderBookProxy<W>,
155 pub order_book: OrderBook<W>,
157 pub contract_id: ContractId,
159 pub base_asset: AssetId,
161 pub quote_asset: AssetId,
163 pub whitelist: Option<OrderBookWhitelist<W>>,
165 pub whitelist_id: Option<ContractId>,
167}
168
169impl<W: Account + Clone> OrderBookDeploy<W> {
170 pub fn new(
171 w: W,
172 contract_id: ContractId,
173 base_asset: AssetId,
174 quote_asset: AssetId,
175 ) -> Self {
176 let order_book = OrderBook::new(contract_id, w.clone());
177 let order_book_proxy = OrderBookProxy::new(contract_id, w.clone());
178 Self {
179 order_book,
180 order_book_proxy,
181 contract_id,
182 base_asset,
183 quote_asset,
184 whitelist: None,
185 whitelist_id: None,
186 }
187 }
188
189 pub async fn initialize(&self) -> Result<()> {
190 self.order_book_proxy
192 .methods()
193 .initialize_proxy()
194 .call()
195 .await?;
196 self.order_book.methods().initialize().call().await?;
198 Ok(())
199 }
200
201 pub async fn deploy_order_book_blacklist(
202 deployer_wallet: &W,
203 owner_identity: &Identity,
204 config: &OrderBookDeployConfig,
205 ) -> Result<OrderBookBlacklist<W>> {
206 let contract = Contract::regular(
207 config.order_book_blacklist_bytecode.clone(),
208 config.salt,
209 config.order_book_blacklist_storage_slots.clone(),
210 )
211 .with_configurables(
212 OrderBookBlacklistConfigurables::default()
213 .with_INITIAL_OWNER(State::Initialized(*owner_identity))?,
214 )
215 .with_salt(config.salt);
216 let contract_id = contract.contract_id();
217 let instance = OrderBookBlacklist::new(contract_id, deployer_wallet.clone());
218 let contract_exists = deployer_wallet
219 .try_provider()?
220 .contract_exists(&contract_id)
221 .await?;
222 if !contract_exists {
223 contract
224 .deploy(deployer_wallet, TxPolicies::default())
225 .await?;
226 instance.methods().initialize().call().await?;
227 }
228 Ok(instance)
229 }
230
231 pub async fn deploy_order_book_whitelist(
232 deployer_wallet: &W,
233 owner_identity: &Identity,
234 config: &OrderBookDeployConfig,
235 ) -> Result<OrderBookWhitelist<W>> {
236 let contract = Contract::regular(
237 config.order_book_whitelist_bytecode.clone(),
238 config.salt,
239 config.order_book_whitelist_storage_slots.clone(),
240 )
241 .with_configurables(
242 OrderBookWhitelistConfigurables::default()
243 .with_INITIAL_OWNER(State::Initialized(*owner_identity))?,
244 )
245 .with_salt(config.salt);
246 let contract_id = contract.contract_id();
247 let instance = OrderBookWhitelist::new(contract_id, deployer_wallet.clone());
248 let contract_exists = deployer_wallet
249 .try_provider()?
250 .contract_exists(&contract_id)
251 .await?;
252
253 if !contract_exists {
254 contract
255 .deploy(deployer_wallet, TxPolicies::default())
256 .await?;
257 instance.methods().initialize().call().await?;
258 }
259
260 Ok(instance)
261 }
262
263 pub async fn order_book_blob(
264 deployer_wallet: &W,
265 base_asset: AssetId,
266 quote_asset: AssetId,
267 config: &OrderBookDeployConfig,
268 ) -> Result<OrderBookBlob> {
269 let order_book_owner = config
270 .order_book_owner
271 .unwrap_or(Identity::Address(deployer_wallet.address()));
272 let configurables = config
274 .order_book_configurables
275 .clone()
276 .with_BASE_ASSET(base_asset)?
277 .with_QUOTE_ASSET(quote_asset)?
278 .with_INITIAL_OWNER(State::Initialized(order_book_owner))?;
279 let (data_blobs, loader_blob) = blob_loader::build_loader_blobs(
282 config.order_book_bytecode.clone(),
283 config.salt,
284 config.order_book_storage_slots.clone(),
285 configurables.clone(),
286 config.max_words_per_blob,
287 )?;
288 let loader_blob_id = loader_blob.id();
289 let loader_blob_exists = deployer_wallet
290 .try_provider()?
291 .blob_exists(loader_blob_id)
292 .await?;
293
294 Ok(OrderBookBlob {
295 id: loader_blob_id,
296 exists: loader_blob_exists,
297 blob: loader_blob,
298 data_blobs,
299 })
300 }
301
302 pub async fn deploy_order_book_blob(
313 deployer_wallet: &W,
314 base_asset: AssetId,
315 quote_asset: AssetId,
316 config: &OrderBookDeployConfig,
317 ) -> Result<BlobId> {
318 let order_book_blob =
319 Self::order_book_blob(deployer_wallet, base_asset, quote_asset, config)
320 .await?;
321 blob_loader::upload_loader_blobs(
322 deployer_wallet,
323 order_book_blob.data_blobs,
324 order_book_blob.blob,
325 )
326 .await
327 }
328
329 pub async fn deploy_order_book_proxy(
330 deployer_wallet: &W,
331 order_book_blob_id: &BlobId,
332 config: &OrderBookDeployConfig,
333 ) -> Result<(OrderBookProxy<W>, bool)> {
334 let proxy_owner = config
335 .proxy_owner
336 .unwrap_or(Identity::Address(deployer_wallet.address()));
337 let blob_id = ContractId::new(*order_book_blob_id);
338 let configurables = config
339 .order_book_proxy_configurables
340 .clone()
341 .with_INITIAL_TARGET(blob_id)?
342 .with_INITIAL_OWNER(State::Initialized(proxy_owner))?;
343 let contract = Contract::regular(
344 config.order_book_proxy_bytecode.clone(),
345 config.salt,
346 config.order_book_proxy_storage_slots.clone(),
347 )
348 .with_configurables(configurables);
349
350 let contract_id = contract.contract_id();
351 let already_deployed = deployer_wallet
352 .try_provider()?
353 .contract_exists(&contract_id)
354 .await?;
355 let order_book_proxy = OrderBookProxy::new(contract_id, deployer_wallet.clone());
356
357 if !already_deployed {
358 contract
359 .deploy(deployer_wallet, TxPolicies::default())
360 .await?;
361 }
362 let requires_initialization = !already_deployed;
363
364 Ok((order_book_proxy, requires_initialization))
365 }
366
367 pub async fn deploy(
384 deployer_wallet: &W,
385 base_asset: AssetId,
386 quote_asset: AssetId,
387 config: &OrderBookDeployConfig,
388 ) -> Result<OrderBookDeploy<W>> {
389 let (deploy, requires_initialization) = Self::deploy_without_initialization(
391 deployer_wallet,
392 base_asset,
393 quote_asset,
394 config,
395 )
396 .await?;
397
398 if requires_initialization {
399 deploy.initialize().await?;
401 }
402
403 Ok(deploy)
404 }
405
406 pub async fn deploy_without_initialization(
408 deployer_wallet: &W,
409 base_asset: AssetId,
410 quote_asset: AssetId,
411 config: &OrderBookDeployConfig,
412 ) -> Result<(OrderBookDeploy<W>, bool)> {
413 let order_book_blob_id = Self::deploy_order_book_blob(
414 deployer_wallet,
415 base_asset,
416 quote_asset,
417 config,
418 )
419 .await?;
420 let (deploy_order_book_proxy, requires_initialization) =
421 Self::deploy_order_book_proxy(deployer_wallet, &order_book_blob_id, config)
422 .await?;
423 let contract_id = deploy_order_book_proxy.contract_id();
424 let deploy = OrderBookDeploy::new(
425 deployer_wallet.clone(),
426 contract_id,
427 base_asset,
428 quote_asset,
429 );
430
431 Ok((deploy, requires_initialization))
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438 use fuels::test_helpers::{
439 WalletsConfig,
440 launch_custom_provider_and_get_wallets,
441 };
442
443 #[tokio::test]
444 async fn test_deploy_order_book_blob() {
445 let mut wallets = launch_custom_provider_and_get_wallets(
447 WalletsConfig::new(Some(2), Some(1), Some(1_000_000_000)),
448 None,
449 Some(::fuels::test_helpers::ChainConfig::local_testnet()),
450 )
451 .await
452 .unwrap();
453 let deployer_wallet = wallets.pop().unwrap();
454 let contract_owner = wallets.pop().unwrap();
455
456 let base_asset = AssetId::from([1u8; 32]);
458 let quote_asset = AssetId::from([2u8; 32]);
459 let config = OrderBookDeployConfig {
460 proxy_owner: Some(Identity::Address(contract_owner.address())),
461 order_book_owner: Some(Identity::Address(contract_owner.address())),
462 ..Default::default()
463 };
464
465 let order_book_blob_id = OrderBookDeploy::deploy_order_book_blob(
466 &deployer_wallet,
467 base_asset,
468 quote_asset,
469 &config,
470 )
471 .await
472 .unwrap();
473 let (order_book_deploy, requires_initialization) =
474 OrderBookDeploy::deploy_without_initialization(
475 &deployer_wallet,
476 base_asset,
477 quote_asset,
478 &config,
479 )
480 .await
481 .unwrap();
482 let order_book = order_book_deploy.order_book;
483 let order_book_proxy = order_book_deploy.order_book_proxy;
484
485 if requires_initialization {
486 order_book_proxy
487 .methods()
488 .initialize_proxy()
489 .call()
490 .await
491 .unwrap();
492 order_book.methods().initialize().call().await.unwrap();
493 }
494
495 assert_eq!(
496 order_book_proxy
497 .methods()
498 .proxy_owner()
499 .simulate(Execution::state_read_only())
500 .await
501 .unwrap()
502 .value,
503 State::Initialized(Identity::Address(contract_owner.address()))
504 );
505 assert_eq!(
506 order_book_proxy
507 .methods()
508 .proxy_target()
509 .simulate(Execution::state_read_only())
510 .await
511 .unwrap()
512 .value,
513 Some(ContractId::new(order_book_blob_id))
514 );
515 assert_eq!(
516 order_book
517 .methods()
518 .owner()
519 .simulate(Execution::state_read_only())
520 .await
521 .unwrap()
522 .value,
523 State::Initialized(Identity::Address(contract_owner.address()))
524 );
525 assert_eq!(
526 order_book
527 .methods()
528 .get_base_asset()
529 .simulate(Execution::state_read_only())
530 .await
531 .unwrap()
532 .value,
533 base_asset,
534 );
535 assert_eq!(
536 order_book
537 .methods()
538 .get_quote_asset()
539 .simulate(Execution::state_read_only())
540 .await
541 .unwrap()
542 .value,
543 quote_asset,
544 );
545
546 let new_base_asset = AssetId::from([3u8; 32]);
548 let order_book_blob_id = OrderBookDeploy::deploy_order_book_blob(
549 &deployer_wallet,
550 new_base_asset,
551 quote_asset,
552 &config,
553 )
554 .await
555 .unwrap();
556
557 order_book_proxy
558 .with_account(contract_owner)
559 .methods()
560 .set_proxy_target(ContractId::new(order_book_blob_id))
561 .call()
562 .await
563 .unwrap();
564 assert_eq!(
565 order_book
566 .methods()
567 .get_base_asset()
568 .simulate(Execution::state_read_only())
569 .await
570 .unwrap()
571 .value,
572 new_base_asset
573 );
574 }
575
576 #[tokio::test]
577 async fn test_order_book_deployment() {
578 let mut wallets = launch_custom_provider_and_get_wallets(
580 WalletsConfig::new(Some(2), Some(1), Some(1_000_000_000)),
581 None,
582 Some(::fuels::test_helpers::ChainConfig::local_testnet()),
583 )
584 .await
585 .unwrap();
586 let deployer_wallet = wallets.pop().unwrap();
587
588 let base_asset = AssetId::new([1u8; 32]);
590 let quote_asset = AssetId::new([2u8; 32]);
591 let config = OrderBookDeployConfig::default();
592
593 let deployment =
594 OrderBookDeploy::deploy(&deployer_wallet, base_asset, quote_asset, &config)
595 .await
596 .unwrap();
597
598 let provider = deployer_wallet.try_provider().unwrap();
600 let contract_exists = provider
601 .contract_exists(&deployment.contract_id)
602 .await
603 .unwrap();
604 assert!(contract_exists, "OrderBook contract should exist");
605
606 assert_eq!(deployment.base_asset, base_asset);
608 assert_eq!(deployment.quote_asset, quote_asset);
609 }
610}