lib.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. use anchor_lang::prelude::*;
  2. use bolt_helpers_world_apply::apply_system;
  3. use std::collections::BTreeSet;
  4. use tuple_conv::RepeatedTuple;
  5. #[cfg(not(feature = "no-entrypoint"))]
  6. use solana_security_txt::security_txt;
  7. declare_id!("WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n");
  8. #[cfg(not(feature = "no-entrypoint"))]
  9. security_txt! {
  10. name: "Bolt",
  11. project_url: "https://magicblock.gg",
  12. contacts: "email:dev@magicblock.gg,twitter:@magicblock",
  13. policy: "",
  14. preferred_languages: "en",
  15. source_code: "https://github.com/magicblock-labs/bolt"
  16. }
  17. mod error;
  18. #[apply_system(max_components = 5)]
  19. #[program]
  20. pub mod world {
  21. use super::*;
  22. use crate::error::WorldError;
  23. pub fn initialize_registry(_ctx: Context<InitializeRegistry>) -> Result<()> {
  24. Ok(())
  25. }
  26. pub fn initialize_new_world(ctx: Context<InitializeNewWorld>) -> Result<()> {
  27. ctx.accounts.world.set_inner(World::default());
  28. ctx.accounts.world.id = ctx.accounts.registry.worlds;
  29. ctx.accounts.registry.worlds += 1;
  30. Ok(())
  31. }
  32. #[allow(unused_variables)]
  33. pub fn add_authority(ctx: Context<AddAuthority>, world_id: u64) -> Result<()> {
  34. if ctx.accounts.world.authorities.is_empty()
  35. || (ctx
  36. .accounts
  37. .world
  38. .authorities
  39. .contains(ctx.accounts.authority.key)
  40. && !ctx
  41. .accounts
  42. .world
  43. .authorities
  44. .contains(ctx.accounts.new_authority.key))
  45. {
  46. ctx.accounts
  47. .world
  48. .authorities
  49. .push(*ctx.accounts.new_authority.key);
  50. let new_space = World::space_for_authorities(
  51. ctx.accounts.world.authorities.len(),
  52. ctx.accounts.world.systems.len(),
  53. );
  54. // Transfer to make it rent exempt
  55. let rent = Rent::get()?;
  56. let new_minimum_balance = rent.minimum_balance(new_space);
  57. let lamports_diff =
  58. new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
  59. if lamports_diff > 0 {
  60. anchor_lang::solana_program::program::invoke(
  61. &anchor_lang::solana_program::system_instruction::transfer(
  62. ctx.accounts.authority.key,
  63. ctx.accounts.world.to_account_info().key,
  64. lamports_diff,
  65. ),
  66. &[
  67. ctx.accounts.authority.to_account_info(),
  68. ctx.accounts.world.to_account_info(),
  69. ctx.accounts.system_program.to_account_info(),
  70. ],
  71. )?;
  72. }
  73. ctx.accounts
  74. .world
  75. .to_account_info()
  76. .realloc(new_space, false)?;
  77. }
  78. Ok(())
  79. }
  80. #[allow(unused_variables)]
  81. pub fn remove_authority(ctx: Context<RemoveAuthority>, world_id: u64) -> Result<()> {
  82. if !ctx
  83. .accounts
  84. .world
  85. .authorities
  86. .contains(ctx.accounts.authority.key)
  87. {
  88. return Err(WorldError::InvalidAuthority.into());
  89. }
  90. if let Some(index) = ctx
  91. .accounts
  92. .world
  93. .authorities
  94. .iter()
  95. .position(|&x| x == *ctx.accounts.authority_to_delete.key)
  96. {
  97. ctx.accounts.world.authorities.remove(index);
  98. let new_space = World::space_for_authorities(
  99. ctx.accounts.world.authorities.len(),
  100. ctx.accounts.world.systems.len(),
  101. );
  102. // Remove the extra rent
  103. let rent = Rent::get()?;
  104. let new_minimum_balance = rent.minimum_balance(new_space);
  105. let lamports_diff =
  106. new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
  107. **ctx
  108. .accounts
  109. .world
  110. .to_account_info()
  111. .try_borrow_mut_lamports()? += lamports_diff;
  112. **ctx
  113. .accounts
  114. .authority
  115. .to_account_info()
  116. .try_borrow_mut_lamports()? -= lamports_diff;
  117. ctx.accounts
  118. .world
  119. .to_account_info()
  120. .realloc(new_space, false)?;
  121. Ok(())
  122. } else {
  123. Err(WorldError::AuthorityNotFound.into())
  124. }
  125. }
  126. pub fn approve_system(ctx: Context<ApproveSystem>) -> Result<()> {
  127. if !ctx.accounts.authority.is_signer {
  128. return Err(WorldError::InvalidAuthority.into());
  129. }
  130. if !ctx
  131. .accounts
  132. .world
  133. .authorities
  134. .contains(ctx.accounts.authority.key)
  135. {
  136. return Err(WorldError::InvalidAuthority.into());
  137. }
  138. if ctx.accounts.world.permissionless {
  139. ctx.accounts.world.permissionless = false;
  140. }
  141. let mut world_systems = ctx.accounts.world.systems();
  142. world_systems
  143. .approved_systems
  144. .insert(ctx.accounts.system.key());
  145. let encoded_world_systems = world_systems.try_to_vec()?;
  146. ctx.accounts.world.systems = encoded_world_systems.clone();
  147. let new_space = World::space_for_authorities(
  148. ctx.accounts.world.authorities.len(),
  149. encoded_world_systems.len(),
  150. );
  151. // Transfer to make it rent exempt
  152. let rent = Rent::get()?;
  153. let new_minimum_balance = rent.minimum_balance(new_space);
  154. let lamports_diff =
  155. new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
  156. if lamports_diff > 0 {
  157. anchor_lang::solana_program::program::invoke(
  158. &anchor_lang::solana_program::system_instruction::transfer(
  159. ctx.accounts.authority.key,
  160. ctx.accounts.world.to_account_info().key,
  161. lamports_diff,
  162. ),
  163. &[
  164. ctx.accounts.authority.to_account_info(),
  165. ctx.accounts.world.to_account_info(),
  166. ctx.accounts.system_program.to_account_info(),
  167. ],
  168. )?;
  169. }
  170. ctx.accounts
  171. .world
  172. .to_account_info()
  173. .realloc(new_space, false)?;
  174. msg!("Approved system: {:?}", world_systems);
  175. Ok(())
  176. }
  177. pub fn remove_system(ctx: Context<RemoveSystem>) -> Result<()> {
  178. if !ctx.accounts.authority.is_signer {
  179. return Err(WorldError::InvalidAuthority.into());
  180. }
  181. if !ctx
  182. .accounts
  183. .world
  184. .authorities
  185. .contains(ctx.accounts.authority.key)
  186. {
  187. return Err(WorldError::InvalidAuthority.into());
  188. }
  189. let mut world_systems = ctx.accounts.world.systems();
  190. world_systems
  191. .approved_systems
  192. .remove(&ctx.accounts.system.key());
  193. let encoded_world_systems = world_systems.try_to_vec()?;
  194. ctx.accounts.world.systems = encoded_world_systems.clone();
  195. let new_space = World::space_for_authorities(
  196. ctx.accounts.world.authorities.len(),
  197. encoded_world_systems.len(),
  198. );
  199. if world_systems.approved_systems.is_empty() {
  200. ctx.accounts.world.permissionless = true;
  201. }
  202. // Remove the extra rent
  203. let rent = Rent::get()?;
  204. let new_minimum_balance = rent.minimum_balance(new_space);
  205. let lamports_diff =
  206. new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
  207. **ctx
  208. .accounts
  209. .world
  210. .to_account_info()
  211. .try_borrow_mut_lamports()? += lamports_diff;
  212. **ctx
  213. .accounts
  214. .authority
  215. .to_account_info()
  216. .try_borrow_mut_lamports()? -= lamports_diff;
  217. ctx.accounts
  218. .world
  219. .to_account_info()
  220. .realloc(new_space, false)?;
  221. msg!("Approved system: {:?}", world_systems);
  222. Ok(())
  223. }
  224. #[allow(unused_variables)]
  225. pub fn add_entity(ctx: Context<AddEntity>, extra_seed: Option<String>) -> Result<()> {
  226. require!(
  227. ctx.accounts.world.key() == ctx.accounts.world.pda().0,
  228. WorldError::WorldAccountMismatch
  229. );
  230. ctx.accounts.entity.id = ctx.accounts.world.entities;
  231. ctx.accounts.world.entities += 1;
  232. Ok(())
  233. }
  234. pub fn initialize_component(ctx: Context<InitializeComponent>) -> Result<()> {
  235. if !ctx.accounts.authority.is_signer && ctx.accounts.authority.key != &ID {
  236. return Err(WorldError::InvalidAuthority.into());
  237. }
  238. bolt_component::cpi::initialize(ctx.accounts.build())?;
  239. Ok(())
  240. }
  241. pub fn apply<'info>(
  242. ctx: Context<'_, '_, '_, 'info, ApplySystem<'info>>,
  243. args: Vec<u8>,
  244. ) -> Result<()> {
  245. if !ctx.accounts.authority.is_signer && ctx.accounts.authority.key != &ID {
  246. return Err(WorldError::InvalidAuthority.into());
  247. }
  248. if !ctx.accounts.world.permissionless
  249. && !ctx
  250. .accounts
  251. .world
  252. .systems()
  253. .approved_systems
  254. .contains(&ctx.accounts.bolt_system.key())
  255. {
  256. return Err(WorldError::SystemNotApproved.into());
  257. }
  258. let remaining_accounts: Vec<AccountInfo<'info>> = ctx.remaining_accounts.to_vec();
  259. let res = bolt_system::cpi::execute(
  260. ctx.accounts
  261. .build()
  262. .with_remaining_accounts(remaining_accounts),
  263. args,
  264. )?;
  265. bolt_component::cpi::update(
  266. build_update_context(
  267. ctx.accounts.component_program.clone(),
  268. ctx.accounts.bolt_component.clone(),
  269. ctx.accounts.authority.clone(),
  270. ctx.accounts.instruction_sysvar_account.clone(),
  271. ),
  272. res.get(),
  273. )?;
  274. Ok(())
  275. }
  276. #[derive(Accounts)]
  277. pub struct ApplySystem<'info> {
  278. /// CHECK: bolt component program check
  279. pub component_program: UncheckedAccount<'info>,
  280. /// CHECK: bolt system program check
  281. pub bolt_system: UncheckedAccount<'info>,
  282. #[account(mut)]
  283. /// CHECK: component account
  284. pub bolt_component: UncheckedAccount<'info>,
  285. /// CHECK: authority check
  286. pub authority: Signer<'info>,
  287. #[account(address = anchor_lang::solana_program::sysvar::instructions::id())]
  288. /// CHECK: instruction sysvar check
  289. pub instruction_sysvar_account: UncheckedAccount<'info>,
  290. #[account()]
  291. pub world: Account<'info, World>,
  292. }
  293. impl<'info> ApplySystem<'info> {
  294. pub fn build(
  295. &self,
  296. ) -> CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::SetData<'info>> {
  297. let cpi_program = self.bolt_system.to_account_info();
  298. let cpi_accounts = bolt_system::cpi::accounts::SetData {
  299. component: self.bolt_component.to_account_info(),
  300. authority: self.authority.to_account_info(),
  301. };
  302. CpiContext::new(cpi_program, cpi_accounts)
  303. }
  304. }
  305. }
  306. #[derive(Accounts)]
  307. pub struct InitializeRegistry<'info> {
  308. #[account(init, payer = payer, space = Registry::size(), seeds = [Registry::seed()], bump)]
  309. pub registry: Account<'info, Registry>,
  310. #[account(mut)]
  311. pub payer: Signer<'info>,
  312. pub system_program: Program<'info, System>,
  313. }
  314. #[derive(Accounts)]
  315. pub struct InitializeNewWorld<'info> {
  316. #[account(mut)]
  317. pub payer: Signer<'info>,
  318. #[account(init, payer = payer, space = World::size(), seeds = [World::seed(), &registry.worlds.to_be_bytes()], bump)]
  319. pub world: Account<'info, World>,
  320. #[account(mut, address = Registry::pda().0)]
  321. pub registry: Account<'info, Registry>,
  322. pub system_program: Program<'info, System>,
  323. }
  324. #[derive(Accounts)]
  325. #[instruction(world_id: u64)]
  326. pub struct AddAuthority<'info> {
  327. #[account(mut)]
  328. pub authority: Signer<'info>,
  329. #[account()]
  330. /// CHECK: new authority check
  331. pub new_authority: AccountInfo<'info>,
  332. #[account(mut, seeds = [World::seed(), &world_id.to_be_bytes()], bump)]
  333. pub world: Account<'info, World>,
  334. pub system_program: Program<'info, System>,
  335. }
  336. #[derive(Accounts)]
  337. #[instruction(world_id: u64)]
  338. pub struct RemoveAuthority<'info> {
  339. #[account(mut)]
  340. pub authority: Signer<'info>,
  341. #[account()]
  342. /// CHECK: new authority check
  343. pub authority_to_delete: AccountInfo<'info>,
  344. #[account(mut, seeds = [World::seed(), &world_id.to_be_bytes()], bump)]
  345. pub world: Account<'info, World>,
  346. pub system_program: Program<'info, System>,
  347. }
  348. #[derive(Accounts)]
  349. pub struct ApproveSystem<'info> {
  350. #[account(mut)]
  351. pub authority: Signer<'info>,
  352. #[account(mut)]
  353. pub world: Account<'info, World>,
  354. /// CHECK: Used for the pda derivation
  355. pub system: AccountInfo<'info>,
  356. pub system_program: Program<'info, System>,
  357. }
  358. #[derive(Accounts)]
  359. pub struct RemoveSystem<'info> {
  360. #[account(mut)]
  361. pub authority: Signer<'info>,
  362. #[account(mut)]
  363. pub world: Account<'info, World>,
  364. /// CHECK: Used for the pda derivation
  365. pub system: AccountInfo<'info>,
  366. pub system_program: Program<'info, System>,
  367. }
  368. #[derive(Accounts)]
  369. #[instruction(extra_seed: Option<String>)]
  370. pub struct AddEntity<'info> {
  371. #[account(mut)]
  372. pub payer: Signer<'info>,
  373. #[account(init, payer = payer, space = World::size(), seeds = [Entity::seed(), &world.id.to_be_bytes(),
  374. &match extra_seed {
  375. Some(ref seed) => [0; 8],
  376. None => world.entities.to_be_bytes()
  377. },
  378. match extra_seed {
  379. Some(ref seed) => seed.as_bytes(),
  380. None => &[],
  381. }], bump)]
  382. pub entity: Account<'info, Entity>,
  383. #[account(mut)]
  384. pub world: Account<'info, World>,
  385. pub system_program: Program<'info, System>,
  386. }
  387. #[derive(Accounts)]
  388. pub struct InitializeComponent<'info> {
  389. #[account(mut)]
  390. pub payer: Signer<'info>,
  391. #[account(mut)]
  392. /// CHECK: component data check
  393. pub data: AccountInfo<'info>,
  394. #[account()]
  395. pub entity: Account<'info, Entity>,
  396. /// CHECK: component program check
  397. pub component_program: AccountInfo<'info>,
  398. /// CHECK: authority check
  399. pub authority: AccountInfo<'info>,
  400. #[account(address = anchor_lang::solana_program::sysvar::instructions::id())]
  401. /// CHECK: instruction sysvar check
  402. pub instruction_sysvar_account: UncheckedAccount<'info>,
  403. pub system_program: Program<'info, System>,
  404. }
  405. impl<'info> InitializeComponent<'info> {
  406. pub fn build(
  407. &self,
  408. ) -> CpiContext<'_, '_, '_, 'info, bolt_component::cpi::accounts::Initialize<'info>> {
  409. let cpi_program = self.component_program.to_account_info();
  410. let cpi_accounts = bolt_component::cpi::accounts::Initialize {
  411. payer: self.payer.to_account_info(),
  412. data: self.data.to_account_info(),
  413. entity: self.entity.to_account_info(),
  414. authority: self.authority.to_account_info(),
  415. instruction_sysvar_account: self.instruction_sysvar_account.to_account_info(),
  416. system_program: self.system_program.to_account_info(),
  417. };
  418. CpiContext::new(cpi_program, cpi_accounts)
  419. }
  420. }
  421. #[account]
  422. #[derive(InitSpace, Default, Copy)]
  423. pub struct Registry {
  424. pub worlds: u64,
  425. }
  426. impl Registry {
  427. pub fn seed() -> &'static [u8] {
  428. b"registry"
  429. }
  430. pub fn size() -> usize {
  431. 8 + Registry::INIT_SPACE
  432. }
  433. pub fn pda() -> (Pubkey, u8) {
  434. Pubkey::find_program_address(&[Registry::seed()], &crate::ID)
  435. }
  436. }
  437. #[account]
  438. #[derive(Debug)]
  439. pub struct World {
  440. pub id: u64,
  441. pub entities: u64,
  442. pub authorities: Vec<Pubkey>,
  443. pub permissionless: bool,
  444. pub systems: Vec<u8>,
  445. }
  446. impl Default for World {
  447. fn default() -> Self {
  448. Self {
  449. id: 0,
  450. entities: 0,
  451. authorities: Vec::new(),
  452. permissionless: true,
  453. systems: Vec::new(),
  454. }
  455. }
  456. }
  457. impl World {
  458. fn space_for_authorities(auths: usize, systems_space: usize) -> usize {
  459. 16 + 8 + 32 * auths + 1 + 8 + systems_space
  460. }
  461. pub fn systems(&self) -> WorldSystems {
  462. if self.permissionless {
  463. return WorldSystems::default();
  464. }
  465. WorldSystems::try_from_slice(self.systems.as_ref()).unwrap_or_default()
  466. }
  467. }
  468. #[derive(
  469. anchor_lang::prelude::borsh::BorshSerialize,
  470. anchor_lang::prelude::borsh::BorshDeserialize,
  471. Default,
  472. Debug,
  473. )]
  474. pub struct WorldSystems {
  475. pub approved_systems: BTreeSet<Pubkey>,
  476. }
  477. impl World {
  478. pub fn seed() -> &'static [u8] {
  479. b"world"
  480. }
  481. pub fn size() -> usize {
  482. 16 + 8 + 1 + 8
  483. }
  484. pub fn pda(&self) -> (Pubkey, u8) {
  485. Pubkey::find_program_address(&[World::seed(), &self.id.to_be_bytes()], &crate::ID)
  486. }
  487. }
  488. #[account]
  489. #[derive(InitSpace, Default, Copy)]
  490. pub struct Entity {
  491. pub id: u64,
  492. }
  493. impl Entity {
  494. pub fn seed() -> &'static [u8] {
  495. b"entity"
  496. }
  497. }
  498. #[account]
  499. #[derive(InitSpace, Default)]
  500. pub struct SystemWhitelist {}
  501. impl SystemWhitelist {
  502. pub fn seed() -> &'static [u8] {
  503. b"whitelist"
  504. }
  505. pub fn size() -> usize {
  506. 8 + Registry::INIT_SPACE
  507. }
  508. }
  509. /// Builds the context for updating a component.
  510. pub fn build_update_context<'info>(
  511. component_program: UncheckedAccount<'info>,
  512. component: UncheckedAccount<'info>,
  513. authority: Signer<'info>,
  514. instruction_sysvar_account: UncheckedAccount<'info>,
  515. ) -> CpiContext<'info, 'info, 'info, 'info, bolt_component::cpi::accounts::Update<'info>> {
  516. let cpi_program = component_program.to_account_info();
  517. let cpi_accounts = bolt_component::cpi::accounts::Update {
  518. bolt_component: component.to_account_info(),
  519. authority: authority.to_account_info(),
  520. instruction_sysvar_account: instruction_sysvar_account.to_account_info(),
  521. };
  522. CpiContext::new(cpi_program, cpi_accounts)
  523. }