bolt.ts 18 KB

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