bolt.ts 17 KB

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