bolt.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. import * as anchor from "@coral-xyz/anchor";
  2. import { type Program, web3 } from "@coral-xyz/anchor";
  3. import { type PublicKey } from "@solana/web3.js";
  4. import { type Position } from "../target/types/position";
  5. import { type Velocity } from "../target/types/velocity";
  6. import { type BoltComponent } from "../target/types/bolt_component";
  7. import { type SystemSimpleMovement } from "../target/types/system_simple_movement";
  8. import { type SystemFly } from "../target/types/system_fly";
  9. import { type SystemApplyVelocity } from "../target/types/system_apply_velocity";
  10. import { expect } from "chai";
  11. import type BN from "bn.js";
  12. import {
  13. AddEntity,
  14. createDelegateInstruction,
  15. createUndelegateInstruction,
  16. createInitializeRegistryInstruction,
  17. DELEGATION_PROGRAM_ID,
  18. FindRegistryPda,
  19. InitializeComponent,
  20. InitializeNewWorld,
  21. ApplySystem,
  22. createAllowUndelegationInstruction,
  23. } from "../clients/bolt-sdk";
  24. enum Direction {
  25. Left = "Left",
  26. Right = "Right",
  27. Up = "Up",
  28. Down = "Down",
  29. }
  30. function padCenter(value: string, width: number) {
  31. const length = value.length;
  32. if (width <= length) {
  33. return value;
  34. }
  35. const padding = (width - length) / 2;
  36. const align = width - padding;
  37. return value.padStart(align, " ").padEnd(width, " ");
  38. }
  39. function logPosition(title: string, { x, y, z }: { x: BN; y: BN; z: BN }) {
  40. console.log(" +----------------------------------+");
  41. console.log(` | ${padCenter(title, 32)} |`);
  42. console.log(" +-----------------+----------------+");
  43. console.log(` | X Position | ${String(x).padEnd(14, " ")} |`);
  44. console.log(` | Y Position | ${String(y).padEnd(14, " ")} |`);
  45. console.log(` | Z Position | ${String(z).padEnd(14, " ")} |`);
  46. console.log(" +-----------------+----------------+");
  47. }
  48. function logVelocity(
  49. title: string,
  50. { x, y, z, lastApplied }: { x: BN; y: BN; z: BN; lastApplied: BN }
  51. ) {
  52. console.log(" +----------------------------------+");
  53. console.log(` | ${padCenter(title, 32)} |`);
  54. console.log(" +-----------------+----------------+");
  55. console.log(` | X Velocity | ${String(x).padEnd(14, " ")} |`);
  56. console.log(` | Y Velocity | ${String(y).padEnd(14, " ")} |`);
  57. console.log(` | Z Velocity | ${String(z).padEnd(14, " ")} |`);
  58. console.log(` | Last Applied | ${String(lastApplied).padEnd(14, " ")} |`);
  59. console.log(" +-----------------+----------------+");
  60. }
  61. describe("bolt", () => {
  62. const provider = anchor.AnchorProvider.env();
  63. anchor.setProvider(provider);
  64. const boltComponentProgram = anchor.workspace
  65. .BoltComponent as Program<BoltComponent>;
  66. const exampleComponentPosition = anchor.workspace
  67. .Position as Program<Position>;
  68. const exampleComponentVelocity = anchor.workspace
  69. .Velocity as Program<Velocity>;
  70. const exampleSystemSimpleMovement = (
  71. anchor.workspace.SystemSimpleMovement as Program<SystemSimpleMovement>
  72. ).programId;
  73. const exampleSystemFly = (anchor.workspace.SystemFly as Program<SystemFly>)
  74. .programId;
  75. const exampleSystemApplyVelocity = (
  76. anchor.workspace.SystemApplyVelocity as Program<SystemApplyVelocity>
  77. ).programId;
  78. let worldPda: PublicKey;
  79. let entity1Pda: PublicKey;
  80. let entity2Pda: PublicKey;
  81. let entity4Pda: PublicKey;
  82. let entity5Pda: PublicKey;
  83. let componentPositionEntity1Pda: PublicKey;
  84. let componentVelocityEntity1Pda: PublicKey;
  85. let componentPositionEntity4Pda: PublicKey;
  86. let componentPositionEntity5Pda: PublicKey;
  87. it("InitializeRegistry", async () => {
  88. const registryPda = FindRegistryPda({});
  89. const initializeRegistryIx = createInitializeRegistryInstruction({
  90. registry: registryPda,
  91. payer: provider.wallet.publicKey,
  92. });
  93. const tx = new anchor.web3.Transaction().add(initializeRegistryIx);
  94. await provider.sendAndConfirm(tx);
  95. });
  96. it("InitializeNewWorld", async () => {
  97. const initializeNewWorld = await InitializeNewWorld({
  98. payer: provider.wallet.publicKey,
  99. connection: provider.connection,
  100. });
  101. await provider.sendAndConfirm(initializeNewWorld.transaction);
  102. worldPda = initializeNewWorld.worldPda; // Saved for later
  103. });
  104. it("InitializeNewWorld 2", async () => {
  105. const initializeNewWorld = await InitializeNewWorld({
  106. payer: provider.wallet.publicKey,
  107. connection: provider.connection,
  108. });
  109. await provider.sendAndConfirm(initializeNewWorld.transaction);
  110. });
  111. it("Add entity 1", async () => {
  112. const addEntity = await AddEntity({
  113. payer: provider.wallet.publicKey,
  114. world: worldPda,
  115. connection: provider.connection,
  116. });
  117. await provider.sendAndConfirm(addEntity.transaction);
  118. entity1Pda = addEntity.entityPda; // Saved for later
  119. });
  120. it("Add entity 2", async () => {
  121. const addEntity = await AddEntity({
  122. payer: provider.wallet.publicKey,
  123. world: worldPda,
  124. connection: provider.connection,
  125. });
  126. await provider.sendAndConfirm(addEntity.transaction);
  127. entity2Pda = addEntity.entityPda; // Saved for later
  128. });
  129. it("Add entity 3", async () => {
  130. const addEntity = await AddEntity({
  131. payer: provider.wallet.publicKey,
  132. world: worldPda,
  133. connection: provider.connection,
  134. });
  135. await provider.sendAndConfirm(addEntity.transaction);
  136. });
  137. it("Add entity 4 (with seed)", async () => {
  138. const addEntity = await AddEntity({
  139. payer: provider.wallet.publicKey,
  140. world: worldPda,
  141. seed: "extra-seed",
  142. connection: provider.connection,
  143. });
  144. await provider.sendAndConfirm(addEntity.transaction);
  145. entity4Pda = addEntity.entityPda;
  146. });
  147. it("Add entity 5", async () => {
  148. const addEntity = await AddEntity({
  149. payer: provider.wallet.publicKey,
  150. world: worldPda,
  151. connection: provider.connection,
  152. });
  153. await provider.sendAndConfirm(addEntity.transaction);
  154. entity5Pda = addEntity.entityPda; // Saved for later
  155. });
  156. it("Initialize Original Component on Entity 1, trough the world instance", async () => {
  157. const initializeComponent = await InitializeComponent({
  158. payer: provider.wallet.publicKey,
  159. entity: entity1Pda,
  160. seed: "origin-component",
  161. componentId: boltComponentProgram.programId,
  162. });
  163. await provider.sendAndConfirm(initializeComponent.transaction);
  164. });
  165. it("Initialize Original Component on Entity 2, trough the world instance", async () => {
  166. const initializeComponent = await InitializeComponent({
  167. payer: provider.wallet.publicKey,
  168. entity: entity2Pda,
  169. seed: "origin-component",
  170. componentId: boltComponentProgram.programId,
  171. });
  172. await provider.sendAndConfirm(initializeComponent.transaction);
  173. });
  174. it("Initialize Position Component on Entity 1", async () => {
  175. const initializeComponent = await InitializeComponent({
  176. payer: provider.wallet.publicKey,
  177. entity: entity1Pda,
  178. componentId: exampleComponentPosition.programId,
  179. });
  180. await provider.sendAndConfirm(initializeComponent.transaction);
  181. componentPositionEntity1Pda = initializeComponent.componentPda; // Saved for later
  182. });
  183. it("Initialize Velocity Component on Entity 1 (with seed)", async () => {
  184. const initializeComponent = await InitializeComponent({
  185. payer: provider.wallet.publicKey,
  186. entity: entity1Pda,
  187. componentId: exampleComponentVelocity.programId,
  188. seed: "component-velocity",
  189. });
  190. await provider.sendAndConfirm(initializeComponent.transaction);
  191. componentVelocityEntity1Pda = initializeComponent.componentPda; // Saved for later
  192. });
  193. it("Initialize Position Component on Entity 2", async () => {
  194. const initializeComponent = await InitializeComponent({
  195. payer: provider.wallet.publicKey,
  196. entity: entity2Pda,
  197. componentId: exampleComponentPosition.programId,
  198. });
  199. await provider.sendAndConfirm(initializeComponent.transaction);
  200. });
  201. it("Initialize Position Component on Entity 4", async () => {
  202. const initializeComponent = await InitializeComponent({
  203. payer: provider.wallet.publicKey,
  204. entity: entity4Pda,
  205. componentId: exampleComponentPosition.programId,
  206. });
  207. await provider.sendAndConfirm(initializeComponent.transaction);
  208. componentPositionEntity4Pda = initializeComponent.componentPda; // Saved for later
  209. });
  210. it("Initialize Position Component on Entity 5 (with authority)", async () => {
  211. const initializeComponent = await InitializeComponent({
  212. payer: provider.wallet.publicKey,
  213. entity: entity5Pda,
  214. componentId: exampleComponentPosition.programId,
  215. authority: provider.wallet.publicKey,
  216. });
  217. await provider.sendAndConfirm(initializeComponent.transaction);
  218. componentPositionEntity5Pda = initializeComponent.componentPda; // Saved for later
  219. });
  220. it("Check Position on Entity 1 is default", async () => {
  221. const position = await exampleComponentPosition.account.position.fetch(
  222. componentPositionEntity1Pda
  223. );
  224. logPosition("Default State: Entity 1", position);
  225. expect(position.x.toNumber()).to.equal(0);
  226. expect(position.y.toNumber()).to.equal(0);
  227. expect(position.z.toNumber()).to.equal(0);
  228. });
  229. it("Apply Simple Movement System (Up) on Entity 1", async () => {
  230. const applySystem = await ApplySystem({
  231. authority: provider.wallet.publicKey,
  232. systemId: exampleSystemSimpleMovement,
  233. entities: [
  234. {
  235. entity: entity1Pda,
  236. components: [{ componentId: exampleComponentPosition.programId }],
  237. },
  238. ],
  239. args: {
  240. direction: Direction.Up,
  241. },
  242. });
  243. await provider.sendAndConfirm(applySystem.transaction);
  244. const position = await exampleComponentPosition.account.position.fetch(
  245. componentPositionEntity1Pda
  246. );
  247. logPosition("Movement System: Entity 1", position);
  248. expect(position.x.toNumber()).to.equal(0);
  249. expect(position.y.toNumber()).to.equal(1);
  250. expect(position.z.toNumber()).to.equal(0);
  251. });
  252. it("Apply Simple Movement System (Right) on Entity 1", async () => {
  253. const applySystem = await ApplySystem({
  254. authority: provider.wallet.publicKey,
  255. systemId: exampleSystemSimpleMovement,
  256. entities: [
  257. {
  258. entity: entity1Pda,
  259. components: [{ componentId: exampleComponentPosition.programId }],
  260. },
  261. ],
  262. args: {
  263. direction: Direction.Right,
  264. },
  265. });
  266. await provider.sendAndConfirm(applySystem.transaction);
  267. const position = await exampleComponentPosition.account.position.fetch(
  268. componentPositionEntity1Pda
  269. );
  270. logPosition("Movement System: Entity 1", position);
  271. expect(position.x.toNumber()).to.equal(1);
  272. expect(position.y.toNumber()).to.equal(1);
  273. expect(position.z.toNumber()).to.equal(0);
  274. });
  275. it("Apply Fly System on Entity 1", async () => {
  276. const applySystem = await ApplySystem({
  277. authority: provider.wallet.publicKey,
  278. systemId: exampleSystemFly,
  279. entities: [
  280. {
  281. entity: entity1Pda,
  282. components: [{ componentId: exampleComponentPosition.programId }],
  283. },
  284. ],
  285. });
  286. await provider.sendAndConfirm(applySystem.transaction);
  287. const position = await exampleComponentPosition.account.position.fetch(
  288. componentPositionEntity1Pda
  289. );
  290. logPosition("Fly System: Entity 1", position);
  291. expect(position.x.toNumber()).to.equal(1);
  292. expect(position.y.toNumber()).to.equal(1);
  293. expect(position.z.toNumber()).to.equal(1);
  294. });
  295. it("Apply System Velocity on Entity 1", async () => {
  296. const applySystem = await ApplySystem({
  297. authority: provider.wallet.publicKey,
  298. systemId: exampleSystemApplyVelocity,
  299. entities: [
  300. {
  301. entity: entity1Pda,
  302. components: [
  303. {
  304. componentId: exampleComponentVelocity.programId,
  305. seed: "component-velocity",
  306. },
  307. { componentId: exampleComponentPosition.programId },
  308. ],
  309. },
  310. ],
  311. });
  312. await provider.sendAndConfirm(applySystem.transaction);
  313. const velocity = await exampleComponentVelocity.account.velocity.fetch(
  314. componentVelocityEntity1Pda
  315. );
  316. logVelocity("Apply System Velocity: Entity 1", velocity);
  317. expect(velocity.x.toNumber()).to.equal(10);
  318. expect(velocity.y.toNumber()).to.equal(0);
  319. expect(velocity.z.toNumber()).to.equal(0);
  320. expect(velocity.lastApplied.toNumber()).to.not.equal(0);
  321. const position = await exampleComponentPosition.account.position.fetch(
  322. componentPositionEntity1Pda
  323. );
  324. logPosition("Apply System Velocity: Entity 1", position);
  325. expect(position.x.toNumber()).to.greaterThan(1);
  326. expect(position.y.toNumber()).to.equal(1);
  327. expect(position.z.toNumber()).to.equal(1);
  328. });
  329. it("Apply System Velocity on Entity 1, with Clock external account", async () => {
  330. const applySystem = await ApplySystem({
  331. authority: provider.wallet.publicKey,
  332. systemId: exampleSystemApplyVelocity,
  333. entities: [
  334. {
  335. entity: entity1Pda,
  336. components: [
  337. {
  338. componentId: exampleComponentVelocity.programId,
  339. seed: "component-velocity",
  340. },
  341. { componentId: exampleComponentPosition.programId },
  342. ],
  343. },
  344. ],
  345. extraAccounts: [
  346. {
  347. pubkey: new web3.PublicKey(
  348. "SysvarC1ock11111111111111111111111111111111"
  349. ),
  350. isWritable: false,
  351. isSigner: false,
  352. },
  353. ],
  354. });
  355. await provider.sendAndConfirm(applySystem.transaction);
  356. const position = await exampleComponentPosition.account.position.fetch(
  357. componentPositionEntity1Pda
  358. );
  359. logPosition("Apply System Velocity: Entity 1", position);
  360. expect(position.x.toNumber()).to.greaterThan(1);
  361. expect(position.y.toNumber()).to.equal(1);
  362. expect(position.z.toNumber()).to.equal(300);
  363. });
  364. it("Apply Fly System on Entity 4", async () => {
  365. const applySystem = await ApplySystem({
  366. authority: provider.wallet.publicKey,
  367. systemId: exampleSystemFly,
  368. entities: [
  369. {
  370. entity: entity4Pda,
  371. components: [{ componentId: exampleComponentPosition.programId }],
  372. },
  373. ],
  374. });
  375. await provider.sendAndConfirm(applySystem.transaction);
  376. const position = await exampleComponentPosition.account.position.fetch(
  377. componentPositionEntity4Pda
  378. );
  379. logPosition("Fly System: Entity 4", position);
  380. expect(position.x.toNumber()).to.equal(0);
  381. expect(position.y.toNumber()).to.equal(0);
  382. expect(position.z.toNumber()).to.equal(1);
  383. });
  384. it("Apply Fly System on Entity 5 (should fail with wrong authority)", async () => {
  385. const positionBefore =
  386. await exampleComponentPosition.account.position.fetch(
  387. componentPositionEntity5Pda
  388. );
  389. const applySystem = await ApplySystem({
  390. authority: provider.wallet.publicKey,
  391. systemId: exampleSystemFly,
  392. entities: [
  393. {
  394. entity: entity5Pda,
  395. components: [{ componentId: exampleComponentPosition.programId }],
  396. },
  397. ],
  398. });
  399. let failed = false;
  400. try {
  401. await provider.sendAndConfirm(applySystem.transaction);
  402. } catch (error) {
  403. failed = true;
  404. // console.log("error", error);
  405. expect(error.logs.join("\n")).to.contain("Error Code: InvalidAuthority");
  406. }
  407. expect(failed).to.equal(true);
  408. const positionAfter = await exampleComponentPosition.account.position.fetch(
  409. componentPositionEntity5Pda
  410. );
  411. expect(positionBefore.x.toNumber()).to.equal(positionAfter.x.toNumber());
  412. expect(positionBefore.y.toNumber()).to.equal(positionAfter.y.toNumber());
  413. expect(positionBefore.z.toNumber()).to.equal(positionAfter.z.toNumber());
  414. });
  415. it("Check invalid component init without CPI", async () => {
  416. let invalid = false;
  417. try {
  418. await exampleComponentPosition.methods
  419. .initialize()
  420. .accounts({
  421. payer: provider.wallet.publicKey,
  422. data: componentPositionEntity5Pda,
  423. entity: entity5Pda,
  424. authority: provider.wallet.publicKey,
  425. })
  426. .rpc();
  427. } catch (error) {
  428. // console.log("error", error);
  429. expect(error.message).to.contain("Error Code: InvalidCaller");
  430. invalid = true;
  431. }
  432. expect(invalid).to.equal(true);
  433. });
  434. it("Check invalid component update without CPI", async () => {
  435. let invalid = false;
  436. try {
  437. await boltComponentProgram.methods
  438. .update(Buffer.from(""))
  439. .accounts({
  440. boltComponent: componentPositionEntity4Pda,
  441. authority: provider.wallet.publicKey,
  442. })
  443. .rpc();
  444. } catch (error) {
  445. // console.log("error", error);
  446. expect(error.message).to.contain(
  447. "bolt_component. Error Code: AccountOwnedByWrongProgram"
  448. );
  449. invalid = true;
  450. }
  451. expect(invalid).to.equal(true);
  452. });
  453. it("Check component delegation", async () => {
  454. const delegateIx = createDelegateInstruction({
  455. entity: entity1Pda,
  456. account: componentPositionEntity1Pda,
  457. ownerProgram: exampleComponentPosition.programId,
  458. payer: provider.wallet.publicKey,
  459. });
  460. const tx = new anchor.web3.Transaction().add(delegateIx);
  461. await provider.sendAndConfirm(tx);
  462. const acc = await provider.connection.getAccountInfo(
  463. componentPositionEntity1Pda
  464. );
  465. expect(acc.owner.toString()).to.equal(DELEGATION_PROGRAM_ID);
  466. });
  467. it("Check component undelegation", async () => {
  468. const allowUndelegateIx = createAllowUndelegationInstruction({
  469. delegatedAccount: componentPositionEntity1Pda,
  470. ownerProgram: exampleComponentPosition.programId,
  471. });
  472. const delegateIx = createUndelegateInstruction({
  473. payer: provider.wallet.publicKey,
  474. delegatedAccount: componentPositionEntity1Pda,
  475. ownerProgram: exampleComponentPosition.programId,
  476. reimbursement: provider.wallet.publicKey,
  477. });
  478. const tx = new anchor.web3.Transaction()
  479. .add(allowUndelegateIx)
  480. .add(delegateIx);
  481. await provider.sendAndConfirm(tx);
  482. const acc = await provider.connection.getAccountInfo(
  483. componentPositionEntity1Pda
  484. );
  485. expect(acc.owner).to.deep.equal(exampleComponentPosition.programId);
  486. });
  487. });