1#![allow(clippy::manual_unwrap_or_default)]
2use anchor_lang::prelude::*;
3use bolt_component::CpiContextBuilder;
4use error::WorldError;
5use std::collections::BTreeSet;
6
7#[cfg(not(feature = "no-entrypoint"))]
8use solana_security_txt::security_txt;
9
10declare_id!("WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n");
11
12#[cfg(not(feature = "no-entrypoint"))]
13security_txt! {
14 name: "Bolt",
15 project_url: "https://magicblock.gg",
16 contacts: "email:dev@magicblock.gg,twitter:@magicblock",
17 policy: "",
18 preferred_languages: "en",
19 source_code: "https://github.com/magicblock-labs/bolt"
20}
21
22mod error;
23
24#[program]
25pub mod world {
26 use super::*;
27 use crate::error::WorldError;
28
29 pub fn initialize_registry(_ctx: Context<InitializeRegistry>) -> Result<()> {
30 Ok(())
31 }
32
33 pub fn initialize_new_world(ctx: Context<InitializeNewWorld>) -> Result<()> {
34 ctx.accounts.world.set_inner(World::default());
35 ctx.accounts.world.id = ctx.accounts.registry.worlds;
36 ctx.accounts.registry.worlds += 1;
37 Ok(())
38 }
39
40 #[allow(unused_variables)]
41 pub fn add_authority(ctx: Context<AddAuthority>, world_id: u64) -> Result<()> {
42 if ctx.accounts.world.authorities.is_empty()
43 || (ctx
44 .accounts
45 .world
46 .authorities
47 .contains(ctx.accounts.authority.key)
48 && !ctx
49 .accounts
50 .world
51 .authorities
52 .contains(ctx.accounts.new_authority.key))
53 {
54 ctx.accounts
55 .world
56 .authorities
57 .push(*ctx.accounts.new_authority.key);
58
59 let new_space = World::space_for_authorities(
60 ctx.accounts.world.authorities.len(),
61 ctx.accounts.world.systems.len(),
62 );
63
64 let rent = Rent::get()?;
66 let new_minimum_balance = rent.minimum_balance(new_space);
67 let lamports_diff =
68 new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
69 if lamports_diff > 0 {
70 anchor_lang::solana_program::program::invoke(
71 &anchor_lang::solana_program::system_instruction::transfer(
72 ctx.accounts.authority.key,
73 ctx.accounts.world.to_account_info().key,
74 lamports_diff,
75 ),
76 &[
77 ctx.accounts.authority.to_account_info(),
78 ctx.accounts.world.to_account_info(),
79 ctx.accounts.system_program.to_account_info(),
80 ],
81 )?;
82 }
83 ctx.accounts
84 .world
85 .to_account_info()
86 .realloc(new_space, false)?;
87 }
88 Ok(())
89 }
90
91 #[allow(unused_variables)]
92 pub fn remove_authority(ctx: Context<RemoveAuthority>, world_id: u64) -> Result<()> {
93 if !ctx
94 .accounts
95 .world
96 .authorities
97 .contains(ctx.accounts.authority.key)
98 {
99 return Err(WorldError::InvalidAuthority.into());
100 }
101 if let Some(index) = ctx
102 .accounts
103 .world
104 .authorities
105 .iter()
106 .position(|&x| x == *ctx.accounts.authority_to_delete.key)
107 {
108 ctx.accounts.world.authorities.remove(index);
109
110 let new_space = World::space_for_authorities(
111 ctx.accounts.world.authorities.len(),
112 ctx.accounts.world.systems.len(),
113 );
114
115 let rent = Rent::get()?;
117 let new_minimum_balance = rent.minimum_balance(new_space);
118 let lamports_diff =
119 new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
120 **ctx
121 .accounts
122 .world
123 .to_account_info()
124 .try_borrow_mut_lamports()? += lamports_diff;
125 **ctx
126 .accounts
127 .authority
128 .to_account_info()
129 .try_borrow_mut_lamports()? -= lamports_diff;
130 ctx.accounts
131 .world
132 .to_account_info()
133 .realloc(new_space, false)?;
134 Ok(())
135 } else {
136 Err(WorldError::AuthorityNotFound.into())
137 }
138 }
139
140 pub fn approve_system(ctx: Context<ApproveSystem>) -> Result<()> {
141 if !ctx.accounts.authority.is_signer {
142 return Err(WorldError::InvalidAuthority.into());
143 }
144 if !ctx
145 .accounts
146 .world
147 .authorities
148 .contains(ctx.accounts.authority.key)
149 {
150 return Err(WorldError::InvalidAuthority.into());
151 }
152 if ctx.accounts.world.permissionless {
153 ctx.accounts.world.permissionless = false;
154 }
155
156 let mut world_systems = ctx.accounts.world.systems();
157 world_systems
158 .approved_systems
159 .insert(ctx.accounts.system.key());
160
161 let encoded_world_systems = world_systems.try_to_vec()?;
162 ctx.accounts.world.systems = encoded_world_systems.clone();
163
164 let new_space = World::space_for_authorities(
165 ctx.accounts.world.authorities.len(),
166 encoded_world_systems.len(),
167 );
168
169 let rent = Rent::get()?;
171 let new_minimum_balance = rent.minimum_balance(new_space);
172 let lamports_diff =
173 new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
174 if lamports_diff > 0 {
175 anchor_lang::solana_program::program::invoke(
176 &anchor_lang::solana_program::system_instruction::transfer(
177 ctx.accounts.authority.key,
178 ctx.accounts.world.to_account_info().key,
179 lamports_diff,
180 ),
181 &[
182 ctx.accounts.authority.to_account_info(),
183 ctx.accounts.world.to_account_info(),
184 ctx.accounts.system_program.to_account_info(),
185 ],
186 )?;
187 }
188 ctx.accounts
189 .world
190 .to_account_info()
191 .realloc(new_space, false)?;
192 msg!("Approved system: {:?}", world_systems);
193 Ok(())
194 }
195
196 pub fn remove_system(ctx: Context<RemoveSystem>) -> Result<()> {
197 if !ctx.accounts.authority.is_signer {
198 return Err(WorldError::InvalidAuthority.into());
199 }
200 if !ctx
201 .accounts
202 .world
203 .authorities
204 .contains(ctx.accounts.authority.key)
205 {
206 return Err(WorldError::InvalidAuthority.into());
207 }
208
209 let mut world_systems = ctx.accounts.world.systems();
210 world_systems
211 .approved_systems
212 .remove(&ctx.accounts.system.key());
213
214 let encoded_world_systems = world_systems.try_to_vec()?;
215 ctx.accounts.world.systems = encoded_world_systems.clone();
216
217 let new_space = World::space_for_authorities(
218 ctx.accounts.world.authorities.len(),
219 encoded_world_systems.len(),
220 );
221
222 if world_systems.approved_systems.is_empty() {
223 ctx.accounts.world.permissionless = true;
224 }
225
226 let rent = Rent::get()?;
228 let new_minimum_balance = rent.minimum_balance(new_space);
229 let lamports_diff =
230 new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
231 **ctx
232 .accounts
233 .world
234 .to_account_info()
235 .try_borrow_mut_lamports()? += lamports_diff;
236 **ctx
237 .accounts
238 .authority
239 .to_account_info()
240 .try_borrow_mut_lamports()? -= lamports_diff;
241 ctx.accounts
242 .world
243 .to_account_info()
244 .realloc(new_space, false)?;
245 msg!("Approved system: {:?}", world_systems);
246 Ok(())
247 }
248
249 #[allow(unused_variables)]
250 pub fn add_entity(ctx: Context<AddEntity>, extra_seed: Option<Vec<u8>>) -> Result<()> {
251 require!(
252 ctx.accounts.world.key() == ctx.accounts.world.pda().0,
253 WorldError::WorldAccountMismatch
254 );
255 ctx.accounts.entity.id = ctx.accounts.world.entities;
256 ctx.accounts.world.entities += 1;
257 Ok(())
258 }
259
260 pub fn initialize_component(ctx: Context<InitializeComponent>) -> Result<()> {
261 if !ctx.accounts.authority.is_signer && ctx.accounts.authority.key != &ID {
262 return Err(WorldError::InvalidAuthority.into());
263 }
264 bolt_component::cpi::initialize(ctx.accounts.build(&[World::cpi_auth_seeds().as_slice()]))?;
265 Ok(())
266 }
267
268 pub fn destroy_component(ctx: Context<DestroyComponent>) -> Result<()> {
269 bolt_component::cpi::destroy(ctx.accounts.build(&[World::cpi_auth_seeds().as_slice()]))?;
270 Ok(())
271 }
272
273 pub fn apply<'info>(
274 ctx: Context<'_, '_, '_, 'info, Apply<'info>>,
275 args: Vec<u8>,
276 ) -> Result<()> {
277 let (pairs, results) = apply_impl(
278 &ctx.accounts.authority,
279 &ctx.accounts.world,
280 &ctx.accounts.bolt_system,
281 ctx.accounts.build(),
282 args,
283 ctx.remaining_accounts.to_vec(),
284 )?;
285 for ((program, component), result) in pairs.into_iter().zip(results.into_iter()) {
286 bolt_component::cpi::update(
287 build_update_context(
288 program,
289 component,
290 ctx.accounts.authority.clone(),
291 ctx.accounts.cpi_auth.clone(),
292 &[World::cpi_auth_seeds().as_slice()],
293 ),
294 result,
295 )?;
296 }
297 Ok(())
298 }
299
300 #[derive(Accounts)]
301 pub struct Apply<'info> {
302 #[account()]
304 pub bolt_system: UncheckedAccount<'info>,
305 #[account()]
307 pub authority: Signer<'info>,
308 #[account()]
309 pub cpi_auth: UncheckedAccount<'info>,
311 #[account()]
312 pub world: Account<'info, World>,
313 }
314
315 impl<'info> Apply<'info> {
316 pub fn build(
317 &self,
318 ) -> CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::BoltExecute<'info>> {
319 let cpi_program = self.bolt_system.to_account_info();
320 let cpi_accounts = bolt_system::cpi::accounts::BoltExecute {
321 authority: self.authority.to_account_info(),
322 };
323 CpiContext::new(cpi_program, cpi_accounts)
324 }
325 }
326
327 pub fn apply_with_session<'info>(
328 ctx: Context<'_, '_, '_, 'info, ApplyWithSession<'info>>,
329 args: Vec<u8>,
330 ) -> Result<()> {
331 let (pairs, results) = apply_impl(
332 &ctx.accounts.authority,
333 &ctx.accounts.world,
334 &ctx.accounts.bolt_system,
335 ctx.accounts.build(),
336 args,
337 ctx.remaining_accounts.to_vec(),
338 )?;
339 for ((program, component), result) in pairs.into_iter().zip(results.into_iter()) {
340 bolt_component::cpi::update_with_session(
341 build_update_context_with_session(
342 program,
343 component,
344 ctx.accounts.authority.clone(),
345 ctx.accounts.cpi_auth.clone(),
346 ctx.accounts.session_token.clone(),
347 &[World::cpi_auth_seeds().as_slice()],
348 ),
349 result,
350 )?;
351 }
352 Ok(())
353 }
354
355 #[derive(Accounts)]
356 pub struct ApplyWithSession<'info> {
357 #[account()]
359 pub bolt_system: UncheckedAccount<'info>,
360 #[account()]
362 pub authority: Signer<'info>,
363 #[account()]
364 pub cpi_auth: UncheckedAccount<'info>,
366 #[account()]
367 pub world: Account<'info, World>,
368 #[account()]
369 pub session_token: UncheckedAccount<'info>,
371 }
372
373 impl<'info> ApplyWithSession<'info> {
374 pub fn build(
375 &self,
376 ) -> CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::BoltExecute<'info>> {
377 let cpi_program = self.bolt_system.to_account_info();
378 let cpi_accounts = bolt_system::cpi::accounts::BoltExecute {
379 authority: self.authority.to_account_info(),
380 };
381 CpiContext::new(cpi_program, cpi_accounts)
382 }
383 }
384}
385
386#[allow(clippy::type_complexity)]
387fn apply_impl<'info>(
388 authority: &Signer<'info>,
389 world: &Account<'info, World>,
390 bolt_system: &UncheckedAccount<'info>,
391 cpi_context: CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::BoltExecute<'info>>,
392 args: Vec<u8>,
393 mut remaining_accounts: Vec<AccountInfo<'info>>,
394) -> Result<(Vec<(AccountInfo<'info>, AccountInfo<'info>)>, Vec<Vec<u8>>)> {
395 if !authority.is_signer && authority.key != &ID {
396 return Err(WorldError::InvalidAuthority.into());
397 }
398 if !world.permissionless
399 && !world
400 .systems()
401 .approved_systems
402 .contains(&bolt_system.key())
403 {
404 return Err(WorldError::SystemNotApproved.into());
405 }
406
407 let mut pairs = Vec::new();
408 while remaining_accounts.len() >= 2 {
409 let program = remaining_accounts.remove(0);
410 if program.key() == ID {
411 break;
412 }
413 let component = remaining_accounts.remove(0);
414 pairs.push((program, component));
415 }
416
417 let mut components_accounts = pairs
418 .iter()
419 .map(|(_, component)| component)
420 .cloned()
421 .collect::<Vec<_>>();
422 components_accounts.append(&mut remaining_accounts);
423 let remaining_accounts = components_accounts;
424
425 let results = bolt_system::cpi::bolt_execute(
426 cpi_context.with_remaining_accounts(remaining_accounts),
427 args,
428 )?
429 .get();
430
431 if results.len() != pairs.len() {
432 return Err(WorldError::InvalidSystemOutput.into());
433 }
434 Ok((pairs, results))
435}
436
437#[derive(Accounts)]
438pub struct InitializeRegistry<'info> {
439 #[account(init, payer = payer, space = Registry::size(), seeds = [Registry::seed()], bump)]
440 pub registry: Account<'info, Registry>,
441 #[account(mut)]
442 pub payer: Signer<'info>,
443 pub system_program: Program<'info, System>,
444}
445
446#[derive(Accounts)]
447pub struct InitializeNewWorld<'info> {
448 #[account(mut)]
449 pub payer: Signer<'info>,
450 #[account(init, payer = payer, space = World::size(), seeds = [World::seed(), ®istry.worlds.to_be_bytes()], bump)]
451 pub world: Account<'info, World>,
452 #[account(mut, address = Registry::pda().0)]
453 pub registry: Account<'info, Registry>,
454 pub system_program: Program<'info, System>,
455}
456
457#[derive(Accounts)]
458#[instruction(world_id: u64)]
459pub struct AddAuthority<'info> {
460 #[account(mut)]
461 pub authority: Signer<'info>,
462 #[account()]
463 pub new_authority: AccountInfo<'info>,
465 #[account(mut, seeds = [World::seed(), &world_id.to_be_bytes()], bump)]
466 pub world: Account<'info, World>,
467 pub system_program: Program<'info, System>,
468}
469
470#[derive(Accounts)]
471#[instruction(world_id: u64)]
472pub struct RemoveAuthority<'info> {
473 #[account(mut)]
474 pub authority: Signer<'info>,
475 #[account()]
476 pub authority_to_delete: AccountInfo<'info>,
478 #[account(mut, seeds = [World::seed(), &world_id.to_be_bytes()], bump)]
479 pub world: Account<'info, World>,
480 pub system_program: Program<'info, System>,
481}
482
483#[derive(Accounts)]
484pub struct ApproveSystem<'info> {
485 #[account(mut)]
486 pub authority: Signer<'info>,
487 #[account(mut)]
488 pub world: Account<'info, World>,
489 pub system: AccountInfo<'info>,
491 pub system_program: Program<'info, System>,
492}
493
494#[derive(Accounts)]
495pub struct RemoveSystem<'info> {
496 #[account(mut)]
497 pub authority: Signer<'info>,
498 #[account(mut)]
499 pub world: Account<'info, World>,
500 pub system: AccountInfo<'info>,
502 pub system_program: Program<'info, System>,
503}
504
505#[derive(Accounts)]
506#[instruction(extra_seed: Option<Vec<u8>>)]
507pub struct AddEntity<'info> {
508 #[account(mut)]
509 pub payer: Signer<'info>,
510 #[account(init, payer = payer, space = World::size(), seeds = [Entity::seed(), &world.id.to_be_bytes(),
511 &match extra_seed {
512 Some(ref _seed) => [0; 8],
513 None => world.entities.to_be_bytes()
514 },
515 match extra_seed {
516 Some(ref seed) => seed,
517 None => &[],
518 }], bump)]
519 pub entity: Account<'info, Entity>,
520 #[account(mut)]
521 pub world: Account<'info, World>,
522 pub system_program: Program<'info, System>,
523}
524
525#[derive(Accounts)]
526pub struct InitializeComponent<'info> {
527 #[account(mut)]
528 pub payer: Signer<'info>,
529 #[account(mut)]
530 pub data: AccountInfo<'info>,
532 #[account()]
533 pub entity: Account<'info, Entity>,
534 pub component_program: AccountInfo<'info>,
536 pub authority: AccountInfo<'info>,
538 #[account()]
539 pub cpi_auth: UncheckedAccount<'info>,
541 pub system_program: Program<'info, System>,
542}
543
544impl<'info> InitializeComponent<'info> {
545 pub fn build<'a, 'b, 'c>(
546 &self,
547 signer_seeds: &'a [&'b [&'c [u8]]],
548 ) -> CpiContext<'a, 'b, 'c, 'info, bolt_component::cpi::accounts::Initialize<'info>> {
549 let cpi_program = self.component_program.to_account_info();
550
551 let cpi_accounts = bolt_component::cpi::accounts::Initialize {
552 payer: self.payer.to_account_info(),
553 data: self.data.to_account_info(),
554 entity: self.entity.to_account_info(),
555 authority: self.authority.to_account_info(),
556 cpi_auth: self.cpi_auth.to_account_info(),
557 system_program: self.system_program.to_account_info(),
558 };
559 CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds)
560 }
561}
562
563#[derive(Accounts)]
564pub struct DestroyComponent<'info> {
565 #[account(mut)]
566 pub authority: Signer<'info>,
567 #[account(mut)]
568 pub receiver: AccountInfo<'info>,
570 pub component_program: AccountInfo<'info>,
572 pub component_program_data: AccountInfo<'info>,
574 #[account()]
575 pub entity: Account<'info, Entity>,
576 #[account(mut)]
577 pub component: UncheckedAccount<'info>,
579 #[account()]
580 pub cpi_auth: UncheckedAccount<'info>,
582 pub system_program: Program<'info, System>,
583}
584
585impl<'info> DestroyComponent<'info> {
586 pub fn build<'a, 'b, 'c>(
587 &self,
588 signer_seeds: &'a [&'b [&'c [u8]]],
589 ) -> CpiContext<'a, 'b, 'c, 'info, bolt_component::cpi::accounts::Destroy<'info>> {
590 let cpi_program = self.component_program.to_account_info();
591
592 let cpi_accounts = bolt_component::cpi::accounts::Destroy {
593 authority: self.authority.to_account_info(),
594 receiver: self.receiver.to_account_info(),
595 entity: self.entity.to_account_info(),
596 component: self.component.to_account_info(),
597 component_program_data: self.component_program_data.to_account_info(),
598 cpi_auth: self.cpi_auth.to_account_info(),
599 system_program: self.system_program.to_account_info(),
600 };
601 CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds)
602 }
603}
604
605#[account]
606#[derive(InitSpace, Default, Copy)]
607pub struct Registry {
608 pub worlds: u64,
609}
610
611impl Registry {
612 pub fn seed() -> &'static [u8] {
613 b"registry"
614 }
615
616 pub fn size() -> usize {
617 8 + Registry::INIT_SPACE
618 }
619
620 pub fn pda() -> (Pubkey, u8) {
621 Pubkey::find_program_address(&[Registry::seed()], &crate::ID)
622 }
623}
624
625#[account]
626#[derive(Debug)]
627pub struct World {
628 pub id: u64,
629 pub entities: u64,
630 pub authorities: Vec<Pubkey>,
631 pub permissionless: bool,
632 pub systems: Vec<u8>,
633}
634
635impl Default for World {
636 fn default() -> Self {
637 Self {
638 id: 0,
639 entities: 0,
640 authorities: Vec::new(),
641 permissionless: true,
642 systems: Vec::new(),
643 }
644 }
645}
646
647impl World {
648 fn space_for_authorities(auths: usize, systems_space: usize) -> usize {
649 16 + 8 + 32 * auths + 1 + 8 + systems_space
650 }
651
652 pub fn systems(&self) -> WorldSystems {
653 if self.permissionless {
654 return WorldSystems::default();
655 }
656 WorldSystems::try_from_slice(self.systems.as_ref()).unwrap_or_default()
657 }
658}
659
660#[derive(
661 anchor_lang::prelude::borsh::BorshSerialize,
662 anchor_lang::prelude::borsh::BorshDeserialize,
663 Default,
664 Debug,
665)]
666pub struct WorldSystems {
667 pub approved_systems: BTreeSet<Pubkey>,
668}
669
670impl World {
671 pub fn seed() -> &'static [u8] {
672 b"world"
673 }
674
675 pub fn size() -> usize {
676 16 + 8 + 1 + 8
677 }
678
679 pub fn pda(&self) -> (Pubkey, u8) {
680 Pubkey::find_program_address(&[World::seed(), &self.id.to_be_bytes()], &crate::ID)
681 }
682
683 pub fn cpi_auth_seeds() -> [&'static [u8]; 2] {
684 [b"cpi_auth", &[251]] }
686
687 pub const fn cpi_auth_address() -> Pubkey {
688 Pubkey::from_str_const("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi") }
690}
691
692#[account]
693#[derive(InitSpace, Default, Copy)]
694pub struct Entity {
695 pub id: u64,
696}
697
698impl Entity {
699 pub fn seed() -> &'static [u8] {
700 b"entity"
701 }
702}
703
704#[account]
705#[derive(InitSpace, Default)]
706pub struct SystemWhitelist {}
707
708impl SystemWhitelist {
709 pub fn seed() -> &'static [u8] {
710 b"whitelist"
711 }
712
713 pub fn size() -> usize {
714 8 + Registry::INIT_SPACE
715 }
716}
717
718pub fn build_update_context<'a, 'b, 'c, 'info>(
720 component_program: AccountInfo<'info>,
721 bolt_component: AccountInfo<'info>,
722 authority: Signer<'info>,
723 cpi_auth: UncheckedAccount<'info>,
724 signer_seeds: &'a [&'b [&'c [u8]]],
725) -> CpiContext<'a, 'b, 'c, 'info, bolt_component::cpi::accounts::Update<'info>> {
726 let authority = authority.to_account_info();
727 let cpi_auth = cpi_auth.to_account_info();
728 let cpi_program = component_program;
729 bolt_component::cpi::accounts::Update {
730 bolt_component,
731 authority,
732 cpi_auth,
733 }
734 .build_cpi_context(cpi_program, signer_seeds)
735}
736
737pub fn build_update_context_with_session<'a, 'b, 'c, 'info>(
739 component_program: AccountInfo<'info>,
740 bolt_component: AccountInfo<'info>,
741 authority: Signer<'info>,
742 cpi_auth: UncheckedAccount<'info>,
743 session_token: UncheckedAccount<'info>,
744 signer_seeds: &'a [&'b [&'c [u8]]],
745) -> CpiContext<'a, 'b, 'c, 'info, bolt_component::cpi::accounts::UpdateWithSession<'info>> {
746 let authority = authority.to_account_info();
747 let cpi_auth = cpi_auth.to_account_info();
748 let cpi_program = component_program;
749 let session_token = session_token.to_account_info();
750 bolt_component::cpi::accounts::UpdateWithSession {
751 bolt_component,
752 authority,
753 cpi_auth,
754 session_token,
755 }
756 .build_cpi_context(cpi_program, signer_seeds)
757}