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