diff --git a/Anchor.toml b/Anchor.toml index 0fe098a2..c2a2aa44 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -39,14 +39,11 @@ members = [ test = """ echo "Waiting for 2 seconds..." sleep 2 -echo "Running low level API tests..." -yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/bolt.low-level.api.ts -echo "Running intermediate level API tests..." -yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/bolt.intermediate-level.api.ts +yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/main.ts """ [test] -startup_wait = 5000 +startup_wait = 15000 shutdown_wait = 2000 upgradeable = false @@ -55,6 +52,11 @@ address = "DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh" program = "tests/fixtures/delegation.so" upgradeable = false +[[test.genesis]] +address="KeyspM2ssCJbqUhQ4k7sveSiY4WjnYsrXkC8oDbwde5" +program="tests/fixtures/session_keys.so" +upgradeable = false + [test.validator] bind_address = "0.0.0.0" ledger = ".anchor/test-ledger" diff --git a/Cargo.lock b/Cargo.lock index 18b2d5a7..634f6b06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1104,6 +1104,7 @@ dependencies = [ "ephemeral-rollups-sdk 0.2.1", "serde", "serde_json", + "session-keys", "world 0.2.0", ] @@ -4210,6 +4211,37 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "session-keys" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059475c4228de6bcd7ef7dd9bb52c7329201d62dae701d73a71f2262c86dfc51" +dependencies = [ + "anchor-lang", + "session-keys-macros", + "solana-security-txt", +] + +[[package]] +name = "session-keys-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6349e5cb01c08d3b3efad6cad6c63d622b703603812258ba6741cbc2c62ca39d" +dependencies = [ + "session-keys-macros-attribute", +] + +[[package]] +name = "session-keys-macros-attribute" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e07d122114bc5388a5440d62374b5b752f6013e270d100b479bb1b650d375d11" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "sha1" version = "0.10.6" diff --git a/Cargo.toml b/Cargo.toml index f6564cbc..f7e94543 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ bolt-system = { path = "crates/programs/bolt-system", features = ["cpi"], versio bolt-component = { path = "crates/programs/bolt-component", features = ["cpi"], version = "=0.2.0"} ## External crates +session-keys = { version = "=2.0.6", features = ["no-entrypoint"] } anchor-lang = { version = "=0.30.1", features = ["init-if-needed"] } anchor-cli = { version = "=0.30.1" } anchor-client = { version = "=0.30.1" } diff --git a/clients/bolt-sdk/src/generated/idl/gpl_session.json b/clients/bolt-sdk/src/generated/idl/gpl_session.json new file mode 100644 index 00000000..38a9bf7e --- /dev/null +++ b/clients/bolt-sdk/src/generated/idl/gpl_session.json @@ -0,0 +1,231 @@ +{ + "address": "KeyspM2ssCJbqUhQ4k7sveSiY4WjnYsrXkC8oDbwde5", + "metadata": { + "name": "gpl_session", + "version": "2.0.6", + "spec": "0.1.0", + "description": "Gum Session Protocol (GPL Session)", + "repository": "https://github.com/magicblock-labs/gum-program-library" + }, + "instructions": [ + { + "name": "create_session", + "discriminator": [ + 242, + 193, + 143, + 179, + 150, + 25, + 122, + 227 + ], + "accounts": [ + { + "name": "session_token", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 115, + 101, + 115, + 115, + 105, + 111, + 110, + 95, + 116, + 111, + 107, + 101, + 110 + ] + }, + { + "kind": "account", + "path": "target_program" + }, + { + "kind": "account", + "path": "session_signer" + }, + { + "kind": "account", + "path": "authority" + } + ] + } + }, + { + "name": "session_signer", + "writable": true, + "signer": true + }, + { + "name": "authority", + "writable": true, + "signer": true + }, + { + "name": "target_program", + "docs": [ + "CHECK the target program is actually a program." + ] + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "top_up", + "type": { + "option": "bool" + } + }, + { + "name": "valid_until", + "type": { + "option": "i64" + } + }, + { + "name": "lamports", + "type": { + "option": "u64" + } + } + ] + }, + { + "name": "revoke_session", + "discriminator": [ + 86, + 92, + 198, + 120, + 144, + 2, + 7, + 194 + ], + "accounts": [ + { + "name": "session_token", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 115, + 101, + 115, + 115, + 105, + 111, + 110, + 95, + 116, + 111, + 107, + 101, + 110 + ] + }, + { + "kind": "account", + "path": "session_token.target_program", + "account": "SessionToken" + }, + { + "kind": "account", + "path": "session_token.session_signer", + "account": "SessionToken" + }, + { + "kind": "account", + "path": "session_token.authority", + "account": "SessionToken" + } + ] + } + }, + { + "name": "authority", + "writable": true, + "relations": [ + "session_token" + ] + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [] + } + ], + "accounts": [ + { + "name": "SessionToken", + "discriminator": [ + 233, + 4, + 115, + 14, + 46, + 21, + 1, + 15 + ] + } + ], + "errors": [ + { + "code": 6000, + "name": "ValidityTooLong", + "msg": "Requested validity is too long" + }, + { + "code": 6001, + "name": "InvalidToken", + "msg": "Invalid session token" + }, + { + "code": 6002, + "name": "NoToken", + "msg": "No session token provided" + } + ], + "types": [ + { + "name": "SessionToken", + "type": { + "kind": "struct", + "fields": [ + { + "name": "authority", + "type": "pubkey" + }, + { + "name": "target_program", + "type": "pubkey" + }, + { + "name": "session_signer", + "type": "pubkey" + }, + { + "name": "valid_until", + "type": "i64" + } + ] + } + } + ] +} diff --git a/clients/bolt-sdk/src/generated/idl/world.json b/clients/bolt-sdk/src/generated/idl/world.json index c063e7a0..1d46979b 100644 --- a/clients/bolt-sdk/src/generated/idl/world.json +++ b/clients/bolt-sdk/src/generated/idl/world.json @@ -111,6 +111,10 @@ }, { "name": "world" + }, + { + "name": "session_token", + "optional": true } ], "args": [ diff --git a/clients/bolt-sdk/src/generated/index.ts b/clients/bolt-sdk/src/generated/index.ts index 50e75dde..d5c57844 100644 --- a/clients/bolt-sdk/src/generated/index.ts +++ b/clients/bolt-sdk/src/generated/index.ts @@ -8,6 +8,7 @@ import { PublicKey } from "@solana/web3.js"; import { type World as WorldProgram } from "./types/world"; import idl from "./idl/world.json"; +import gpl_session from "./idl/gpl_session.json"; export * from "./accounts"; export * from "./errors"; export * from "./instructions"; @@ -30,3 +31,4 @@ export const PROGRAM_ID = new PublicKey(PROGRAM_ADDRESS); export default WorldProgram; export { idl as worldIdl }; +export { gpl_session as sessionIdl }; diff --git a/clients/bolt-sdk/src/generated/types/world.ts b/clients/bolt-sdk/src/generated/types/world.ts index 3bb42829..f0f369c1 100644 --- a/clients/bolt-sdk/src/generated/types/world.ts +++ b/clients/bolt-sdk/src/generated/types/world.ts @@ -91,6 +91,10 @@ export type World = { { name: "world"; }, + { + name: "sessionToken"; + optional: true; + }, ]; args: [ { diff --git a/clients/bolt-sdk/src/index.ts b/clients/bolt-sdk/src/index.ts index 899ca0c2..92c885b6 100644 --- a/clients/bolt-sdk/src/index.ts +++ b/clients/bolt-sdk/src/index.ts @@ -1,7 +1,8 @@ import { PublicKey } from "@solana/web3.js"; -import type BN from "bn.js"; +import BN from "bn.js"; import { PROGRAM_ID as WORLD_PROGRAM_ID } from "./generated"; import { World as WORLD_PROGRAM_IDL } from "./generated/types"; +export { BN }; export * from "./generated/accounts"; export * from "./generated/instructions"; export * from "./world/transactions"; @@ -11,6 +12,7 @@ export { DELEGATION_PROGRAM_ID } from "@magicblock-labs/ephemeral-rollups-sdk"; // Re-export anchor import * as anchor from "@coral-xyz/anchor"; +import { SessionProgram, Session } from "./session"; export { anchor }; export { Provider, Program, Wallet, web3, workspace } from "@coral-xyz/anchor"; export { WORLD_PROGRAM_ID, WORLD_PROGRAM_IDL }; @@ -68,6 +70,24 @@ export function FindEntityPda({ )[0]; } +export function FindSessionTokenPda({ + sessionSigner, + authority, +}: { + sessionSigner: PublicKey; + authority: PublicKey; +}) { + return PublicKey.findProgramAddressSync( + [ + Buffer.from("session_token"), + WORLD_PROGRAM_ID.toBytes(), + sessionSigner.toBytes(), + authority.toBytes(), + ], + SessionProgram.programId, + )[0]; +} + // TODO: seed must be Uint8Array like the other FindPda functions export function FindComponentPda({ componentId, @@ -99,3 +119,5 @@ export function SerializeArgs(args: any = {}) { binaryData.byteLength, ); } + +export { SessionProgram, Session }; diff --git a/clients/bolt-sdk/src/session.ts b/clients/bolt-sdk/src/session.ts new file mode 100644 index 00000000..8a6f67be --- /dev/null +++ b/clients/bolt-sdk/src/session.ts @@ -0,0 +1,42 @@ +import { Program, Idl } from "@coral-xyz/anchor"; +import { sessionIdl } from "./generated"; +import { Keypair, PublicKey } from "@solana/web3.js"; +let _actualInstance: Program | null = null; + +function getOrCreateInstance(): Program { + if (!_actualInstance) { + _actualInstance = new Program(sessionIdl as Idl); + } + return _actualInstance; +} + +export const SessionProgram = new Proxy({} as Program, { + get(_target, property, receiver) { + // Ensure the real object is instantiated + const actual = getOrCreateInstance(); + + // Forward the property access to the real instance + return Reflect.get(actual, property, receiver); + }, + + set(_target, property, value, receiver) { + const actual = getOrCreateInstance(); + return Reflect.set(actual, property, value, receiver); + }, + + // If you need to handle method calls specifically (Function type check), + // or other traps (has, apply, etc.), you can add them here as well. +}); + +export class Session { + public readonly signer: Keypair; + public readonly token: PublicKey; + + constructor( + public readonly sessionSigner: Keypair, + public readonly sessionToken: PublicKey, + ) { + this.signer = sessionSigner; + this.token = sessionToken; + } +} diff --git a/clients/bolt-sdk/src/world/transactions.ts b/clients/bolt-sdk/src/world/transactions.ts index 68dd8092..b2b977ad 100644 --- a/clients/bolt-sdk/src/world/transactions.ts +++ b/clients/bolt-sdk/src/world/transactions.ts @@ -11,11 +11,16 @@ import { SerializeArgs, SYSVAR_INSTRUCTIONS_PUBKEY, World, + SessionProgram, + Session, + FindSessionTokenPda, + WORLD_PROGRAM_ID, + BN, } from "../index"; -import BN from "bn.js"; import type web3 from "@solana/web3.js"; import { type Connection, + Keypair, type PublicKey, Transaction, type TransactionInstruction, @@ -47,6 +52,45 @@ export async function InitializeRegistry({ }; } +export async function CreateSession({ + sessionSigner, + authority, + topUp, + validity, +}: { + sessionSigner?: Keypair; + authority: PublicKey; + topUp?: BN; + validity?: BN; +}): Promise<{ + instruction: TransactionInstruction; + transaction: Transaction; + session: Session; +}> { + sessionSigner = sessionSigner ?? Keypair.generate(); + const sessionToken = FindSessionTokenPda({ + sessionSigner: sessionSigner.publicKey, + authority, + }); + const lamports = topUp ?? null; + const shouldTopUp = topUp ? true : false; + let instruction = await SessionProgram.methods + .createSession(shouldTopUp, validity ?? null, lamports) + .accounts({ + sessionSigner: sessionSigner.publicKey, + authority, + targetProgram: WORLD_PROGRAM_ID, + sessionToken, + }) + .instruction(); + const transaction = new Transaction().add(instruction); + return { + instruction, + transaction, + session: new Session(sessionSigner, sessionToken), + }; +} + /** * Create the transaction to Initialize a new world * @param payer @@ -337,6 +381,7 @@ interface ApplySystemInstruction { systemId: PublicKey; entities: ApplySystemEntity[]; world: PublicKey; + session?: Session; extraAccounts?: web3.AccountMeta[]; args?: object; } @@ -345,6 +390,7 @@ async function createApplySystemInstruction({ systemId, entities, world, + session, extraAccounts, args, }: ApplySystemInstruction): Promise { @@ -359,9 +405,12 @@ async function createApplySystemInstruction({ throw new Error("No components provided"); } + let sessionToken = session ? session.token : null; + const applyAccounts = { authority: authority ?? PROGRAM_ID, boltSystem: systemId, + sessionToken, world, }; @@ -435,6 +484,7 @@ export async function ApplySystem({ world, extraAccounts, args, + session, }: { authority: PublicKey; systemId: PublicKey; @@ -442,6 +492,7 @@ export async function ApplySystem({ world: PublicKey; extraAccounts?: web3.AccountMeta[]; args?: object; + session?: Session; }): Promise<{ instruction: TransactionInstruction; transaction: Transaction }> { const instruction = await createApplySystemInstruction({ authority, @@ -450,6 +501,7 @@ export async function ApplySystem({ world, extraAccounts, args, + session, }); const transaction = new Transaction().add(instruction); return { diff --git a/clients/bolt-sdk/yarn.lock b/clients/bolt-sdk/yarn.lock index b19054cb..dcabd7f6 100644 --- a/clients/bolt-sdk/yarn.lock +++ b/clients/bolt-sdk/yarn.lock @@ -2,28 +2,21 @@ # yarn lockfile v1 -"@babel/runtime@^7.17.2", "@babel/runtime@^7.23.2": - version "7.23.6" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz" - integrity sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ== - dependencies: - regenerator-runtime "^0.14.0" - "@babel/runtime@^7.25.0": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6" - integrity sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w== + version "7.26.7" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz" + integrity sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ== dependencies: regenerator-runtime "^0.14.0" "@coral-xyz/anchor-errors@^0.30.1": version "0.30.1" - resolved "https://registry.yarnpkg.com/@coral-xyz/anchor-errors/-/anchor-errors-0.30.1.tgz#bdfd3a353131345244546876eb4afc0e125bec30" + resolved "https://registry.npmjs.org/@coral-xyz/anchor-errors/-/anchor-errors-0.30.1.tgz" integrity sha512-9Mkradf5yS5xiLWrl9WrpjqOrAV+/W2RQHDlbnAZBivoGpOs1ECjoDCkVk4aRG8ZdiFiB8zQEVlxf+8fKkmSfQ== "@coral-xyz/anchor@^0.30.1": version "0.30.1" - resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.30.1.tgz#17f3e9134c28cd0ea83574c6bab4e410bcecec5d" + resolved "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.30.1.tgz" integrity sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ== dependencies: "@coral-xyz/anchor-errors" "^0.30.1" @@ -44,7 +37,7 @@ "@coral-xyz/borsh@^0.30.1": version "0.30.1" - resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.30.1.tgz#869d8833abe65685c72e9199b8688477a4f6b0e3" + resolved "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.30.1.tgz" integrity sha512-aaxswpPrCFKl8vZTbxLssA2RvwX2zmKLlRCIktJOwW+VpVwYtXRtlWiIP+c2pPRKneiTiWCN2GEMSH9j1zTlWQ== dependencies: bn.js "^5.1.2" @@ -64,7 +57,7 @@ "@magicblock-labs/ephemeral-rollups-sdk@0.2.1": version "0.2.1" - resolved "https://registry.yarnpkg.com/@magicblock-labs/ephemeral-rollups-sdk/-/ephemeral-rollups-sdk-0.2.1.tgz#bf85a0725b9fe47bc96738a99bf9e84aa9e405d0" + resolved "https://registry.npmjs.org/@magicblock-labs/ephemeral-rollups-sdk/-/ephemeral-rollups-sdk-0.2.1.tgz" integrity sha512-s1j6kXLbg6hA5ysSkG1pGEutAdxPOymtl2I94pD5UVfWWILcR0rbqONOvNV1WKDGZbILqC1AjFl/7GJGRMKB5Q== dependencies: "@metaplex-foundation/beet" "^0.7.2" @@ -83,7 +76,7 @@ "@metaplex-foundation/beet-solana@^0.4.0": version "0.4.1" - resolved "https://registry.yarnpkg.com/@metaplex-foundation/beet-solana/-/beet-solana-0.4.1.tgz#255747aa7feee1c20202146a752c057feca1948f" + resolved "https://registry.npmjs.org/@metaplex-foundation/beet-solana/-/beet-solana-0.4.1.tgz" integrity sha512-/6o32FNUtwK8tjhotrvU/vorP7umBuRFvBZrC6XCk51aKidBHe5LPVPA5AjGPbV3oftMfRuXPNd9yAGeEqeCDQ== dependencies: "@metaplex-foundation/beet" ">=0.1.0" @@ -102,7 +95,7 @@ "@metaplex-foundation/beet@^0.7.2": version "0.7.2" - resolved "https://registry.yarnpkg.com/@metaplex-foundation/beet/-/beet-0.7.2.tgz#fa4726e4cfd4fb6fed6cddc9b5213c1c2a2d0b77" + resolved "https://registry.npmjs.org/@metaplex-foundation/beet/-/beet-0.7.2.tgz" integrity sha512-K+g3WhyFxKPc0xIvcIjNyV1eaTVJTiuaHZpig7Xx0MuYRMoJLLvhLTnUXhFdR5Tu2l2QSyKwfyXDgZlzhULqFg== dependencies: ansicolors "^0.3.2" @@ -137,92 +130,33 @@ snake-case "^3.0.4" spok "^1.4.3" -"@noble/curves@^1.2.0": - version "1.3.0" - resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz" - integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA== - dependencies: - "@noble/hashes" "1.3.3" - "@noble/curves@^1.4.2": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" - integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== + version "1.8.1" + resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz" + integrity sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ== dependencies: - "@noble/hashes" "1.5.0" - -"@noble/hashes@1.3.3", "@noble/hashes@^1.3.1": - version "1.3.3" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz" - integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== - -"@noble/hashes@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" - integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== + "@noble/hashes" "1.7.1" -"@noble/hashes@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" - integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== +"@noble/hashes@1.7.1", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0": + version "1.7.1" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz" + integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ== "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@solana/buffer-layout@^4.0.0", "@solana/buffer-layout@^4.0.1": +"@solana/buffer-layout@^4.0.1": version "4.0.1" resolved "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz" integrity sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA== dependencies: buffer "~6.0.3" -"@solana/web3.js@^1.56.2": - version "1.87.6" - resolved "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.87.6.tgz" - integrity sha512-LkqsEBgTZztFiccZZXnawWa8qNCATEqE97/d0vIwjTclmVlc8pBpD1DmjfVHtZ1HS5fZorFlVhXfpwnCNDZfyg== - dependencies: - "@babel/runtime" "^7.23.2" - "@noble/curves" "^1.2.0" - "@noble/hashes" "^1.3.1" - "@solana/buffer-layout" "^4.0.0" - agentkeepalive "^4.3.0" - bigint-buffer "^1.1.5" - bn.js "^5.2.1" - borsh "^0.7.0" - bs58 "^4.0.1" - buffer "6.0.3" - fast-stable-stringify "^1.0.0" - jayson "^4.1.0" - node-fetch "^2.6.12" - rpc-websockets "^7.5.1" - superstruct "^0.14.2" - -"@solana/web3.js@^1.68.0": - version "1.95.3" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.3.tgz#70b5f4d76823f56b5af6403da51125fffeb65ff3" - integrity sha512-O6rPUN0w2fkNqx/Z3QJMB9L225Ex10PRDH8bTaIUPZXMPV0QP8ZpPvjQnXK+upUczlRgzHzd6SjKIha1p+I6og== - dependencies: - "@babel/runtime" "^7.25.0" - "@noble/curves" "^1.4.2" - "@noble/hashes" "^1.4.0" - "@solana/buffer-layout" "^4.0.1" - agentkeepalive "^4.5.0" - bigint-buffer "^1.1.5" - bn.js "^5.2.1" - borsh "^0.7.0" - bs58 "^4.0.1" - buffer "6.0.3" - fast-stable-stringify "^1.0.0" - jayson "^4.1.1" - node-fetch "^2.7.0" - rpc-websockets "^9.0.2" - superstruct "^2.0.2" - -"@solana/web3.js@^1.98.0": +"@solana/web3.js@^1.56.2", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.98.0": version "1.98.0" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.98.0.tgz#21ecfe8198c10831df6f0cfde7f68370d0405917" + resolved "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.0.tgz" integrity sha512-nz3Q5OeyGFpFCR+erX2f6JPt3sKhzhYcSycBCSPkWjzSVDh/Rr1FqTVMRe58FKO16/ivTUcuJjeS5MyBvpkbzA== dependencies: "@babel/runtime" "^7.25.0" @@ -242,11 +176,11 @@ superstruct "^2.0.2" "@swc/helpers@^0.5.11": - version "0.5.11" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.11.tgz#5bab8c660a6e23c13b2d23fcd1ee44a2db1b0cb7" - integrity sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A== + version "0.5.15" + resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz" + integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== dependencies: - tslib "^2.4.0" + tslib "^2.8.0" "@types/connect@^3.4.33": version "3.4.38" @@ -255,21 +189,14 @@ dependencies: "@types/node" "*" -"@types/node@*": - version "20.10.4" - resolved "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz" - integrity sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg== - dependencies: - undici-types "~5.26.4" - -"@types/node@^12.12.54": +"@types/node@*", "@types/node@^12.12.54": version "12.20.55" resolved "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== "@types/uuid@^8.3.4": version "8.3.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + resolved "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/ws@^7.4.4": @@ -280,9 +207,9 @@ "@types/node" "*" "@types/ws@^8.2.2": - version "8.5.10" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" - integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== + version "8.5.14" + resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz" + integrity sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw== dependencies: "@types/node" "*" @@ -294,7 +221,7 @@ JSONStream@^1.3.5: jsonparse "^1.2.0" through ">=2.2.7 <3" -agentkeepalive@^4.3.0, agentkeepalive@^4.5.0: +agentkeepalive@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz" integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== @@ -340,7 +267,7 @@ ansicolors@^0.3.2, ansicolors@~0.3.2: assert@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/assert/-/assert-2.1.0.tgz#6d92a238d05dc02e7427c881fb8be81c8448b2dd" + resolved "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz" integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw== dependencies: call-bind "^1.0.2" @@ -351,7 +278,7 @@ assert@^2.1.0: available-typed-arrays@^1.0.7: version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz" integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== dependencies: possible-typed-array-names "^1.0.0" @@ -429,7 +356,7 @@ bs58@^5.0.0: buffer-layout@^1.2.0, buffer-layout@^1.2.2: version "1.2.2" - resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.2.tgz#b9814e7c7235783085f9ca4966a0cfff112259d5" + resolved "https://registry.npmjs.org/buffer-layout/-/buffer-layout-1.2.2.tgz" integrity sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA== buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: @@ -441,9 +368,9 @@ buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: ieee754 "^1.2.1" bufferutil@^4.0.1: - version "4.0.8" - resolved "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz" - integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== + version "4.0.9" + resolved "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz" + integrity sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw== dependencies: node-gyp-build "^4.3.0" @@ -458,7 +385,7 @@ call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5: call-bind@^1.0.7: version "1.0.7" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== dependencies: es-define-property "^1.0.0" @@ -504,7 +431,7 @@ commander@^5.1.0: cross-fetch@^3.1.5: version "3.1.8" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" + resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz" integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== dependencies: node-fetch "^2.6.12" @@ -520,7 +447,7 @@ cross-spawn@^7.0.0: crypto-hash@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247" + resolved "https://registry.npmjs.org/crypto-hash/-/crypto-hash-1.3.0.tgz" integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg== debug@^4.1.1, debug@^4.3.3, debug@^4.3.4: @@ -541,7 +468,7 @@ define-data-property@^1.0.1, define-data-property@^1.1.1: define-data-property@^1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz" integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== dependencies: es-define-property "^1.0.0" @@ -587,14 +514,14 @@ emoji-regex@^9.2.2: es-define-property@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz" integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== dependencies: get-intrinsic "^1.2.4" es-errors@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== es6-promise@^4.0.3: @@ -616,7 +543,7 @@ eventemitter3@^4.0.7: eventemitter3@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== eyes@^0.1.8: @@ -675,7 +602,7 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: get-intrinsic@^1.2.4: version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz" integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== dependencies: es-errors "^1.3.0" @@ -728,7 +655,7 @@ has-property-descriptors@^1.0.0: has-property-descriptors@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz" integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== dependencies: es-define-property "^1.0.0" @@ -752,7 +679,7 @@ has-tostringtag@^1.0.0: has-tostringtag@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: has-symbols "^1.0.3" @@ -778,12 +705,12 @@ ieee754@^1.2.1: inherits@^2.0.3: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== is-arguments@^1.0.4: version "1.1.1" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz" integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== dependencies: call-bind "^1.0.2" @@ -801,14 +728,14 @@ is-fullwidth-code-point@^3.0.0: is-generator-function@^1.0.7: version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz" integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== dependencies: has-tostringtag "^1.0.0" is-nan@^1.3.2: version "1.3.2" - resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + resolved "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz" integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== dependencies: call-bind "^1.0.0" @@ -816,7 +743,7 @@ is-nan@^1.3.2: is-typed-array@^1.1.3: version "1.1.13" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz" integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== dependencies: which-typed-array "^1.1.14" @@ -840,28 +767,10 @@ jackspeak@^2.3.5: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" -jayson@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz" - integrity sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A== - dependencies: - "@types/connect" "^3.4.33" - "@types/node" "^12.12.54" - "@types/ws" "^7.4.4" - JSONStream "^1.3.5" - commander "^2.20.3" - delay "^5.0.0" - es6-promisify "^5.0.0" - eyes "^0.1.8" - isomorphic-ws "^4.0.1" - json-stringify-safe "^5.0.1" - uuid "^8.3.2" - ws "^7.4.5" - jayson@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.2.tgz#443c26a8658703e0b2e881117b09395d88b6982e" - integrity sha512-5nzMWDHy6f+koZOuYsArh2AXs73NfWYVlFyJJuCedr93GpY+Ku8qq10ropSXVfHK+H0T6paA88ww+/dV+1fBNA== + version "4.1.3" + resolved "https://registry.npmjs.org/jayson/-/jayson-4.1.3.tgz" + integrity sha512-LtXh5aYZodBZ9Fc3j6f2w+MTNcnxteMOrb+QgIouguGOulWi0lieEkOUg+HkjjFs0DGoWDds6bi4E9hpNFLulQ== dependencies: "@types/connect" "^3.4.33" "@types/node" "^12.12.54" @@ -973,13 +882,13 @@ node-fetch@^2.6.12, node-fetch@^2.7.0: whatwg-url "^5.0.0" node-gyp-build@^4.3.0: - version "4.7.1" - resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.7.1.tgz" - integrity sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg== + version "4.8.4" + resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz" + integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== object-is@^1.1.5: version "1.1.6" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" + resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz" integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== dependencies: call-bind "^1.0.7" @@ -1002,7 +911,7 @@ object.assign@^4.1.4: pako@^2.0.3: version "2.1.0" - resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + resolved "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz" integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== path-key@^3.1.0: @@ -1020,7 +929,7 @@ path-scurry@^1.10.1: possible-typed-array-names@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + resolved "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz" integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== prettier@^2.5.1: @@ -1030,13 +939,13 @@ prettier@^2.5.1: prettier@^3.3.3: version "3.3.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz" integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== regenerator-runtime@^0.14.0: - version "0.14.0" - resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz" - integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + version "0.14.1" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== rimraf@^5.0.5: version "5.0.5" @@ -1045,22 +954,9 @@ rimraf@^5.0.5: dependencies: glob "^10.3.7" -rpc-websockets@^7.5.1: - version "7.8.0" - resolved "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.8.0.tgz" - integrity sha512-AStkq6KDvSAmA4WiwlK1pDvj/33BWmExTATUokC0v+NhWekXSTNzXS5OGXeYwq501/pj6lBZMofg/h4dx4/tCg== - dependencies: - "@babel/runtime" "^7.17.2" - eventemitter3 "^4.0.7" - uuid "^8.3.2" - ws "^8.5.0" - optionalDependencies: - bufferutil "^4.0.1" - utf-8-validate "^5.0.2" - rpc-websockets@^9.0.2, rpc-websockets@^9.0.4: version "9.0.4" - resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.0.4.tgz#9d8ee82533b5d1e13d9ded729e3e38d0d8fa083f" + resolved "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.0.4.tgz" integrity sha512-yWZWN0M+bivtoNLnaDbtny4XchdAIF5Q4g/ZsC5UC61Ckbp0QczwO8fg44rV3uYmY4WHd+EZQbn90W1d8ojzqQ== dependencies: "@swc/helpers" "^0.5.11" @@ -1098,7 +994,7 @@ set-function-length@^1.1.1: set-function-length@^1.2.1: version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz" integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== dependencies: define-data-property "^1.1.4" @@ -1204,19 +1100,14 @@ strip-ansi@^7.0.1: dependencies: ansi-regex "^6.0.1" -superstruct@^0.14.2: - version "0.14.2" - resolved "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz" - integrity sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ== - superstruct@^0.15.4: version "0.15.5" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.15.5.tgz#0f0a8d3ce31313f0d84c6096cd4fa1bfdedc9dab" + resolved "https://registry.npmjs.org/superstruct/-/superstruct-0.15.5.tgz" integrity sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ== superstruct@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-2.0.2.tgz#3f6d32fbdc11c357deff127d591a39b996300c54" + resolved "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz" integrity sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A== supports-color@^7.1.0: @@ -1251,15 +1142,10 @@ tr46@~0.0.3: resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -tslib@^2.0.3: - version "2.6.2" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== - -tslib@^2.4.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" - integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +tslib@^2.0.3, tslib@^2.8.0: + version "2.8.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== typedoc-plugin-markdown@^3.17.1: version "3.17.1" @@ -1280,7 +1166,7 @@ typedoc@^0.25.4: typescript@^4.5.5: version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== uglify-js@^3.1.4: @@ -1288,11 +1174,6 @@ uglify-js@^3.1.4: resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz" integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - utf-8-validate@^5.0.2: version "5.0.10" resolved "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz" @@ -1302,7 +1183,7 @@ utf-8-validate@^5.0.2: util@^0.12.5: version "0.12.5" - resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + resolved "https://registry.npmjs.org/util/-/util-0.12.5.tgz" integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== dependencies: inherits "^2.0.3" @@ -1341,7 +1222,7 @@ whatwg-url@^5.0.0: which-typed-array@^1.1.14, which-typed-array@^1.1.2: version "1.1.15" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz" integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== dependencies: available-typed-arrays "^1.0.7" @@ -1380,20 +1261,15 @@ wrap-ansi@^8.1.0: string-width "^5.0.1" strip-ansi "^7.0.1" -ws@^7.4.5: - version "7.5.9" - resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== - ws@^7.5.10: version "7.5.10" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== ws@^8.5.0: - version "8.15.1" - resolved "https://registry.npmjs.org/ws/-/ws-8.15.1.tgz" - integrity sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ== + version "8.18.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== yallist@^4.0.0: version "4.0.0" diff --git a/crates/bolt-lang/Cargo.toml b/crates/bolt-lang/Cargo.toml index 05eb5dc5..45465695 100644 --- a/crates/bolt-lang/Cargo.toml +++ b/crates/bolt-lang/Cargo.toml @@ -30,8 +30,11 @@ bolt-attribute-bolt-arguments = { workspace = true } world = { workspace = true } bolt-system = { workspace = true } +# Session Keys +session-keys = { workspace = true } + # Delegation -ephemeral-rollups-sdk = { workspace = true , features = ["anchor"]} +ephemeral-rollups-sdk = { workspace = true, features = ["anchor"]} # Other dependencies serde = { workspace = true } diff --git a/crates/bolt-lang/attribute/bolt-program/src/lib.rs b/crates/bolt-lang/attribute/bolt-program/src/lib.rs index 7cdbe6fc..0f548652 100644 --- a/crates/bolt-lang/attribute/bolt-program/src/lib.rs +++ b/crates/bolt-lang/attribute/bolt-program/src/lib.rs @@ -148,17 +148,28 @@ fn generate_update(component_type: &Type) -> (TokenStream2, TokenStream2) { quote! { #[automatically_derived] pub fn update(ctx: Context, data: Vec) -> Result<()> { + if let Some(session_token) = &ctx.accounts.session_token { + if ctx.accounts.bolt_component.bolt_metadata.authority == World::id() { + require!(Clock::get()?.unix_timestamp < session_token.valid_until, bolt_lang::session_keys::SessionError::InvalidToken); + } else { + let validity_ctx = bolt_lang::session_keys::ValidityChecker { + session_token: session_token.clone(), + session_signer: ctx.accounts.authority.clone(), + authority: ctx.accounts.bolt_component.bolt_metadata.authority.clone(), + target_program: World::id(), + }; + require!(session_token.validate(validity_ctx)?, bolt_lang::session_keys::SessionError::InvalidToken); + require_eq!(ctx.accounts.bolt_component.bolt_metadata.authority, session_token.authority, bolt_lang::session_keys::SessionError::InvalidToken); + } + } else { + require!(ctx.accounts.bolt_component.bolt_metadata.authority == World::id() || (ctx.accounts.bolt_component.bolt_metadata.authority == *ctx.accounts.authority.key && ctx.accounts.authority.is_signer), BoltError::InvalidAuthority); + } + // Check if the instruction is called from the world program let instruction = anchor_lang::solana_program::sysvar::instructions::get_instruction_relative( 0, &ctx.accounts.instruction_sysvar_account.to_account_info() ).unwrap(); - if instruction.program_id != World::id() { - return Err(BoltError::InvalidCaller.into()); - } - // Check if the authority is authorized to modify the data - if ctx.accounts.bolt_component.bolt_metadata.authority != World::id() && (ctx.accounts.bolt_component.bolt_metadata.authority != *ctx.accounts.authority.key || !ctx.accounts.authority.is_signer) { - return Err(BoltError::InvalidAuthority.into()); - } + require_eq!(instruction.program_id, World::id(), BoltError::InvalidCaller); ctx.accounts.bolt_component.set_inner(<#component_type>::try_from_slice(&data)?); Ok(()) @@ -171,10 +182,11 @@ fn generate_update(component_type: &Type) -> (TokenStream2, TokenStream2) { #[account(mut)] pub bolt_component: Account<'info, #component_type>, #[account()] - /// CHECK: The authority of the component - pub authority: AccountInfo<'info>, + pub authority: Signer<'info>, #[account(address = anchor_lang::solana_program::sysvar::instructions::id())] pub instruction_sysvar_account: UncheckedAccount<'info>, + #[account(constraint = session_token.to_account_info().owner == &bolt_lang::session_keys::ID)] + pub session_token: Option>, } }, ) diff --git a/crates/bolt-lang/attribute/system-input/src/lib.rs b/crates/bolt-lang/attribute/system-input/src/lib.rs index 4544bbc0..2701aa8c 100644 --- a/crates/bolt-lang/attribute/system-input/src/lib.rs +++ b/crates/bolt-lang/attribute/system-input/src/lib.rs @@ -76,8 +76,9 @@ pub fn system_input(_attr: TokenStream, item: TokenStream) -> TokenStream { #[derive(Accounts)] pub struct #name<'info> { #(#transformed_fields)* + /// CHECK: Authority check #[account()] - pub authority: Signer<'info>, + pub authority: AccountInfo<'info>, } }; @@ -136,8 +137,9 @@ pub fn system_input(_attr: TokenStream, item: TokenStream) -> TokenStream { #[derive(Accounts)] pub struct VariadicBoltComponents<'info> { + /// CHECK: Authority check #[account()] - pub authority: Signer<'info>, + pub authority: AccountInfo<'info>, } }; diff --git a/crates/bolt-lang/attribute/system/src/lib.rs b/crates/bolt-lang/attribute/system/src/lib.rs index d8966b12..9fc47052 100644 --- a/crates/bolt-lang/attribute/system/src/lib.rs +++ b/crates/bolt-lang/attribute/system/src/lib.rs @@ -1,6 +1,6 @@ use proc_macro::TokenStream; use proc_macro2::Ident; -use quote::{quote, ToTokens}; +use quote::{quote, ToTokens, TokenStreamExt}; use syn::{ parse_macro_input, parse_quote, visit_mut::VisitMut, Expr, FnArg, GenericArgument, ItemFn, ItemMod, ItemStruct, PathArguments, ReturnType, Stmt, Type, TypePath, @@ -34,9 +34,8 @@ struct Extractor { /// } /// ``` #[proc_macro_attribute] -pub fn system(attr: TokenStream, item: TokenStream) -> TokenStream { +pub fn system(_attr: TokenStream, item: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(item as ItemMod); - let _attr = parse_macro_input!(attr as syn::AttributeArgs); // Extract the number of components from the module let mut extractor = Extractor::default(); @@ -153,14 +152,22 @@ impl VisitMut for SystemTransform { for item in content.iter_mut() { match item { syn::Item::Fn(item_fn) => self.visit_item_fn_mut(item_fn), - syn::Item::Struct(item_struct) + syn::Item::Struct(item_struct) => { + if let Some(attr) = item_struct + .attrs + .iter_mut() + .find(|attr| attr.path.is_ident("system_input")) + { + attr.tokens.append_all(quote! { (session_key) }); + } if item_struct .attrs .iter() - .any(|attr| attr.path.is_ident("extra_accounts")) => - { - extra_accounts_struct_name = Some(&item_struct.ident); - break; + .any(|attr| attr.path.is_ident("extra_accounts")) + { + extra_accounts_struct_name = Some(&item_struct.ident); + break; + } } _ => {} } diff --git a/crates/bolt-lang/src/lib.rs b/crates/bolt-lang/src/lib.rs index b16d870e..714d1c99 100644 --- a/crates/bolt-lang/src/lib.rs +++ b/crates/bolt-lang/src/lib.rs @@ -7,6 +7,8 @@ pub use anchor_lang::{ AccountDeserialize, AccountSerialize, AnchorDeserialize, AnchorSerialize, Bumps, Result, }; +pub use session_keys; + pub use bolt_attribute_bolt_arguments::arguments; pub use bolt_attribute_bolt_component::component; pub use bolt_attribute_bolt_component_deserialize::component_deserialize; diff --git a/crates/programs/bolt-component/src/lib.rs b/crates/programs/bolt-component/src/lib.rs index aac60151..b81d64a5 100644 --- a/crates/programs/bolt-component/src/lib.rs +++ b/crates/programs/bolt-component/src/lib.rs @@ -6,58 +6,27 @@ declare_id!("CmP2djJgABZ4cRokm4ndxuq6LerqpNHLBsaUv2XKEJua"); pub mod bolt_component { use super::*; - pub fn initialize(ctx: Context) -> Result<()> { - let instruction = - anchor_lang::solana_program::sysvar::instructions::get_instruction_relative( - 0, - &ctx.accounts.instruction_sysvar_account.to_account_info(), - ) - .unwrap(); - if instruction.program_id == id() { - panic!("The instruction must be called from a CPI"); - } - ctx.accounts.data.bolt_metadata.authority = *ctx.accounts.authority.key; + pub fn initialize(_ctx: Context) -> Result<()> { Ok(()) } - pub fn apply(_ctx: Context, _args: Vec) -> Result<()> { - Ok(()) - } - - #[derive(Accounts)] - pub struct Apply<'info> { - #[account(mut)] - pub bolt_component: Account<'info, Component>, - /// CHECK: The system can modify the data of the component - #[account()] - pub bolt_system: UncheckedAccount<'info>, - #[account()] - pub authority: Signer<'info>, - } - - pub fn update(ctx: Context, _data: Vec) -> Result<()> { - let instruction = - anchor_lang::solana_program::sysvar::instructions::get_instruction_relative( - 0, - &ctx.accounts.instruction_sysvar_account.to_account_info(), - ) - .unwrap(); - if instruction.program_id == id() { - panic!("The instruction must be called from a CPI"); - } + pub fn update(_ctx: Context, _data: Vec) -> Result<()> { Ok(()) } #[derive(Accounts)] pub struct Update<'info> { #[account(mut)] - pub bolt_component: Account<'info, Component>, + /// CHECK: The component to update + pub bolt_component: UncheckedAccount<'info>, #[account()] /// CHECK: The authority of the component - pub authority: AccountInfo<'info>, + pub authority: Signer<'info>, #[account(address = anchor_lang::solana_program::sysvar::instructions::id())] /// CHECK: The instruction sysvar pub instruction_sysvar_account: AccountInfo<'info>, + #[account()] + pub session_token: Option>, } } @@ -65,8 +34,9 @@ pub mod bolt_component { pub struct Initialize<'info> { #[account(mut)] pub payer: Signer<'info>, - #[account(init_if_needed, payer = payer, space = Component::size(), seeds = [Component::seed(), entity.key().as_ref()], bump)] - pub data: Account<'info, Component>, + #[account(mut)] + /// CHECK: The component to initialize + pub data: UncheckedAccount<'info>, #[account()] /// CHECK: A generic entity account pub entity: AccountInfo<'info>, @@ -79,31 +49,27 @@ pub struct Initialize<'info> { pub system_program: Program<'info, System>, } -// Component data -#[account] -#[derive(InitSpace, Default, Copy)] -pub struct Component { - pub position: Position, - pub bolt_metadata: BoltMetadata, -} - -impl Component { - pub fn size() -> usize { - 8 + Component::INIT_SPACE - } - pub fn seed() -> &'static [u8] { - b"origin-component" - } +#[derive(InitSpace, AnchorSerialize, AnchorDeserialize, Default, Copy, Clone)] +pub struct BoltMetadata { + pub authority: Pubkey, } -#[derive(InitSpace, AnchorSerialize, AnchorDeserialize, Default, Copy, Clone)] -pub struct Position { - pub x: i64, - pub y: i64, - pub z: i64, +#[cfg(feature = "cpi")] +pub trait CpiContextBuilder<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized { + fn build_cpi_context( + self, + program: AccountInfo<'info>, + ) -> CpiContext<'info, 'info, 'info, 'info, Self>; } -#[derive(InitSpace, AnchorSerialize, AnchorDeserialize, Default, Copy, Clone)] -pub struct BoltMetadata { - pub authority: Pubkey, +#[cfg(feature = "cpi")] +impl<'info> CpiContextBuilder<'info> for cpi::accounts::Update<'info> { + fn build_cpi_context( + mut self, + program: AccountInfo<'info>, + ) -> CpiContext<'info, 'info, 'info, 'info, Self> { + let cpi_program = program.to_account_info(); + self.session_token = Some(self.session_token.unwrap_or(program.to_account_info())); + CpiContext::new(cpi_program, self) + } } diff --git a/crates/programs/bolt-system/src/lib.rs b/crates/programs/bolt-system/src/lib.rs index 79ec25fd..46f855ea 100644 --- a/crates/programs/bolt-system/src/lib.rs +++ b/crates/programs/bolt-system/src/lib.rs @@ -6,13 +6,14 @@ declare_id!("7X4EFsDJ5aYTcEjKzJ94rD8FRKgQeXC89fkpeTS4KaqP"); #[program] pub mod bolt_system { use super::*; - pub fn bolt_execute(_ctx: Context, _args: Vec) -> Result>> { + pub fn bolt_execute(_ctx: Context, _args: Vec) -> Result>> { Ok(Vec::new()) } } #[derive(Accounts, Clone)] -pub struct SetData<'info> { +pub struct BoltExecute<'info> { + /// CHECK: authority check #[account()] - pub authority: Signer<'info>, + pub authority: AccountInfo<'info>, } diff --git a/crates/programs/world/src/lib.rs b/crates/programs/world/src/lib.rs index 51e5c0dc..be8ac2cb 100644 --- a/crates/programs/world/src/lib.rs +++ b/crates/programs/world/src/lib.rs @@ -1,5 +1,6 @@ #![allow(clippy::manual_unwrap_or_default)] use anchor_lang::prelude::*; +use bolt_component::CpiContextBuilder; use std::collections::BTreeSet; #[cfg(not(feature = "no-entrypoint"))] @@ -267,7 +268,10 @@ pub mod world { ctx: Context<'_, '_, '_, 'info, Apply<'info>>, args: Vec, ) -> Result<()> { - if !ctx.accounts.authority.is_signer && ctx.accounts.authority.key != &ID { + if !ctx.accounts.authority.is_signer + && ctx.accounts.authority.key != &ID + && ctx.accounts.session_token.is_none() + { return Err(WorldError::InvalidAuthority.into()); } if !ctx.accounts.world.permissionless @@ -320,6 +324,7 @@ pub mod world { component, ctx.accounts.authority.clone(), ctx.accounts.instruction_sysvar_account.clone(), + ctx.accounts.session_token.clone(), ), result, )?; @@ -340,15 +345,18 @@ pub mod world { pub instruction_sysvar_account: UncheckedAccount<'info>, #[account()] pub world: Account<'info, World>, + #[account()] + pub session_token: Option>, } impl<'info> Apply<'info> { pub fn build( &self, - ) -> CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::SetData<'info>> { - let authority = self.authority.to_account_info(); + ) -> CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::BoltExecute<'info>> { let cpi_program = self.bolt_system.to_account_info(); - let cpi_accounts = bolt_system::cpi::accounts::SetData { authority }; + let cpi_accounts = bolt_system::cpi::accounts::BoltExecute { + authority: self.authority.to_account_info(), + }; CpiContext::new(cpi_program, cpi_accounts) } } @@ -590,14 +598,17 @@ pub fn build_update_context<'info>( bolt_component: AccountInfo<'info>, authority: Signer<'info>, instruction_sysvar_account: UncheckedAccount<'info>, + session_token: Option>, ) -> CpiContext<'info, 'info, 'info, 'info, bolt_component::cpi::accounts::Update<'info>> { let authority = authority.to_account_info(); let instruction_sysvar_account = instruction_sysvar_account.to_account_info(); let cpi_program = component_program; - let cpi_accounts = bolt_component::cpi::accounts::Update { + let session_token = session_token.map(|x| x.to_account_info()); + bolt_component::cpi::accounts::Update { bolt_component, authority, instruction_sysvar_account, - }; - CpiContext::new(cpi_program, cpi_accounts) + session_token, + } + .build_cpi_context(cpi_program) } diff --git a/examples/system-simple-movement/src/lib.rs b/examples/system-simple-movement/src/lib.rs index bb5e8ba1..311996d7 100644 --- a/examples/system-simple-movement/src/lib.rs +++ b/examples/system-simple-movement/src/lib.rs @@ -4,7 +4,6 @@ declare_id!("FSa6qoJXFBR3a7ThQkTAMrC15p6NkchPEjBdd4n6dXxA"); #[system] pub mod system_simple_movement { - pub fn execute(ctx: Context, args: Args) -> Result { // Compute the new position based on the direction let (dx, dy) = match args.direction { diff --git a/scripts/lint.sh b/scripts/lint.sh index 73468d1e..ea7fd042 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -3,11 +3,11 @@ SCRIPT_DIR=$(dirname "$0") PROJECT_DIR="$SCRIPT_DIR/.." pushd "$PROJECT_DIR" echo "### Checking formatting..." -cargo fmt -- --check --verbose +cargo fmt -- --verbose echo "### Checking clippy..." -cargo clippy -- --deny=warnings +cargo clippy --fix -- --deny=warnings echo "### Checking yarn lint..." -yarn lint +yarn lint --write popd diff --git a/tests/bolt.intermediate-level.api.ts b/tests/bolt.intermediate-level.api.ts deleted file mode 100644 index 871a41c7..00000000 --- a/tests/bolt.intermediate-level.api.ts +++ /dev/null @@ -1,713 +0,0 @@ -import { Keypair, type PublicKey } from "@solana/web3.js"; -import { type Position } from "../target/types/position"; -import { type Velocity } from "../target/types/velocity"; -import { type BoltComponent } from "../target/types/bolt_component"; -import { type SystemSimpleMovement } from "../target/types/system_simple_movement"; -import { type SystemFly } from "../target/types/system_fly"; -import { type SystemApplyVelocity } from "../target/types/system_apply_velocity"; -import { expect } from "chai"; -import type BN from "bn.js"; -import { - AddEntity, - DELEGATION_PROGRAM_ID, - InitializeRegistry, - InitializeComponent, - InitializeNewWorld, - ApplySystem, - DelegateComponent, - AddAuthority, - RemoveAuthority, - ApproveSystem, - RemoveSystem, - type Program, - anchor, - web3, - WORLD_PROGRAM_IDL as World, -} from "../clients/bolt-sdk"; - -enum Direction { - Left = "Left", - Right = "Right", - Up = "Up", - Down = "Down", -} - -function padCenter(value: string, width: number) { - const length = value.length; - if (width <= length) { - return value; - } - const padding = (width - length) / 2; - const align = width - padding; - return value.padStart(align, " ").padEnd(width, " "); -} - -function logPosition(title: string, { x, y, z }: { x: BN; y: BN; z: BN }) { - console.log(" +----------------------------------+"); - console.log(` | ${padCenter(title, 32)} |`); - console.log(" +-----------------+----------------+"); - console.log(` | X Position | ${String(x).padEnd(14, " ")} |`); - console.log(` | Y Position | ${String(y).padEnd(14, " ")} |`); - console.log(` | Z Position | ${String(z).padEnd(14, " ")} |`); - console.log(" +-----------------+----------------+"); -} - -function logVelocity( - title: string, - { x, y, z, lastApplied }: { x: BN; y: BN; z: BN; lastApplied: BN }, -) { - console.log(" +----------------------------------+"); - console.log(` | ${padCenter(title, 32)} |`); - console.log(" +-----------------+----------------+"); - console.log(` | X Velocity | ${String(x).padEnd(14, " ")} |`); - console.log(` | Y Velocity | ${String(y).padEnd(14, " ")} |`); - console.log(` | Z Velocity | ${String(z).padEnd(14, " ")} |`); - console.log(` | Last Applied | ${String(lastApplied).padEnd(14, " ")} |`); - console.log(" +-----------------+----------------+"); -} - -describe("bolt", () => { - const provider = anchor.AnchorProvider.env(); - anchor.setProvider(provider); - - const worldProgram = anchor.workspace.World as Program; - - const boltComponentProgram = anchor.workspace - .BoltComponent as Program; - - const exampleComponentPosition = anchor.workspace - .Position as Program; - const exampleComponentVelocity = anchor.workspace - .Velocity as Program; - - const exampleSystemSimpleMovement = ( - anchor.workspace.SystemSimpleMovement as Program - ).programId; - const exampleSystemFly = (anchor.workspace.SystemFly as Program) - .programId; - const exampleSystemApplyVelocity = ( - anchor.workspace.SystemApplyVelocity as Program - ).programId; - - let worldPda: PublicKey; - - let entity1Pda: PublicKey; - let entity2Pda: PublicKey; - let entity4Pda: PublicKey; - let entity5Pda: PublicKey; - - let componentPositionEntity1Pda: PublicKey; - let componentVelocityEntity1Pda: PublicKey; - - let componentPositionEntity4Pda: PublicKey; - let componentPositionEntity5Pda: PublicKey; - - const secondAuthority = Keypair.generate().publicKey; - - it("InitializeRegistry", async () => { - const initializeRegistry = await InitializeRegistry({ - payer: provider.wallet.publicKey, - connection: provider.connection, - }); - try { - await provider.sendAndConfirm(initializeRegistry.transaction); - } catch (error) { - // This is expected to fail because the registry already exists if another api level test ran before - } - }); - - it("InitializeNewWorld", async () => { - const initializeNewWorld = await InitializeNewWorld({ - payer: provider.wallet.publicKey, - connection: provider.connection, - }); - const signature = await provider.sendAndConfirm( - initializeNewWorld.transaction, - ); - console.log("InitializeNewWorld signature: ", signature); - worldPda = initializeNewWorld.worldPda; // Saved for later - }); - - it("Add authority", async () => { - const addAuthority = await AddAuthority({ - authority: provider.wallet.publicKey, - newAuthority: provider.wallet.publicKey, - world: worldPda, - connection: provider.connection, - }); - await provider.sendAndConfirm(addAuthority.transaction, [], { - skipPreflight: true, - }); - const worldAccount = await worldProgram.account.world.fetch(worldPda); - expect( - worldAccount.authorities.some((auth) => - auth.equals(provider.wallet.publicKey), - ), - ); - }); - - it("Add a second authority", async () => { - const addAuthority = await AddAuthority({ - authority: provider.wallet.publicKey, - newAuthority: secondAuthority, - world: worldPda, - connection: provider.connection, - }); - const signature = await provider.sendAndConfirm(addAuthority.transaction); - console.log(`Add Authority signature: ${signature}`); - const worldAccount = await worldProgram.account.world.fetch(worldPda); - expect( - worldAccount.authorities.some((auth) => auth.equals(secondAuthority)), - ); - }); - - it("Remove an authority", async () => { - const addAuthority = await RemoveAuthority({ - authority: provider.wallet.publicKey, - authorityToDelete: secondAuthority, - world: worldPda, - connection: provider.connection, - }); - const signature = await provider.sendAndConfirm(addAuthority.transaction); - console.log(`Add Authority signature: ${signature}`); - const worldAccount = await worldProgram.account.world.fetch(worldPda); - expect( - !worldAccount.authorities.some((auth) => auth.equals(secondAuthority)), - ); - }); - - it("InitializeNewWorld 2", async () => { - const initializeNewWorld = await InitializeNewWorld({ - payer: provider.wallet.publicKey, - connection: provider.connection, - }); - await provider.sendAndConfirm(initializeNewWorld.transaction); - }); - - it("Add entity 1", async () => { - const addEntity = await AddEntity({ - payer: provider.wallet.publicKey, - world: worldPda, - connection: provider.connection, - }); - await provider.sendAndConfirm(addEntity.transaction); - entity1Pda = addEntity.entityPda; // Saved for later - }); - - it("Add entity 2", async () => { - const addEntity = await AddEntity({ - payer: provider.wallet.publicKey, - world: worldPda, - connection: provider.connection, - }); - await provider.sendAndConfirm(addEntity.transaction); - entity2Pda = addEntity.entityPda; // Saved for later - }); - - it("Add entity 3", async () => { - const addEntity = await AddEntity({ - payer: provider.wallet.publicKey, - world: worldPda, - connection: provider.connection, - }); - await provider.sendAndConfirm(addEntity.transaction); - }); - - it("Add entity 4 (with seed)", async () => { - const addEntity = await AddEntity({ - payer: provider.wallet.publicKey, - world: worldPda, - seed: Buffer.from("custom-seed"), - connection: provider.connection, - }); - await provider.sendAndConfirm(addEntity.transaction); - entity4Pda = addEntity.entityPda; - }); - - it("Add entity 5", async () => { - const addEntity = await AddEntity({ - payer: provider.wallet.publicKey, - world: worldPda, - connection: provider.connection, - }); - await provider.sendAndConfirm(addEntity.transaction); - entity5Pda = addEntity.entityPda; // Saved for later - }); - - it("Initialize Original Component on Entity 1, trough the world instance", async () => { - const initializeComponent = await InitializeComponent({ - payer: provider.wallet.publicKey, - entity: entity1Pda, - seed: "origin-component", - componentId: boltComponentProgram.programId, - }); - await provider.sendAndConfirm(initializeComponent.transaction); - }); - - it("Initialize Original Component on Entity 2, trough the world instance", async () => { - const initializeComponent = await InitializeComponent({ - payer: provider.wallet.publicKey, - entity: entity2Pda, - seed: "origin-component", - componentId: boltComponentProgram.programId, - }); - await provider.sendAndConfirm(initializeComponent.transaction); - }); - - it("Initialize Position Component on Entity 1", async () => { - const initializeComponent = await InitializeComponent({ - payer: provider.wallet.publicKey, - entity: entity1Pda, - componentId: exampleComponentPosition.programId, - }); - await provider.sendAndConfirm(initializeComponent.transaction); - componentPositionEntity1Pda = initializeComponent.componentPda; // Saved for later - }); - - it("Initialize Velocity Component on Entity 1 (with seed)", async () => { - const initializeComponent = await InitializeComponent({ - payer: provider.wallet.publicKey, - entity: entity1Pda, - componentId: exampleComponentVelocity.programId, - seed: "component-velocity", - }); - await provider.sendAndConfirm(initializeComponent.transaction); - componentVelocityEntity1Pda = initializeComponent.componentPda; // Saved for later - }); - - it("Initialize Position Component on Entity 2", async () => { - const initializeComponent = await InitializeComponent({ - payer: provider.wallet.publicKey, - entity: entity2Pda, - componentId: exampleComponentPosition.programId, - }); - await provider.sendAndConfirm(initializeComponent.transaction); - }); - - it("Initialize Position Component on Entity 4", async () => { - const initializeComponent = await InitializeComponent({ - payer: provider.wallet.publicKey, - entity: entity4Pda, - componentId: exampleComponentPosition.programId, - }); - await provider.sendAndConfirm(initializeComponent.transaction); - componentPositionEntity4Pda = initializeComponent.componentPda; // Saved for later - }); - - it("Initialize Position Component on Entity 5 (with authority)", async () => { - const initializeComponent = await InitializeComponent({ - payer: provider.wallet.publicKey, - entity: entity5Pda, - componentId: exampleComponentPosition.programId, - authority: provider.wallet.publicKey, - }); - await provider.sendAndConfirm(initializeComponent.transaction); - componentPositionEntity5Pda = initializeComponent.componentPda; // Saved for later - }); - - it("Check Position on Entity 1 is default", async () => { - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity1Pda, - ); - logPosition("Default State: Entity 1", position); - expect(position.x.toNumber()).to.equal(0); - expect(position.y.toNumber()).to.equal(0); - expect(position.z.toNumber()).to.equal(0); - }); - - it("Apply Simple Movement System (Up) on Entity 1 using Apply", async () => { - const apply = await ApplySystem({ - authority: provider.wallet.publicKey, - systemId: exampleSystemSimpleMovement, - entities: [ - { - entity: entity1Pda, - components: [{ componentId: exampleComponentPosition.programId }], - }, - ], - world: worldPda, - args: { direction: Direction.Up }, - }); - - await provider.sendAndConfirm(apply.transaction); - - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity1Pda, - ); - logPosition("Movement System: Entity 1", position); - expect(position.x.toNumber()).to.equal(0); - expect(position.y.toNumber()).to.equal(1); - expect(position.z.toNumber()).to.equal(0); - }); - - it("Apply Simple Movement System (Up) on Entity 1", async () => { - const applySystem = await ApplySystem({ - authority: provider.wallet.publicKey, - systemId: exampleSystemSimpleMovement, - world: worldPda, - entities: [ - { - entity: entity1Pda, - components: [{ componentId: exampleComponentPosition.programId }], - }, - ], - args: { - direction: Direction.Up, - }, - }); - const signature = await provider.sendAndConfirm( - applySystem.transaction, - [], - { skipPreflight: true }, - ); - console.log(`Signature: ${signature}`); - - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity1Pda, - ); - logPosition("Movement System: Entity 1", position); - expect(position.x.toNumber()).to.equal(0); - expect(position.y.toNumber()).to.equal(2); - expect(position.z.toNumber()).to.equal(0); - }); - - it("Apply Simple Movement System (Right) on Entity 1", async () => { - const applySystem = await ApplySystem({ - authority: provider.wallet.publicKey, - systemId: exampleSystemSimpleMovement, - world: worldPda, - entities: [ - { - entity: entity1Pda, - components: [{ componentId: exampleComponentPosition.programId }], - }, - ], - args: { - direction: Direction.Right, - }, - }); - await provider.sendAndConfirm(applySystem.transaction); - - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity1Pda, - ); - logPosition("Movement System: Entity 1", position); - expect(position.x.toNumber()).to.equal(1); - expect(position.y.toNumber()).to.equal(2); - expect(position.z.toNumber()).to.equal(0); - }); - - it("Apply Fly System on Entity 1", async () => { - const applySystem = await ApplySystem({ - authority: provider.wallet.publicKey, - systemId: exampleSystemFly, - world: worldPda, - entities: [ - { - entity: entity1Pda, - components: [{ componentId: exampleComponentPosition.programId }], - }, - ], - }); - await provider.sendAndConfirm(applySystem.transaction); - - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity1Pda, - ); - logPosition("Fly System: Entity 1", position); - expect(position.x.toNumber()).to.equal(1); - expect(position.y.toNumber()).to.equal(2); - expect(position.z.toNumber()).to.equal(1); - }); - - it("Apply System Velocity on Entity 1", async () => { - const applySystem = await ApplySystem({ - authority: provider.wallet.publicKey, - systemId: exampleSystemApplyVelocity, - world: worldPda, - entities: [ - { - entity: entity1Pda, - components: [ - { - componentId: exampleComponentVelocity.programId, - seed: "component-velocity", - }, - { componentId: exampleComponentPosition.programId }, - ], - }, - ], - }); - await provider.sendAndConfirm(applySystem.transaction); - - const velocity = await exampleComponentVelocity.account.velocity.fetch( - componentVelocityEntity1Pda, - ); - logVelocity("Apply System Velocity: Entity 1", velocity); - expect(velocity.x.toNumber()).to.equal(10); - expect(velocity.y.toNumber()).to.equal(0); - expect(velocity.z.toNumber()).to.equal(0); - expect(velocity.lastApplied.toNumber()).to.not.equal(0); - - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity1Pda, - ); - logPosition("Apply System Velocity: Entity 1", position); - expect(position.x.toNumber()).to.greaterThan(1); - expect(position.y.toNumber()).to.equal(2); - expect(position.z.toNumber()).to.equal(1); - }); - - it("Apply System Velocity on Entity 1, with Clock external account", async () => { - const applySystem = await ApplySystem({ - authority: provider.wallet.publicKey, - systemId: exampleSystemApplyVelocity, - world: worldPda, - entities: [ - { - entity: entity1Pda, - components: [ - { - componentId: exampleComponentVelocity.programId, - seed: "component-velocity", - }, - { componentId: exampleComponentPosition.programId }, - ], - }, - ], - extraAccounts: [ - { - pubkey: new web3.PublicKey( - "SysvarC1ock11111111111111111111111111111111", - ), - isWritable: false, - isSigner: false, - }, - ], - }); - await provider.sendAndConfirm(applySystem.transaction); - - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity1Pda, - ); - logPosition("Apply System Velocity: Entity 1", position); - expect(position.x.toNumber()).to.greaterThan(1); - expect(position.y.toNumber()).to.equal(2); - expect(position.z.toNumber()).to.equal(300); - }); - - it("Apply Fly System on Entity 4", async () => { - const applySystem = await ApplySystem({ - authority: provider.wallet.publicKey, - systemId: exampleSystemFly, - world: worldPda, - entities: [ - { - entity: entity4Pda, - components: [{ componentId: exampleComponentPosition.programId }], - }, - ], - }); - await provider.sendAndConfirm(applySystem.transaction); - - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity4Pda, - ); - logPosition("Fly System: Entity 4", position); - expect(position.x.toNumber()).to.equal(0); - expect(position.y.toNumber()).to.equal(0); - expect(position.z.toNumber()).to.equal(1); - }); - - it("Apply Fly System on Entity 5 (should fail with wrong authority)", async () => { - const positionBefore = - await exampleComponentPosition.account.position.fetch( - componentPositionEntity5Pda, - ); - - const applySystem = await ApplySystem({ - authority: provider.wallet.publicKey, - systemId: exampleSystemFly, - world: worldPda, - entities: [ - { - entity: entity5Pda, - components: [{ componentId: exampleComponentPosition.programId }], - }, - ], - }); - - let failed = false; - try { - await provider.sendAndConfirm(applySystem.transaction); - } catch (error) { - failed = true; - // console.log("error", error); - expect(error.logs.join("\n")).to.contain("Error Code: InvalidAuthority"); - } - expect(failed).to.equal(true); - - const positionAfter = await exampleComponentPosition.account.position.fetch( - componentPositionEntity5Pda, - ); - - expect(positionBefore.x.toNumber()).to.equal(positionAfter.x.toNumber()); - expect(positionBefore.y.toNumber()).to.equal(positionAfter.y.toNumber()); - expect(positionBefore.z.toNumber()).to.equal(positionAfter.z.toNumber()); - }); - - it("Whitelist System", async () => { - const approveSystem = await ApproveSystem({ - authority: provider.wallet.publicKey, - systemToApprove: exampleSystemFly, - world: worldPda, - }); - - const signature = await provider.sendAndConfirm( - approveSystem.transaction, - [], - { skipPreflight: true }, - ); - console.log(`Whitelist 2 system approval signature: ${signature}`); - - // Get World and check permissionless and systems - const worldAccount = await worldProgram.account.world.fetch(worldPda); - expect(worldAccount.permissionless).to.equal(false); - expect(worldAccount.systems.length).to.be.greaterThan(0); - }); - - it("Whitelist System 2", async () => { - const approveSystem = await ApproveSystem({ - authority: provider.wallet.publicKey, - systemToApprove: exampleSystemApplyVelocity, - world: worldPda, - }); - - const signature = await provider.sendAndConfirm( - approveSystem.transaction, - [], - { skipPreflight: true }, - ); - console.log(`Whitelist 2 system approval signature: ${signature}`); - - // Get World and check permissionless and systems - const worldAccount = await worldProgram.account.world.fetch(worldPda); - expect(worldAccount.permissionless).to.equal(false); - expect(worldAccount.systems.length).to.be.greaterThan(0); - }); - - it("Apply Fly System on Entity 1", async () => { - const applySystem = await ApplySystem({ - authority: provider.wallet.publicKey, - systemId: exampleSystemFly, - world: worldPda, - entities: [ - { - entity: entity1Pda, - components: [{ componentId: exampleComponentPosition.programId }], - }, - ], - }); - await provider.sendAndConfirm(applySystem.transaction); - }); - - it("Remove System 1", async () => { - const approveSystem = await RemoveSystem({ - authority: provider.wallet.publicKey, - systemToRemove: exampleSystemFly, - world: worldPda, - }); - - const signature = await provider.sendAndConfirm( - approveSystem.transaction, - [], - { skipPreflight: true }, - ); - console.log(`Whitelist 2 system approval signature: ${signature}`); - - // Get World and check permissionless and systems - const worldAccount = await worldProgram.account.world.fetch(worldPda); - expect(worldAccount.permissionless).to.equal(false); - expect(worldAccount.systems.length).to.be.greaterThan(0); - }); - - it("Apply Invalid Fly System on Entity 1", async () => { - const applySystem = await ApplySystem({ - authority: provider.wallet.publicKey, - systemId: exampleSystemFly, - world: worldPda, - entities: [ - { - entity: entity1Pda, - components: [{ componentId: exampleComponentPosition.programId }], - }, - ], - }); - let invalid = false; - try { - await provider.sendAndConfirm(applySystem.transaction); - } catch (error) { - expect(error.logs.join(" ")).to.contain("Error Code: SystemNotApproved"); - invalid = true; - } - expect(invalid).to.equal(true); - }); - - it("Check invalid component init without CPI", async () => { - let invalid = false; - try { - await exampleComponentPosition.methods - .initialize() - .accounts({ - payer: provider.wallet.publicKey, - data: componentPositionEntity5Pda, - entity: entity5Pda, - authority: provider.wallet.publicKey, - }) - .rpc(); - } catch (error) { - // console.log("error", error); - expect(error.message).to.contain("Error Code: InvalidCaller"); - invalid = true; - } - expect(invalid).to.equal(true); - }); - - it("Check invalid component update without CPI", async () => { - let invalid = false; - try { - await boltComponentProgram.methods - .update(Buffer.from("")) - .accounts({ - boltComponent: componentPositionEntity4Pda, - authority: provider.wallet.publicKey, - }) - .rpc(); - } catch (error) { - // console.log("error", error); - expect(error.message).to.contain( - "bolt_component. Error Code: AccountOwnedByWrongProgram", - ); - invalid = true; - } - expect(invalid).to.equal(true); - }); - - it("Check component delegation", async () => { - const delegateComponent = await DelegateComponent({ - payer: provider.wallet.publicKey, - entity: entity1Pda, - componentId: exampleComponentPosition.programId, - }); - - const txSign = await provider.sendAndConfirm( - delegateComponent.transaction, - [], - { skipPreflight: true, commitment: "confirmed" }, - ); - const acc = await provider.connection.getAccountInfo( - delegateComponent.componentPda, - ); - expect(acc?.owner.toBase58()).to.equal(DELEGATION_PROGRAM_ID.toBase58()); - }); -}); diff --git a/tests/bolt.low-level.api.ts b/tests/bolt.low-level.api.ts deleted file mode 100644 index 46033bdf..00000000 --- a/tests/bolt.low-level.api.ts +++ /dev/null @@ -1,988 +0,0 @@ -import { Keypair, type PublicKey } from "@solana/web3.js"; -import { type Position } from "../target/types/position"; -import { type Velocity } from "../target/types/velocity"; -import { type BoltComponent } from "../target/types/bolt_component"; -import { type SystemSimpleMovement } from "../target/types/system_simple_movement"; -import { type SystemFly } from "../target/types/system_fly"; -import { type SystemApplyVelocity } from "../target/types/system_apply_velocity"; -import { expect } from "chai"; -import BN from "bn.js"; -import { - DELEGATION_PROGRAM_ID, - DelegateComponent, - type Program, - anchor, - web3, - FindRegistryPda, - FindWorldPda, - FindEntityPda, - FindComponentPda, - SerializeArgs, - WORLD_PROGRAM_IDL as World, -} from "../clients/bolt-sdk"; - -enum Direction { - Left = "Left", - Right = "Right", - Up = "Up", - Down = "Down", -} - -function padCenter(value: string, width: number) { - const length = value.length; - if (width <= length) { - return value; - } - const padding = (width - length) / 2; - const align = width - padding; - return value.padStart(align, " ").padEnd(width, " "); -} - -function logPosition(title: string, { x, y, z }: { x: BN; y: BN; z: BN }) { - console.log(" +----------------------------------+"); - console.log(` | ${padCenter(title, 32)} |`); - console.log(" +-----------------+----------------+"); - console.log(` | X Position | ${String(x).padEnd(14, " ")} |`); - console.log(` | Y Position | ${String(y).padEnd(14, " ")} |`); - console.log(` | Z Position | ${String(z).padEnd(14, " ")} |`); - console.log(" +-----------------+----------------+"); -} - -function logVelocity( - title: string, - { x, y, z, lastApplied }: { x: BN; y: BN; z: BN; lastApplied: BN }, -) { - console.log(" +----------------------------------+"); - console.log(` | ${padCenter(title, 32)} |`); - console.log(" +-----------------+----------------+"); - console.log(` | X Velocity | ${String(x).padEnd(14, " ")} |`); - console.log(` | Y Velocity | ${String(y).padEnd(14, " ")} |`); - console.log(` | Z Velocity | ${String(z).padEnd(14, " ")} |`); - console.log(` | Last Applied | ${String(lastApplied).padEnd(14, " ")} |`); - console.log(" +-----------------+----------------+"); -} - -describe("bolt", () => { - const provider = anchor.AnchorProvider.env(); - anchor.setProvider(provider); - - const worldProgram = anchor.workspace.World as Program; - - const boltComponentProgram = anchor.workspace - .BoltComponent as Program; - - const exampleComponentPosition = anchor.workspace - .Position as Program; - const exampleComponentVelocity = anchor.workspace - .Velocity as Program; - - const exampleSystemSimpleMovement = ( - anchor.workspace.SystemSimpleMovement as Program - ).programId; - const exampleSystemFly = (anchor.workspace.SystemFly as Program) - .programId; - const exampleSystemApplyVelocity = ( - anchor.workspace.SystemApplyVelocity as Program - ).programId; - - let worldPda: PublicKey; - let worldId: BN; - - let entity1Pda: PublicKey; - let entity2Pda: PublicKey; - let entity4Pda: PublicKey; - let entity5Pda: PublicKey; - - let componentPositionEntity1Pda: PublicKey; - let componentVelocityEntity1Pda: PublicKey; - - let componentPositionEntity4Pda: PublicKey; - let componentPositionEntity5Pda: PublicKey; - - const secondAuthority = Keypair.generate().publicKey; - - it("InitializeRegistry", async () => { - const registryPda = FindRegistryPda({}); - const instruction = await worldProgram.methods - .initializeRegistry() - .accounts({ - registry: registryPda, - payer: provider.wallet.publicKey, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - await provider.sendAndConfirm(transaction); - }); - - it("InitializeNewWorld", async () => { - const registryPda = FindRegistryPda({}); - const registry = await worldProgram.account.registry.fetch(registryPda); - worldId = new BN(registry.worlds); - worldPda = FindWorldPda({ worldId }); - const instruction = await worldProgram.methods - .initializeNewWorld() - .accounts({ - payer: provider.wallet.publicKey, - world: worldPda, - registry: registryPda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log("InitializeNewWorld signature: ", signature); - }); - - it("Add authority", async () => { - const instruction = await worldProgram.methods - .addAuthority(worldId) - .accounts({ - authority: provider.wallet.publicKey, - newAuthority: provider.wallet.publicKey, - world: worldPda, - }) - .instruction(); - - const transaction = new anchor.web3.Transaction().add(instruction); - await provider.sendAndConfirm(transaction, [], { skipPreflight: true }); - const worldAccount = await worldProgram.account.world.fetch(worldPda); - expect( - worldAccount.authorities.some((auth) => - auth.equals(provider.wallet.publicKey), - ), - ); - }); - - it("Add a second authority", async () => { - const instruction = await worldProgram.methods - .addAuthority(worldId) - .accounts({ - authority: provider.wallet.publicKey, - newAuthority: secondAuthority, - world: worldPda, - }) - .instruction(); - - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log(`Add Authority signature: ${signature}`); - const worldAccount = await worldProgram.account.world.fetch(worldPda); - expect( - worldAccount.authorities.some((auth) => auth.equals(secondAuthority)), - ); - }); - - it("Remove an authority", async () => { - const instruction = await worldProgram.methods - .removeAuthority(worldId) - .accounts({ - authority: provider.wallet.publicKey, - authorityToDelete: secondAuthority, - world: worldPda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log(`Remove Authority signature: ${signature}`); - const worldAccount = await worldProgram.account.world.fetch(worldPda); - expect( - !worldAccount.authorities.some((auth) => auth.equals(secondAuthority)), - ); - }); - - it("InitializeNewWorld 2", async () => { - const registryPda = FindRegistryPda({}); - const registry = await worldProgram.account.registry.fetch(registryPda); - const worldId = new BN(registry.worlds); - const worldPda = FindWorldPda({ worldId }); - const instruction = await worldProgram.methods - .initializeNewWorld() - .accounts({ - payer: provider.wallet.publicKey, - world: worldPda, - registry: registryPda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log("InitializeNewWorld 2 signature: ", signature); - }); - - it("Add entity 1", async () => { - const world = await worldProgram.account.world.fetch(worldPda); - entity1Pda = FindEntityPda({ worldId: world.id, entityId: world.entities }); - const instruction = await worldProgram.methods - .addEntity(null) - .accounts({ - payer: provider.wallet.publicKey, - world: worldPda, - entity: entity1Pda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log("Add Entity 1 signature: ", signature); - }); - - it("Add entity 2", async () => { - const world = await worldProgram.account.world.fetch(worldPda); - entity2Pda = FindEntityPda({ worldId: world.id, entityId: world.entities }); - const instruction = await worldProgram.methods - .addEntity(null) - .accounts({ - payer: provider.wallet.publicKey, - world: worldPda, - entity: entity2Pda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log("Add Entity 2 signature: ", signature); - }); - - it("Add entity 3", async () => { - const world = await worldProgram.account.world.fetch(worldPda); - const entity3Pda = FindEntityPda({ - worldId: world.id, - entityId: world.entities, - }); - const instruction = await worldProgram.methods - .addEntity(null) - .accounts({ - payer: provider.wallet.publicKey, - world: worldPda, - entity: entity3Pda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log("Add Entity 3 signature: ", signature); - }); - - it("Add entity 4 (with seed)", async () => { - const world = await worldProgram.account.world.fetch(worldPda); - const seed = Buffer.from("custom-seed"); - entity4Pda = FindEntityPda({ worldId: world.id, seed }); - const instruction = await worldProgram.methods - .addEntity(seed) - .accounts({ - payer: provider.wallet.publicKey, - world: worldPda, - entity: entity4Pda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log("Add Entity 4 signature: ", signature); - }); - - it("Add entity 5", async () => { - const world = await worldProgram.account.world.fetch(worldPda); - entity5Pda = FindEntityPda({ worldId: world.id, entityId: world.entities }); - const instruction = await worldProgram.methods - .addEntity(null) - .accounts({ - payer: provider.wallet.publicKey, - world: worldPda, - entity: entity5Pda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log("Add Entity 5 signature: ", signature); - }); - - it("Initialize Original Component on Entity 1, through the world instance", async () => { - const componentId = boltComponentProgram.programId; - const componentPda = FindComponentPda({ - componentId, - entity: entity1Pda, - seed: "origin-component", - }); - const instruction = await worldProgram.methods - .initializeComponent() - .accounts({ - payer: provider.wallet.publicKey, - entity: entity1Pda, - data: componentPda, - componentProgram: componentId, - authority: provider.wallet.publicKey, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log( - "Initialize Original Component on Entity 1 signature: ", - signature, - ); - }); - - it("Initialize Original Component on Entity 2, trough the world instance", async () => { - const componentId = boltComponentProgram.programId; - const componentPda = FindComponentPda({ - componentId, - entity: entity2Pda, - seed: "origin-component", - }); - const instruction = await worldProgram.methods - .initializeComponent() - .accounts({ - payer: provider.wallet.publicKey, - entity: entity2Pda, - data: componentPda, - componentProgram: componentId, - authority: provider.wallet.publicKey, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log( - "Initialize Original Component on Entity 2 signature: ", - signature, - ); - }); - - it("Initialize Position Component on Entity 1", async () => { - const componentId = exampleComponentPosition.programId; - componentPositionEntity1Pda = FindComponentPda({ - componentId, - entity: entity1Pda, - }); - const instruction = await worldProgram.methods - .initializeComponent() - .accounts({ - payer: provider.wallet.publicKey, - entity: entity1Pda, - data: componentPositionEntity1Pda, - componentProgram: componentId, - authority: worldProgram.programId, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log( - "Initialize Position Component on Entity 1 signature: ", - signature, - ); - }); - - it("Initialize Velocity Component on Entity 1 (with seed)", async () => { - const componentId = exampleComponentVelocity.programId; - componentVelocityEntity1Pda = FindComponentPda({ - componentId, - entity: entity1Pda, - seed: "component-velocity", - }); - const instruction = await worldProgram.methods - .initializeComponent() - .accounts({ - payer: provider.wallet.publicKey, - entity: entity1Pda, - data: componentVelocityEntity1Pda, - componentProgram: componentId, - authority: worldProgram.programId, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log( - "Initialize Velocity Component on Entity 1 signature: ", - signature, - ); - }); - - it("Initialize Position Component on Entity 2", async () => { - const componentId = exampleComponentPosition.programId; - const componentPositionEntity2Pda = FindComponentPda({ - componentId, - entity: entity2Pda, - }); - const instruction = await worldProgram.methods - .initializeComponent() - .accounts({ - payer: provider.wallet.publicKey, - entity: entity2Pda, - data: componentPositionEntity2Pda, - componentProgram: componentId, - authority: worldProgram.programId, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log( - "Initialize Position Component on Entity 2 signature: ", - signature, - ); - }); - - it("Initialize Position Component on Entity 4", async () => { - const componentId = exampleComponentPosition.programId; - componentPositionEntity4Pda = FindComponentPda({ - componentId, - entity: entity4Pda, - }); - const instruction = await worldProgram.methods - .initializeComponent() - .accounts({ - payer: provider.wallet.publicKey, - entity: entity4Pda, - data: componentPositionEntity4Pda, - componentProgram: componentId, - authority: worldProgram.programId, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log( - "Initialize Position Component on Entity 4 signature: ", - signature, - ); - }); - - it("Initialize Position Component on Entity 5 (with authority)", async () => { - const componentId = exampleComponentPosition.programId; - componentPositionEntity5Pda = FindComponentPda({ - componentId, - entity: entity5Pda, - }); - const instruction = await worldProgram.methods - .initializeComponent() - .accounts({ - payer: provider.wallet.publicKey, - entity: entity5Pda, - data: componentPositionEntity5Pda, - componentProgram: componentId, - authority: provider.wallet.publicKey, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log( - "Initialize Position Component on Entity 5 signature: ", - signature, - ); - }); - - it("Check Position on Entity 1 is default", async () => { - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity1Pda, - ); - logPosition("Default State: Entity 1", position); - expect(position.x.toNumber()).to.equal(0); - expect(position.y.toNumber()).to.equal(0); - expect(position.z.toNumber()).to.equal(0); - }); - - it("Apply Simple Movement System (Up) on Entity 1 using Apply", async () => { - const instruction = await worldProgram.methods - .apply(SerializeArgs({ direction: Direction.Up })) - .accounts({ - authority: provider.wallet.publicKey, - boltSystem: exampleSystemSimpleMovement, - world: worldPda, - }) - .remainingAccounts([ - { - pubkey: exampleComponentPosition.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: componentPositionEntity1Pda, - isSigner: false, - isWritable: true, - }, - ]) - .instruction(); - - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log( - "Apply Simple Movement System (Up) on Entity 1 signature: ", - signature, - ); - - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity1Pda, - ); - logPosition("Movement System: Entity 1", position); - expect(position.x.toNumber()).to.equal(0); - expect(position.y.toNumber()).to.equal(1); - expect(position.z.toNumber()).to.equal(0); - }); - - it("Apply Simple Movement System (Up) on Entity 1", async () => { - const instruction = await worldProgram.methods - .apply(SerializeArgs({ direction: Direction.Up })) - .accounts({ - authority: provider.wallet.publicKey, - boltSystem: exampleSystemSimpleMovement, - world: worldPda, - }) - .remainingAccounts([ - { - pubkey: exampleComponentPosition.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: componentPositionEntity1Pda, - isSigner: false, - isWritable: true, - }, - ]) - .instruction(); - - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log( - "Apply Simple Movement System (Up) on Entity 1 signature: ", - signature, - ); - - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity1Pda, - ); - logPosition("Movement System: Entity 1", position); - expect(position.x.toNumber()).to.equal(0); - expect(position.y.toNumber()).to.equal(2); - expect(position.z.toNumber()).to.equal(0); - }); - - it("Apply Simple Movement System (Right) on Entity 1", async () => { - const instruction = await worldProgram.methods - .apply(SerializeArgs({ direction: Direction.Right })) - .accounts({ - authority: provider.wallet.publicKey, - boltSystem: exampleSystemSimpleMovement, - world: worldPda, - }) - .remainingAccounts([ - { - pubkey: exampleComponentPosition.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: componentPositionEntity1Pda, - isSigner: false, - isWritable: true, - }, - ]) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log( - "Apply Simple Movement System (Right) on Entity 1 signature: ", - signature, - ); - - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity1Pda, - ); - logPosition("Movement System: Entity 1", position); - expect(position.x.toNumber()).to.equal(1); - expect(position.y.toNumber()).to.equal(2); - expect(position.z.toNumber()).to.equal(0); - }); - - it("Apply Fly System on Entity 1", async () => { - const instruction = await worldProgram.methods - .apply(SerializeArgs()) - .accounts({ - authority: provider.wallet.publicKey, - boltSystem: exampleSystemFly, - world: worldPda, - }) - .remainingAccounts([ - { - pubkey: exampleComponentPosition.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: componentPositionEntity1Pda, - isSigner: false, - isWritable: true, - }, - ]) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log("Apply Fly System on Entity 1 signature: ", signature); - - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity1Pda, - ); - logPosition("Fly System: Entity 1", position); - expect(position.x.toNumber()).to.equal(1); - expect(position.y.toNumber()).to.equal(2); - expect(position.z.toNumber()).to.equal(1); - }); - - it("Apply System Velocity on Entity 1", async () => { - const instruction = await worldProgram.methods - .apply(SerializeArgs()) - .accounts({ - authority: provider.wallet.publicKey, - boltSystem: exampleSystemApplyVelocity, - world: worldPda, - }) - .remainingAccounts([ - { - pubkey: exampleComponentVelocity.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: componentVelocityEntity1Pda, - isSigner: false, - isWritable: true, - }, - { - pubkey: exampleComponentPosition.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: componentPositionEntity1Pda, - isSigner: false, - isWritable: true, - }, - ]) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction); - console.log("Apply System Velocity on Entity 1 signature: ", signature); - - const velocity = await exampleComponentVelocity.account.velocity.fetch( - componentVelocityEntity1Pda, - ); - logVelocity("Apply System Velocity: Entity 1", velocity); - expect(velocity.x.toNumber()).to.equal(10); - expect(velocity.y.toNumber()).to.equal(0); - expect(velocity.z.toNumber()).to.equal(0); - expect(velocity.lastApplied.toNumber()).to.not.equal(0); - - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity1Pda, - ); - logPosition("Apply System Velocity: Entity 1", position); - expect(position.x.toNumber()).to.greaterThan(1); - expect(position.y.toNumber()).to.equal(2); - expect(position.z.toNumber()).to.equal(1); - }); - - it("Apply System Velocity on Entity 1, with Clock external account", async () => { - const instruction = await worldProgram.methods - .apply(SerializeArgs()) - .accounts({ - authority: provider.wallet.publicKey, - boltSystem: exampleSystemApplyVelocity, - world: worldPda, - }) - .remainingAccounts([ - { - pubkey: exampleComponentVelocity.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: componentVelocityEntity1Pda, - isSigner: false, - isWritable: true, - }, - { - pubkey: exampleComponentPosition.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: componentPositionEntity1Pda, - isSigner: false, - isWritable: true, - }, - { - pubkey: worldProgram.programId, // world program ID is the end of components delimiter - isSigner: false, - isWritable: false, - }, - { - pubkey: new web3.PublicKey( - "SysvarC1ock11111111111111111111111111111111", - ), - isWritable: false, - isSigner: false, - }, - ]) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - await provider.sendAndConfirm(transaction); - - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity1Pda, - ); - logPosition("Apply System Velocity: Entity 1", position); - expect(position.x.toNumber()).to.greaterThan(1); - expect(position.y.toNumber()).to.equal(2); - expect(position.z.toNumber()).to.equal(300); - }); - - it("Apply Fly System on Entity 4", async () => { - const instruction = await worldProgram.methods - .apply(SerializeArgs()) - .accounts({ - authority: provider.wallet.publicKey, - boltSystem: exampleSystemFly, - world: worldPda, - }) - .remainingAccounts([ - { - pubkey: exampleComponentPosition.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: componentPositionEntity4Pda, - isSigner: false, - isWritable: true, - }, - ]) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - await provider.sendAndConfirm(transaction); - - const position = await exampleComponentPosition.account.position.fetch( - componentPositionEntity4Pda, - ); - logPosition("Fly System: Entity 4", position); - expect(position.x.toNumber()).to.equal(0); - expect(position.y.toNumber()).to.equal(0); - expect(position.z.toNumber()).to.equal(1); - }); - - it("Apply Fly System on Entity 5 (should fail with wrong authority)", async () => { - const positionBefore = - await exampleComponentPosition.account.position.fetch( - componentPositionEntity5Pda, - ); - - const instruction = await worldProgram.methods - .apply(SerializeArgs()) - .accounts({ - authority: provider.wallet.publicKey, - boltSystem: exampleSystemFly, - world: worldPda, - }) - .remainingAccounts([ - { - pubkey: exampleComponentPosition.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: componentPositionEntity5Pda, - isSigner: false, - isWritable: true, - }, - ]) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - - let failed = false; - try { - await provider.sendAndConfirm(transaction); - } catch (error) { - failed = true; - // console.log("error", error); - expect(error.logs.join("\n")).to.contain("Error Code: InvalidAuthority"); - } - expect(failed).to.equal(true); - - const positionAfter = await exampleComponentPosition.account.position.fetch( - componentPositionEntity5Pda, - ); - - expect(positionBefore.x.toNumber()).to.equal(positionAfter.x.toNumber()); - expect(positionBefore.y.toNumber()).to.equal(positionAfter.y.toNumber()); - expect(positionBefore.z.toNumber()).to.equal(positionAfter.z.toNumber()); - }); - - it("Whitelist System", async () => { - const instruction = await worldProgram.methods - .approveSystem() - .accounts({ - authority: provider.wallet.publicKey, - system: exampleSystemFly, - world: worldPda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction, [], { - skipPreflight: true, - }); - console.log(`Whitelist 2 system approval signature: ${signature}`); - - // Get World and check permissionless and systems - const worldAccount = await worldProgram.account.world.fetch(worldPda); - expect(worldAccount.permissionless).to.equal(false); - expect(worldAccount.systems.length).to.be.greaterThan(0); - }); - - it("Whitelist System 2", async () => { - const instruction = await worldProgram.methods - .approveSystem() - .accounts({ - authority: provider.wallet.publicKey, - system: exampleSystemApplyVelocity, - world: worldPda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction, [], { - skipPreflight: true, - }); - console.log(`Whitelist 2 system approval signature: ${signature}`); - - // Get World and check permissionless and systems - const worldAccount = await worldProgram.account.world.fetch(worldPda); - expect(worldAccount.permissionless).to.equal(false); - expect(worldAccount.systems.length).to.be.greaterThan(0); - }); - - it("Apply Fly System on Entity 1", async () => { - const instruction = await worldProgram.methods - .apply(SerializeArgs()) - .accounts({ - authority: provider.wallet.publicKey, - boltSystem: exampleSystemFly, - world: worldPda, - }) - .remainingAccounts([ - { - pubkey: exampleComponentPosition.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: componentPositionEntity1Pda, - isSigner: false, - isWritable: true, - }, - ]) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - await provider.sendAndConfirm(transaction); - }); - - it("Remove System 1", async () => { - const instruction = await worldProgram.methods - .removeSystem() - .accounts({ - authority: provider.wallet.publicKey, - system: exampleSystemFly, - world: worldPda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - const signature = await provider.sendAndConfirm(transaction, [], { - skipPreflight: true, - }); - console.log(`Remove System 1 signature: ${signature}`); - - // Get World and check permissionless and systems - const worldAccount = await worldProgram.account.world.fetch(worldPda); - expect(worldAccount.permissionless).to.equal(false); - expect(worldAccount.systems.length).to.be.greaterThan(0); - }); - - it("Apply Invalid Fly System on Entity 1", async () => { - const instruction = await worldProgram.methods - .apply(SerializeArgs()) - .accounts({ - authority: provider.wallet.publicKey, - boltSystem: exampleSystemFly, - world: worldPda, - }) - .remainingAccounts([ - { - pubkey: exampleComponentPosition.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: componentPositionEntity1Pda, - isSigner: false, - isWritable: true, - }, - ]) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - let invalid = false; - try { - await provider.sendAndConfirm(transaction); - } catch (error) { - expect(error.logs.join(" ")).to.contain("Error Code: SystemNotApproved"); - invalid = true; - } - expect(invalid).to.equal(true); - }); - - it("Check invalid component init without CPI", async () => { - let invalid = false; - try { - await exampleComponentPosition.methods - .initialize() - .accounts({ - payer: provider.wallet.publicKey, - data: componentPositionEntity5Pda, - entity: entity5Pda, - authority: provider.wallet.publicKey, - }) - .rpc(); - } catch (error) { - // console.log("error", error); - expect(error.message).to.contain("Error Code: InvalidCaller"); - invalid = true; - } - expect(invalid).to.equal(true); - }); - - it("Check invalid component update without CPI", async () => { - let invalid = false; - try { - await boltComponentProgram.methods - .update(Buffer.from("")) - .accounts({ - boltComponent: componentPositionEntity4Pda, - authority: provider.wallet.publicKey, - }) - .rpc(); - } catch (error) { - // console.log("error", error); - expect(error.message).to.contain( - "bolt_component. Error Code: AccountOwnedByWrongProgram", - ); - invalid = true; - } - expect(invalid).to.equal(true); - }); - - it("Check component delegation", async () => { - const delegateComponent = await DelegateComponent({ - payer: provider.wallet.publicKey, - entity: entity1Pda, - componentId: exampleComponentPosition.programId, - }); - const instruction = delegateComponent.transaction; - const transaction = new anchor.web3.Transaction().add(instruction); - const txSign = await provider.sendAndConfirm(transaction, [], { - skipPreflight: true, - commitment: "confirmed", - }); - console.log(`Delegation signature: ${txSign}`); - const acc = await provider.connection.getAccountInfo( - delegateComponent.componentPda, - ); - expect(acc?.owner.toBase58()).to.equal(DELEGATION_PROGRAM_ID.toBase58()); - }); -}); diff --git a/tests/fixtures/session_keys.so b/tests/fixtures/session_keys.so new file mode 100644 index 00000000..2388bdfb Binary files /dev/null and b/tests/fixtures/session_keys.so differ diff --git a/tests/framework.ts b/tests/framework.ts new file mode 100644 index 00000000..67213df7 --- /dev/null +++ b/tests/framework.ts @@ -0,0 +1,52 @@ +export enum Direction { + Left = "Left", + Right = "Right", + Up = "Up", + Down = "Down", +} + +import { anchor, BN } from "../clients/bolt-sdk/lib"; +import { type World } from "../target/types/world"; +import { type Position } from "../target/types/position"; +import { type Velocity } from "../target/types/velocity"; +import { type SystemSimpleMovement } from "../target/types/system_simple_movement"; +import { type SystemFly } from "../target/types/system_fly"; +import { type SystemApplyVelocity } from "../target/types/system_apply_velocity"; +import { Keypair, PublicKey } from "@solana/web3.js"; + +export class Framework { + provider: anchor.AnchorProvider; + worldProgram: anchor.Program; + exampleComponentPosition: anchor.Program; + exampleComponentVelocity: anchor.Program; + systemSimpleMovement: anchor.Program; + systemFly: anchor.Program; + systemApplyVelocity: anchor.Program; + + worldPda: PublicKey; + worldId: BN; + + secondAuthority: PublicKey; + + entity1Pda: PublicKey; + entity2Pda: PublicKey; + entity4Pda: PublicKey; + + componentPositionEntity1Pda: PublicKey; + componentVelocityEntity1Pda: PublicKey; + + componentPositionEntity4Pda: PublicKey; + + constructor() { + this.secondAuthority = Keypair.generate().publicKey; + this.worldProgram = anchor.workspace.World; + this.exampleComponentPosition = anchor.workspace.Position; + this.exampleComponentVelocity = anchor.workspace.Velocity; + this.systemSimpleMovement = anchor.workspace.SystemSimpleMovement; + this.systemFly = anchor.workspace.SystemFly; + this.systemApplyVelocity = anchor.workspace.SystemApplyVelocity; + + this.provider = anchor.AnchorProvider.env(); + anchor.setProvider(this.provider); + } +} diff --git a/tests/intermediate-level/acceleration.ts b/tests/intermediate-level/acceleration.ts new file mode 100644 index 00000000..0acd2bc5 --- /dev/null +++ b/tests/intermediate-level/acceleration.ts @@ -0,0 +1,30 @@ +import { + DelegateComponent, + DELEGATION_PROGRAM_ID, +} from "../../clients/bolt-sdk/lib"; +import { expect } from "chai"; + +export function acceleration(framework) { + describe("Acceleration", () => { + it("Check component delegation to accelerator", async () => { + const delegateComponent = await DelegateComponent({ + payer: framework.provider.wallet.publicKey, + entity: framework.entity1Pda, + componentId: framework.exampleComponentPosition.programId, + }); + + await framework.provider.sendAndConfirm( + delegateComponent.transaction, + [], + { + skipPreflight: true, + commitment: "confirmed", + }, + ); + const acc = await framework.provider.connection.getAccountInfo( + delegateComponent.componentPda, + ); + expect(acc?.owner.toBase58()).to.equal(DELEGATION_PROGRAM_ID.toBase58()); + }); + }); +} diff --git a/tests/intermediate-level/ecs.ts b/tests/intermediate-level/ecs.ts new file mode 100644 index 00000000..7db812e2 --- /dev/null +++ b/tests/intermediate-level/ecs.ts @@ -0,0 +1,286 @@ +import { + web3, + AddEntity, + ApplySystem, + InitializeComponent, +} from "../../clients/bolt-sdk/lib"; +import { Direction, Framework } from "../framework"; +import { expect } from "chai"; + +export function ecs(framework: Framework) { + describe("ECS", () => { + it("Add entity 1", async () => { + const addEntity = await AddEntity({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addEntity.transaction); + framework.entity1Pda = addEntity.entityPda; // Saved for later + }); + + it("Add entity 2", async () => { + const addEntity = await AddEntity({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addEntity.transaction); + framework.entity2Pda = addEntity.entityPda; // Saved for later + }); + + it("Add entity 3", async () => { + const addEntity = await AddEntity({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addEntity.transaction); + }); + + it("Add entity 4 (with seed)", async () => { + const addEntity = await AddEntity({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + seed: Buffer.from("custom-seed"), + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addEntity.transaction); + framework.entity4Pda = addEntity.entityPda; + }); + + it("Initialize Velocity Component on Entity 1 (with seed)", async () => { + const initializeComponent = await InitializeComponent({ + payer: framework.provider.wallet.publicKey, + entity: framework.entity1Pda, + componentId: framework.exampleComponentVelocity.programId, + seed: "component-velocity", + }); + await framework.provider.sendAndConfirm(initializeComponent.transaction); + framework.componentVelocityEntity1Pda = initializeComponent.componentPda; // Saved for later + }); + + it("Initialize Position Component on Entity 1", async () => { + const initializeComponent = await InitializeComponent({ + payer: framework.provider.wallet.publicKey, + entity: framework.entity1Pda, + componentId: framework.exampleComponentPosition.programId, + }); + await framework.provider.sendAndConfirm(initializeComponent.transaction); + framework.componentPositionEntity1Pda = initializeComponent.componentPda; // Saved for later + }); + + it("Initialize Position Component on Entity 2", async () => { + const initializeComponent = await InitializeComponent({ + payer: framework.provider.wallet.publicKey, + entity: framework.entity2Pda, + componentId: framework.exampleComponentPosition.programId, + }); + await framework.provider.sendAndConfirm(initializeComponent.transaction); + }); + + it("Initialize Position Component on Entity 4", async () => { + const initializeComponent = await InitializeComponent({ + payer: framework.provider.wallet.publicKey, + entity: framework.entity4Pda, + componentId: framework.exampleComponentPosition.programId, + }); + await framework.provider.sendAndConfirm(initializeComponent.transaction); + framework.componentPositionEntity4Pda = initializeComponent.componentPda; // Saved for later + }); + + it("Check Position on Entity 1 is default", async () => { + const position = + await framework.exampleComponentPosition.account.position.fetch( + framework.componentPositionEntity1Pda, + ); + expect(position.x.toNumber()).to.equal(0); + expect(position.y.toNumber()).to.equal(0); + expect(position.z.toNumber()).to.equal(0); + }); + + it("Apply Simple Movement System (Up) on Entity 1", async () => { + const applySystem = await ApplySystem({ + authority: framework.provider.wallet.publicKey, + systemId: framework.systemSimpleMovement.programId, + world: framework.worldPda, + entities: [ + { + entity: framework.entity1Pda, + components: [ + { componentId: framework.exampleComponentPosition.programId }, + ], + }, + ], + args: { + direction: Direction.Up, + }, + }); + await framework.provider.sendAndConfirm(applySystem.transaction, [], { + skipPreflight: true, + }); + + const position = + await framework.exampleComponentPosition.account.position.fetch( + framework.componentPositionEntity1Pda, + ); + expect(position.x.toNumber()).to.equal(0); + expect(position.y.toNumber()).to.equal(1); + expect(position.z.toNumber()).to.equal(0); + }); + + it("Apply Simple Movement System (Right) on Entity 1", async () => { + const applySystem = await ApplySystem({ + authority: framework.provider.wallet.publicKey, + systemId: framework.systemSimpleMovement.programId, + world: framework.worldPda, + entities: [ + { + entity: framework.entity1Pda, + components: [ + { componentId: framework.exampleComponentPosition.programId }, + ], + }, + ], + args: { + direction: Direction.Right, + }, + }); + await framework.provider.sendAndConfirm(applySystem.transaction); + + const position = + await framework.exampleComponentPosition.account.position.fetch( + framework.componentPositionEntity1Pda, + ); + expect(position.x.toNumber()).to.equal(1); + expect(position.y.toNumber()).to.equal(1); + expect(position.z.toNumber()).to.equal(0); + }); + + it("Apply Fly System on Entity 1", async () => { + const applySystem = await ApplySystem({ + authority: framework.provider.wallet.publicKey, + systemId: framework.systemFly.programId, + world: framework.worldPda, + entities: [ + { + entity: framework.entity1Pda, + components: [ + { componentId: framework.exampleComponentPosition.programId }, + ], + }, + ], + }); + await framework.provider.sendAndConfirm(applySystem.transaction); + + const position = + await framework.exampleComponentPosition.account.position.fetch( + framework.componentPositionEntity1Pda, + ); + expect(position.x.toNumber()).to.equal(1); + expect(position.y.toNumber()).to.equal(1); + expect(position.z.toNumber()).to.equal(1); + }); + + it("Apply System Velocity on Entity 1", async () => { + const applySystem = await ApplySystem({ + authority: framework.provider.wallet.publicKey, + systemId: framework.systemApplyVelocity.programId, + world: framework.worldPda, + entities: [ + { + entity: framework.entity1Pda, + components: [ + { + componentId: framework.exampleComponentVelocity.programId, + seed: "component-velocity", + }, + { componentId: framework.exampleComponentPosition.programId }, + ], + }, + ], + }); + await framework.provider.sendAndConfirm(applySystem.transaction); + + const velocity = + await framework.exampleComponentVelocity.account.velocity.fetch( + framework.componentVelocityEntity1Pda, + ); + expect(velocity.x.toNumber()).to.equal(10); + expect(velocity.y.toNumber()).to.equal(0); + expect(velocity.z.toNumber()).to.equal(0); + expect(velocity.lastApplied.toNumber()).to.not.equal(0); + + const position = + await framework.exampleComponentPosition.account.position.fetch( + framework.componentPositionEntity1Pda, + ); + expect(position.x.toNumber()).to.greaterThan(1); + expect(position.y.toNumber()).to.equal(1); + expect(position.z.toNumber()).to.equal(1); + }); + + it("Apply System Velocity on Entity 1, with Clock external account", async () => { + const applySystem = await ApplySystem({ + authority: framework.provider.wallet.publicKey, + systemId: framework.systemApplyVelocity.programId, + world: framework.worldPda, + entities: [ + { + entity: framework.entity1Pda, + components: [ + { + componentId: framework.exampleComponentVelocity.programId, + seed: "component-velocity", + }, + { componentId: framework.exampleComponentPosition.programId }, + ], + }, + ], + extraAccounts: [ + { + pubkey: new web3.PublicKey( + "SysvarC1ock11111111111111111111111111111111", + ), + isWritable: false, + isSigner: false, + }, + ], + }); + await framework.provider.sendAndConfirm(applySystem.transaction); + + const position = + await framework.exampleComponentPosition.account.position.fetch( + framework.componentPositionEntity1Pda, + ); + expect(position.x.toNumber()).to.greaterThan(1); + expect(position.y.toNumber()).to.equal(1); + expect(position.z.toNumber()).to.equal(300); + }); + + it("Apply Fly System on Entity 4", async () => { + const applySystem = await ApplySystem({ + authority: framework.provider.wallet.publicKey, + systemId: framework.systemFly.programId, + world: framework.worldPda, + entities: [ + { + entity: framework.entity4Pda, + components: [ + { componentId: framework.exampleComponentPosition.programId }, + ], + }, + ], + }); + await framework.provider.sendAndConfirm(applySystem.transaction); + + const position = + await framework.exampleComponentPosition.account.position.fetch( + framework.componentPositionEntity4Pda, + ); + expect(position.x.toNumber()).to.equal(0); + expect(position.y.toNumber()).to.equal(0); + expect(position.z.toNumber()).to.equal(1); + }); + }); +} diff --git a/tests/intermediate-level/index.ts b/tests/intermediate-level/index.ts new file mode 100644 index 00000000..240c0571 --- /dev/null +++ b/tests/intermediate-level/index.ts @@ -0,0 +1,15 @@ +import { Framework } from "../framework"; +import { world } from "./world"; +import { ecs } from "./ecs"; +import { session } from "./session"; +import { permissioning } from "./permissioning"; +import { acceleration } from "./acceleration"; + +describe("Intermediate level API", () => { + const framework: Framework = new Framework(); + world(framework); + ecs(framework); + session(framework); + permissioning(framework); + acceleration(framework); +}); diff --git a/tests/intermediate-level/permissioning/component.ts b/tests/intermediate-level/permissioning/component.ts new file mode 100644 index 00000000..d4d64284 --- /dev/null +++ b/tests/intermediate-level/permissioning/component.ts @@ -0,0 +1,115 @@ +import { assert, expect } from "chai"; +import { + anchor, + AddEntity, + ApplySystem, + InitializeComponent, +} from "../../../clients/bolt-sdk/lib"; +import { Keypair } from "@solana/web3.js"; + +export function component(framework) { + describe("Component authority", () => { + let entity: anchor.web3.PublicKey; + let component: anchor.web3.PublicKey; + + it("Add authority test entity", async () => { + const addEntity = await AddEntity({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addEntity.transaction); + entity = addEntity.entityPda; // Saved for later + }); + + it("Initialize position component with authority on authority test entity", async () => { + const initializeComponent = await InitializeComponent({ + payer: framework.provider.wallet.publicKey, + entity: entity, + componentId: framework.exampleComponentPosition.programId, + authority: framework.provider.wallet.publicKey, + }); + await framework.provider.sendAndConfirm(initializeComponent.transaction); + component = initializeComponent.componentPda; // Saved for later + }); + + it("Shouldn't apply fly system on authority test entity with wrong authority", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + const keypair = Keypair.generate(); + + const applySystem = await ApplySystem({ + authority: keypair.publicKey, + systemId: framework.systemFly.programId, + world: framework.worldPda, + entities: [ + { + entity: entity, + components: [ + { componentId: framework.exampleComponentPosition.programId }, + ], + }, + ], + }); + + try { + await framework.provider.sendAndConfirm(applySystem.transaction, [ + keypair, + ]); + assert.fail( + "Shouldn't apply fly system on authority test entity with wrong authority", + ); + } catch (error) { + expect(error.logs.join("\n")).to.contain( + "Error Code: InvalidAuthority", + ); + } + + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + expect(positionBefore.x.toNumber()).to.equal(positionAfter.x.toNumber()); + expect(positionBefore.y.toNumber()).to.equal(positionAfter.y.toNumber()); + expect(positionBefore.z.toNumber()).to.equal(positionAfter.z.toNumber()); + }); + + it("Apply Fly System on authority test entity should succeed with correct authority", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + const applySystem = await ApplySystem({ + authority: framework.provider.wallet.publicKey, + systemId: framework.systemFly.programId, + world: framework.worldPda, + entities: [ + { + entity, + components: [ + { componentId: framework.exampleComponentPosition.programId }, + ], + }, + ], + }); + + await framework.provider.sendAndConfirm(applySystem.transaction); + + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + expect(positionAfter.x.toNumber()).to.equal(positionBefore.x.toNumber()); + expect(positionAfter.y.toNumber()).to.equal(positionBefore.y.toNumber()); + expect(positionAfter.z.toNumber()).to.equal( + positionBefore.z.toNumber() + 1, + ); + }); + }); +} diff --git a/tests/intermediate-level/permissioning/index.ts b/tests/intermediate-level/permissioning/index.ts new file mode 100644 index 00000000..4658b113 --- /dev/null +++ b/tests/intermediate-level/permissioning/index.ts @@ -0,0 +1,9 @@ +import { component } from "./component"; +import { world } from "./world"; + +export function permissioning(framework) { + describe("Permissioning", () => { + component(framework); + world(framework); + }); +} diff --git a/tests/intermediate-level/permissioning/world.ts b/tests/intermediate-level/permissioning/world.ts new file mode 100644 index 00000000..73dfc5f7 --- /dev/null +++ b/tests/intermediate-level/permissioning/world.ts @@ -0,0 +1,168 @@ +import { expect } from "chai"; +import { + AddAuthority, + RemoveAuthority, + ApplySystem, + ApproveSystem, + RemoveSystem, +} from "../../../clients/bolt-sdk/lib"; + +export function world(framework) { + describe("World authority", () => { + it("Add authority", async () => { + const addAuthority = await AddAuthority({ + authority: framework.provider.wallet.publicKey, + newAuthority: framework.provider.wallet.publicKey, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addAuthority.transaction, [], { + skipPreflight: true, + }); + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect( + worldAccount.authorities.some((auth) => + auth.equals(framework.provider.wallet.publicKey), + ), + ); + }); + + it("Add a second authority", async () => { + const addAuthority = await AddAuthority({ + authority: framework.provider.wallet.publicKey, + newAuthority: framework.secondAuthority, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addAuthority.transaction); + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect( + worldAccount.authorities.some((auth) => + auth.equals(framework.secondAuthority), + ), + ); + }); + + it("Remove an authority", async () => { + const removeAuthority = await RemoveAuthority({ + authority: framework.provider.wallet.publicKey, + authorityToDelete: framework.secondAuthority, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(removeAuthority.transaction); + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect( + !worldAccount.authorities.some((auth) => + auth.equals(framework.secondAuthority), + ), + ); + }); + + it("Whitelist Fly System", async () => { + const approveSystem = await ApproveSystem({ + authority: framework.provider.wallet.publicKey, + systemToApprove: framework.systemFly.programId, + world: framework.worldPda, + }); + + await framework.provider.sendAndConfirm(approveSystem.transaction, [], { + skipPreflight: true, + }); + + // Get World and check permissionless and systems + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); + + it("Whitelist Apply Velocity System system", async () => { + const approveSystem = await ApproveSystem({ + authority: framework.provider.wallet.publicKey, + systemToApprove: framework.systemApplyVelocity.programId, + world: framework.worldPda, + }); + + await framework.provider.sendAndConfirm(approveSystem.transaction, [], { + skipPreflight: true, + }); + + // Get World and check permissionless and systems + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); + + it("Apply Fly System on Entity 1", async () => { + const applySystem = await ApplySystem({ + authority: framework.provider.wallet.publicKey, + systemId: framework.systemFly.programId, + world: framework.worldPda, + entities: [ + { + entity: framework.entity1Pda, + components: [ + { componentId: framework.exampleComponentPosition.programId }, + ], + }, + ], + }); + await framework.provider.sendAndConfirm(applySystem.transaction); + }); + + it("Remove Fly System", async () => { + const removeSystem = await RemoveSystem({ + authority: framework.provider.wallet.publicKey, + systemToRemove: framework.systemFly.programId, + world: framework.worldPda, + }); + + await framework.provider.sendAndConfirm(removeSystem.transaction, [], { + skipPreflight: true, + }); + + // Get World and check permissionless and systems + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); + + it("Apply unauthorized Fly System on Entity 1", async () => { + const applySystem = await ApplySystem({ + authority: framework.provider.wallet.publicKey, + systemId: framework.systemFly.programId, + world: framework.worldPda, + entities: [ + { + entity: framework.entity1Pda, + components: [ + { componentId: framework.exampleComponentPosition.programId }, + ], + }, + ], + }); + let invalid = false; + try { + await framework.provider.sendAndConfirm(applySystem.transaction); + } catch (error) { + expect(error.logs.join(" ")).to.contain( + "Error Code: SystemNotApproved", + ); + invalid = true; + } + expect(invalid).to.equal(true); + }); + }); +} diff --git a/tests/intermediate-level/session.ts b/tests/intermediate-level/session.ts new file mode 100644 index 00000000..efdd8bb2 --- /dev/null +++ b/tests/intermediate-level/session.ts @@ -0,0 +1,150 @@ +import { Keypair } from "@solana/web3.js"; +import { + AddEntity, + CreateSession, + InitializeComponent, + ApplySystem, + anchor, + BN, + Session, +} from "../../clients/bolt-sdk/lib"; +import { expect } from "chai"; + +// TODO: Create the API for it. +export function session(framework) { + describe("Session", () => { + let session: Session; + let entity: anchor.web3.PublicKey; + let component: anchor.web3.PublicKey; + let entityWithAuthority: anchor.web3.PublicKey; + let componentWithAuthority: anchor.web3.PublicKey; + + it("Create Session", async () => { + const createSession = await CreateSession({ + authority: framework.provider.wallet.publicKey, + topUp: new BN(1000000000), + }); + session = createSession.session; + await framework.provider.sendAndConfirm(createSession.transaction, [ + session.signer, + ]); + }); + + it("Add entity 1", async () => { + const addEntity = await AddEntity({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addEntity.transaction); + entity = addEntity.entityPda; + }); + + it("Initialize position component", async () => { + const initializeComponent = await InitializeComponent({ + payer: session.signer.publicKey, + entity: entity, + componentId: framework.exampleComponentPosition.programId, + }); + await framework.provider.sendAndConfirm(initializeComponent.transaction, [ + session.signer, + ]); + component = initializeComponent.componentPda; + }); + + it("Apply Fly System on component using session token", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + const applySystem = await ApplySystem({ + authority: session.signer.publicKey, + systemId: framework.systemFly.programId, + world: framework.worldPda, + session, + entities: [ + { + entity: entity, + components: [ + { componentId: framework.exampleComponentPosition.programId }, + ], + }, + ], + }); + await framework.provider.sendAndConfirm(applySystem.transaction, [ + session.signer, + ]); + + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + expect(positionAfter.x.toNumber()).to.equal(positionBefore.x.toNumber()); + expect(positionAfter.y.toNumber()).to.equal(positionBefore.y.toNumber()); + expect(positionAfter.z.toNumber()).to.equal( + positionBefore.z.toNumber() + 1, + ); + }); + + it("Add entity for authority test", async () => { + const addEntity = await AddEntity({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addEntity.transaction); + entityWithAuthority = addEntity.entityPda; + }); + + it("Initialize position component with authority", async () => { + const initializeComponent = await InitializeComponent({ + payer: session.signer.publicKey, + entity: entityWithAuthority, + componentId: framework.exampleComponentPosition.programId, + authority: framework.provider.wallet.publicKey, + }); + await framework.provider.sendAndConfirm(initializeComponent.transaction, [ + session.signer, + ]); + componentWithAuthority = initializeComponent.componentPda; + }); + + it("Apply Fly System on component with authority using session token", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + componentWithAuthority, + ); + + const applySystem = await ApplySystem({ + authority: session.signer.publicKey, + systemId: framework.systemFly.programId, + world: framework.worldPda, + session, + entities: [ + { + entity: entityWithAuthority, + components: [ + { componentId: framework.exampleComponentPosition.programId }, + ], + }, + ], + }); + await framework.provider.sendAndConfirm(applySystem.transaction, [ + session.signer, + ]); + + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + componentWithAuthority, + ); + + expect(positionAfter.x.toNumber()).to.equal(positionBefore.x.toNumber()); + expect(positionAfter.y.toNumber()).to.equal(positionBefore.y.toNumber()); + expect(positionAfter.z.toNumber()).to.equal( + positionBefore.z.toNumber() + 1, + ); + }); + }); +} diff --git a/tests/intermediate-level/world.ts b/tests/intermediate-level/world.ts new file mode 100644 index 00000000..f66f3b57 --- /dev/null +++ b/tests/intermediate-level/world.ts @@ -0,0 +1,37 @@ +import { + InitializeNewWorld, + InitializeRegistry, +} from "../../clients/bolt-sdk/lib"; + +export function world(framework) { + describe("World", () => { + it("Initialize registry", async () => { + const initializeRegistry = await InitializeRegistry({ + payer: framework.provider.wallet.publicKey, + connection: framework.provider.connection, + }); + try { + await framework.provider.sendAndConfirm(initializeRegistry.transaction); + } catch (error) { + // This is expected to fail because the registry already exists if another api level test ran before + } + }); + + it("Initialize world", async () => { + const initializeNewWorld = await InitializeNewWorld({ + payer: framework.provider.wallet.publicKey, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(initializeNewWorld.transaction); + framework.worldPda = initializeNewWorld.worldPda; // Saved for later + }); + + it("Initialize second world", async () => { + const initializeNewWorld = await InitializeNewWorld({ + payer: framework.provider.wallet.publicKey, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(initializeNewWorld.transaction); + }); + }); +} diff --git a/tests/low-level/acceleration.ts b/tests/low-level/acceleration.ts new file mode 100644 index 00000000..6f770688 --- /dev/null +++ b/tests/low-level/acceleration.ts @@ -0,0 +1,28 @@ +import { + anchor, + DelegateComponent, + DELEGATION_PROGRAM_ID, +} from "../../clients/bolt-sdk/lib"; +import { expect } from "chai"; + +export function acceleration(framework) { + describe("Acceleration", () => { + it("Check component delegation to accelerator", async () => { + const delegateComponent = await DelegateComponent({ + payer: framework.provider.wallet.publicKey, + entity: framework.entity1Pda, + componentId: framework.exampleComponentPosition.programId, + }); + const instruction = delegateComponent.transaction; + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [], { + skipPreflight: true, + commitment: "confirmed", + }); + const acc = await framework.provider.connection.getAccountInfo( + delegateComponent.componentPda, + ); + expect(acc?.owner.toBase58()).to.equal(DELEGATION_PROGRAM_ID.toBase58()); + }); + }); +} diff --git a/tests/low-level/ecs.ts b/tests/low-level/ecs.ts new file mode 100644 index 00000000..776ad996 --- /dev/null +++ b/tests/low-level/ecs.ts @@ -0,0 +1,428 @@ +import { expect } from "chai"; +import { + anchor, + web3, + FindComponentPda, + FindEntityPda, + SerializeArgs, +} from "../../clients/bolt-sdk/lib"; +import { Direction } from "../framework"; + +export function ecs(framework) { + describe("ECS", () => { + it("Add entity 1", async () => { + const world = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + framework.entity1Pda = FindEntityPda({ + worldId: world.id, + entityId: world.entities, + }); + const instruction = await framework.worldProgram.methods + .addEntity(null) + .accounts({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + entity: framework.entity1Pda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Add entity 2", async () => { + const world = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + framework.entity2Pda = FindEntityPda({ + worldId: world.id, + entityId: world.entities, + }); + const instruction = await framework.worldProgram.methods + .addEntity(null) + .accounts({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + entity: framework.entity2Pda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Add entity 3", async () => { + const world = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + const entity3Pda = FindEntityPda({ + worldId: world.id, + entityId: world.entities, + }); + const instruction = await framework.worldProgram.methods + .addEntity(null) + .accounts({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + entity: entity3Pda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Add entity 4 (with seed)", async () => { + const world = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + const seed = Buffer.from("custom-seed"); + framework.entity4Pda = FindEntityPda({ worldId: world.id, seed }); + const instruction = await framework.worldProgram.methods + .addEntity(seed) + .accounts({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + entity: framework.entity4Pda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Initialize Velocity Component on Entity 1 (with seed)", async () => { + const componentId = framework.exampleComponentVelocity.programId; + framework.componentVelocityEntity1Pda = FindComponentPda({ + componentId, + entity: framework.entity1Pda, + seed: "component-velocity", + }); + const instruction = await framework.worldProgram.methods + .initializeComponent() + .accounts({ + payer: framework.provider.wallet.publicKey, + entity: framework.entity1Pda, + data: framework.componentVelocityEntity1Pda, + componentProgram: componentId, + authority: framework.worldProgram.programId, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Initialize Position Component on Entity 1", async () => { + const componentId = framework.exampleComponentPosition.programId; + framework.componentPositionEntity1Pda = FindComponentPda({ + componentId, + entity: framework.entity1Pda, + }); + const instruction = await framework.worldProgram.methods + .initializeComponent() + .accounts({ + payer: framework.provider.wallet.publicKey, + entity: framework.entity1Pda, + data: framework.componentPositionEntity1Pda, + componentProgram: componentId, + authority: framework.worldProgram.programId, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Initialize Position Component on Entity 2", async () => { + const componentId = framework.exampleComponentPosition.programId; + const componentPda = FindComponentPda({ + componentId, + entity: framework.entity2Pda, + }); + const instruction = await framework.worldProgram.methods + .initializeComponent() + .accounts({ + payer: framework.provider.wallet.publicKey, + entity: framework.entity2Pda, + data: componentPda, + componentProgram: componentId, + authority: framework.worldProgram.programId, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Initialize Position Component on Entity 4", async () => { + const componentId = framework.exampleComponentPosition.programId; + framework.componentPositionEntity4Pda = FindComponentPda({ + componentId, + entity: framework.entity4Pda, + }); + const instruction = await framework.worldProgram.methods + .initializeComponent() + .accounts({ + payer: framework.provider.wallet.publicKey, + entity: framework.entity4Pda, + data: framework.componentPositionEntity4Pda, + componentProgram: componentId, + authority: framework.worldProgram.programId, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Check Position on Entity 1 is default", async () => { + const position = + await framework.exampleComponentPosition.account.position.fetch( + framework.componentPositionEntity1Pda, + ); + expect(position.x.toNumber()).to.equal(0); + expect(position.y.toNumber()).to.equal(0); + expect(position.z.toNumber()).to.equal(0); + }); + + it("Apply Simple Movement System (Up) on Entity 1", async () => { + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs({ direction: Direction.Up })) + .accounts({ + authority: framework.provider.wallet.publicKey, + boltSystem: framework.systemSimpleMovement.programId, + world: framework.worldPda, + sessionToken: null, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: framework.componentPositionEntity1Pda, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + + const position = + await framework.exampleComponentPosition.account.position.fetch( + framework.componentPositionEntity1Pda, + ); + expect(position.x.toNumber()).to.equal(0); + expect(position.y.toNumber()).to.equal(1); + expect(position.z.toNumber()).to.equal(0); + }); + + it("Apply Simple Movement System (Right) on Entity 1", async () => { + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs({ direction: Direction.Right })) + .accounts({ + authority: framework.provider.wallet.publicKey, + boltSystem: framework.systemSimpleMovement.programId, + world: framework.worldPda, + sessionToken: null, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: framework.componentPositionEntity1Pda, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + + const position = + await framework.exampleComponentPosition.account.position.fetch( + framework.componentPositionEntity1Pda, + ); + expect(position.x.toNumber()).to.equal(1); + expect(position.y.toNumber()).to.equal(1); + expect(position.z.toNumber()).to.equal(0); + }); + + it("Apply Fly System on Entity 1", async () => { + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: framework.provider.wallet.publicKey, + boltSystem: framework.systemFly.programId, + world: framework.worldPda, + sessionToken: null, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: framework.componentPositionEntity1Pda, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + + const position = + await framework.exampleComponentPosition.account.position.fetch( + framework.componentPositionEntity1Pda, + ); + expect(position.x.toNumber()).to.equal(1); + expect(position.y.toNumber()).to.equal(1); + expect(position.z.toNumber()).to.equal(1); + }); + + it("Apply System Velocity on Entity 1", async () => { + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: framework.provider.wallet.publicKey, + boltSystem: framework.systemApplyVelocity.programId, + world: framework.worldPda, + sessionToken: null, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentVelocity.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: framework.componentVelocityEntity1Pda, + isSigner: false, + isWritable: true, + }, + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: framework.componentPositionEntity1Pda, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + + const velocity = + await framework.exampleComponentVelocity.account.velocity.fetch( + framework.componentVelocityEntity1Pda, + ); + expect(velocity.x.toNumber()).to.equal(10); + expect(velocity.y.toNumber()).to.equal(0); + expect(velocity.z.toNumber()).to.equal(0); + expect(velocity.lastApplied.toNumber()).to.not.equal(0); + + const position = + await framework.exampleComponentPosition.account.position.fetch( + framework.componentPositionEntity1Pda, + ); + expect(position.x.toNumber()).to.greaterThan(1); + expect(position.y.toNumber()).to.equal(1); + expect(position.z.toNumber()).to.equal(1); + }); + + it("Apply System Velocity on Entity 1, with Clock external account", async () => { + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: framework.provider.wallet.publicKey, + boltSystem: framework.systemApplyVelocity.programId, + world: framework.worldPda, + sessionToken: null, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentVelocity.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: framework.componentVelocityEntity1Pda, + isSigner: false, + isWritable: true, + }, + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: framework.componentPositionEntity1Pda, + isSigner: false, + isWritable: true, + }, + { + pubkey: framework.worldProgram.programId, // world program ID is the end of components delimiter + isSigner: false, + isWritable: false, + }, + { + pubkey: new web3.PublicKey( + "SysvarC1ock11111111111111111111111111111111", + ), + isWritable: false, + isSigner: false, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + + const position = + await framework.exampleComponentPosition.account.position.fetch( + framework.componentPositionEntity1Pda, + ); + expect(position.x.toNumber()).to.greaterThan(1); + expect(position.y.toNumber()).to.equal(1); + expect(position.z.toNumber()).to.equal(300); + }); + + it("Apply Fly System on Entity 4", async () => { + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: framework.provider.wallet.publicKey, + boltSystem: framework.systemFly.programId, + world: framework.worldPda, + sessionToken: null, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: framework.componentPositionEntity4Pda, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + + const position = + await framework.exampleComponentPosition.account.position.fetch( + framework.componentPositionEntity4Pda, + ); + expect(position.x.toNumber()).to.equal(0); + expect(position.y.toNumber()).to.equal(0); + expect(position.z.toNumber()).to.equal(1); + }); + }); +} diff --git a/tests/low-level/index.ts b/tests/low-level/index.ts new file mode 100644 index 00000000..f2149e9f --- /dev/null +++ b/tests/low-level/index.ts @@ -0,0 +1,15 @@ +import { Framework } from "../framework"; +import { world } from "./world"; +import { ecs } from "./ecs"; +import { session } from "./session"; +import { permissioning } from "./permissioning"; +import { acceleration } from "./acceleration"; + +describe("Low level API", () => { + const framework: Framework = new Framework(); + world(framework); + ecs(framework); + session(framework); + permissioning(framework); + acceleration(framework); +}); diff --git a/tests/low-level/permissioning/component.ts b/tests/low-level/permissioning/component.ts new file mode 100644 index 00000000..dbed55c3 --- /dev/null +++ b/tests/low-level/permissioning/component.ts @@ -0,0 +1,149 @@ +import { Keypair } from "@solana/web3.js"; +import { + anchor, + FindEntityPda, + FindComponentPda, + SerializeArgs, +} from "../../../clients/bolt-sdk/lib"; +import { assert, expect } from "chai"; + +export function component(framework) { + let entity: anchor.web3.PublicKey; + let component: anchor.web3.PublicKey; + + describe("Component authority", () => { + it("Add entity 5", async () => { + const world = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + entity = FindEntityPda({ + worldId: world.id, + entityId: world.entities, + }); + const instruction = await framework.worldProgram.methods + .addEntity(null) + .accounts({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + entity: entity, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Initialize position component with authority on authority test entity", async () => { + const componentId = framework.exampleComponentPosition.programId; + component = FindComponentPda({ + componentId, + entity: entity, + }); + const instruction = await framework.worldProgram.methods + .initializeComponent() + .accounts({ + payer: framework.provider.wallet.publicKey, + entity: entity, + data: component, + componentProgram: componentId, + authority: framework.provider.wallet.publicKey, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Shouldn't apply fly system on authority test entity with wrong authority", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + const keypair = Keypair.generate(); + + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: keypair.publicKey, + boltSystem: framework.systemFly.programId, + world: framework.worldPda, + sessionToken: null, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: component, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + + try { + await framework.provider.sendAndConfirm(transaction, [keypair]); + assert.fail( + "Shouldn't apply fly system on authority test entity with wrong authority", + ); + } catch (error) { + expect(error.logs.join("\n")).to.contain( + "Error Code: InvalidAuthority", + ); + } + + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + expect(positionBefore.x.toNumber()).to.equal(positionAfter.x.toNumber()); + expect(positionBefore.y.toNumber()).to.equal(positionAfter.y.toNumber()); + expect(positionBefore.z.toNumber()).to.equal(positionAfter.z.toNumber()); + }); + + it("Apply Fly System on authority test entity should succeed with correct authority", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: framework.provider.wallet.publicKey, + boltSystem: framework.systemFly.programId, + world: framework.worldPda, + sessionToken: null, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: component, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + + await framework.provider.sendAndConfirm(transaction); + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + expect(positionAfter.x.toNumber()).to.equal(positionBefore.x.toNumber()); + expect(positionAfter.y.toNumber()).to.equal(positionBefore.y.toNumber()); + expect(positionAfter.z.toNumber()).to.equal( + positionBefore.z.toNumber() + 1, + ); + }); + }); +} diff --git a/tests/low-level/permissioning/index.ts b/tests/low-level/permissioning/index.ts new file mode 100644 index 00000000..4658b113 --- /dev/null +++ b/tests/low-level/permissioning/index.ts @@ -0,0 +1,9 @@ +import { component } from "./component"; +import { world } from "./world"; + +export function permissioning(framework) { + describe("Permissioning", () => { + component(framework); + world(framework); + }); +} diff --git a/tests/low-level/permissioning/world.ts b/tests/low-level/permissioning/world.ts new file mode 100644 index 00000000..c8984a3d --- /dev/null +++ b/tests/low-level/permissioning/world.ts @@ -0,0 +1,237 @@ +import { expect } from "chai"; +import { anchor, SerializeArgs } from "../../../clients/bolt-sdk/lib"; + +export function world(framework) { + describe("World authority", () => { + it("Add authority", async () => { + const instruction = await framework.worldProgram.methods + .addAuthority(framework.worldId) + .accounts({ + authority: framework.provider.wallet.publicKey, + newAuthority: framework.provider.wallet.publicKey, + world: framework.worldPda, + }) + .instruction(); + + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [], { + skipPreflight: true, + }); + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect( + worldAccount.authorities.some((auth) => + auth.equals(framework.provider.wallet.publicKey), + ), + ); + }); + + it("Add a second authority", async () => { + const instruction = await framework.worldProgram.methods + .addAuthority(framework.worldId) + .accounts({ + authority: framework.provider.wallet.publicKey, + newAuthority: framework.secondAuthority, + world: framework.worldPda, + }) + .instruction(); + + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect( + worldAccount.authorities.some((auth) => + auth.equals(framework.secondAuthority), + ), + ); + }); + + it("Remove an authority", async () => { + const instruction = await framework.worldProgram.methods + .removeAuthority(framework.worldId) + .accounts({ + authority: framework.provider.wallet.publicKey, + authorityToDelete: framework.secondAuthority, + world: framework.worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect( + !worldAccount.authorities.some((auth) => + auth.equals(framework.secondAuthority), + ), + ); + }); + + it("Whitelist Fly System", async () => { + const instruction = await framework.worldProgram.methods + .approveSystem() + .accounts({ + authority: framework.provider.wallet.publicKey, + system: framework.systemFly.programId, + world: framework.worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [], { + skipPreflight: true, + }); + + // Get World and check permissionless and systems + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); + + it("Whitelist Apply Velocity System system", async () => { + const instruction = await framework.worldProgram.methods + .approveSystem() + .accounts({ + authority: framework.provider.wallet.publicKey, + system: framework.systemApplyVelocity.programId, + world: framework.worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [], { + skipPreflight: true, + }); + + // Get World and check permissionless and systems + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); + + it("Apply Fly System on Entity 1", async () => { + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: framework.provider.wallet.publicKey, + boltSystem: framework.systemFly.programId, + world: framework.worldPda, + sessionToken: null, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: framework.componentPositionEntity1Pda, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Remove Fly System", async () => { + const instruction = await framework.worldProgram.methods + .removeSystem() + .accounts({ + authority: framework.provider.wallet.publicKey, + system: framework.systemFly.programId, + world: framework.worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [], { + skipPreflight: true, + }); + + // Get World and check permissionless and systems + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); + + it("Apply unauthorized Fly System on Entity 1", async () => { + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: framework.provider.wallet.publicKey, + boltSystem: framework.systemFly.programId, + world: framework.worldPda, + sessionToken: null, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: framework.componentPositionEntity1Pda, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + let invalid = false; + try { + await framework.provider.sendAndConfirm(transaction); + } catch (error) { + expect(error.logs.join(" ")).to.contain( + "Error Code: SystemNotApproved", + ); + invalid = true; + } + expect(invalid).to.equal(true); + }); + + it("Check invalid component init without CPI", async () => { + let invalid = false; + try { + await framework.exampleComponentPosition.methods + .initialize() + .accounts({ + payer: framework.provider.wallet.publicKey, + data: framework.componentPositionEntity1Pda, + entity: framework.entity1Pda, + authority: framework.provider.wallet.publicKey, + }) + .rpc(); + } catch (error) { + expect(error.message).to.contain("Error Code: InvalidCaller"); + invalid = true; + } + expect(invalid).to.equal(true); + }); + + it("Check invalid component update without CPI", async () => { + let invalid = false; + try { + await framework.exampleComponentPosition.methods + .update(Buffer.from("")) + .accounts({ + boltComponent: framework.componentPositionEntity4Pda, + authority: framework.provider.wallet.publicKey, + sessionToken: null, + }) + .rpc(); + } catch (error) { + expect(error.message).to.contain("Error Code: InvalidCaller"); + invalid = true; + } + expect(invalid).to.equal(true); + }); + }); +} diff --git a/tests/low-level/session.ts b/tests/low-level/session.ts new file mode 100644 index 00000000..1c078f39 --- /dev/null +++ b/tests/low-level/session.ts @@ -0,0 +1,212 @@ +import { expect } from "chai"; +import { + anchor, + FindComponentPda, + FindEntityPda, + SerializeArgs, + SessionProgram, + FindSessionTokenPda, + BN, +} from "../../clients/bolt-sdk/lib"; +import { Keypair } from "@solana/web3.js"; + +export function session(framework) { + describe("Session", () => { + const sessionSigner: Keypair = Keypair.generate(); + let sessionToken: anchor.web3.PublicKey; + let entity: anchor.web3.PublicKey; + let component: anchor.web3.PublicKey; + let entityWithAuthority: anchor.web3.PublicKey; + let componentWithAuthority: anchor.web3.PublicKey; + + it("Create Session", async () => { + sessionToken = FindSessionTokenPda({ + sessionSigner: sessionSigner.publicKey, + authority: framework.provider.wallet.publicKey, + }); + let instruction = await SessionProgram.methods + .createSession(true, null, new BN(1000000000)) + .accounts({ + sessionSigner: sessionSigner.publicKey, + authority: framework.provider.wallet.publicKey, + targetProgram: framework.worldProgram.programId, + sessionToken, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [sessionSigner]); + }); + + it("Add entity", async () => { + const world = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + entity = FindEntityPda({ + worldId: world.id, + entityId: world.entities, + }); + const instruction = await framework.worldProgram.methods + .addEntity(null) + .accounts({ + payer: sessionSigner.publicKey, + entity: entity, + world: framework.worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [sessionSigner]); + }); + + it("Initialize position component", async () => { + const componentId = framework.exampleComponentPosition.programId; + component = FindComponentPda({ + componentId, + entity, + }); + const instruction = await framework.worldProgram.methods + .initializeComponent() + .accounts({ + payer: sessionSigner.publicKey, + entity: entity, + data: component, + componentProgram: componentId, + authority: framework.worldProgram.programId, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [sessionSigner]); + }); + + it("Apply Fly System on component using session token", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: sessionSigner.publicKey, + boltSystem: framework.systemFly.programId, + world: framework.worldPda, + sessionToken, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: component, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + + let signature = await framework.provider.connection.sendTransaction( + transaction, + [sessionSigner], + ); + await framework.provider.connection.confirmTransaction(signature); + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + expect(positionAfter.x.toNumber()).to.equal(positionBefore.x.toNumber()); + expect(positionAfter.y.toNumber()).to.equal(positionBefore.y.toNumber()); + expect(positionAfter.z.toNumber()).to.equal( + positionBefore.z.toNumber() + 1, + ); + }); + + it("Add entity for authority test", async () => { + const world = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + entityWithAuthority = FindEntityPda({ + worldId: world.id, + entityId: world.entities, + }); + const instruction = await framework.worldProgram.methods + .addEntity(null) + .accounts({ + payer: sessionSigner.publicKey, + world: framework.worldPda, + entity: entityWithAuthority, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [sessionSigner]); + }); + + it("Initialize position component with authority", async () => { + const componentId = framework.exampleComponentPosition.programId; + componentWithAuthority = FindComponentPda({ + componentId, + entity: entityWithAuthority, + }); + const instruction = await framework.worldProgram.methods + .initializeComponent() + .accounts({ + payer: sessionSigner.publicKey, + entity: entityWithAuthority, + data: componentWithAuthority, + componentProgram: componentId, + authority: framework.provider.wallet.publicKey, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [sessionSigner]); + }); + + it("Apply Fly System on component with authority using session token", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + componentWithAuthority, + ); + + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: sessionSigner.publicKey, + boltSystem: framework.systemFly.programId, + world: framework.worldPda, + sessionToken, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: componentWithAuthority, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + + let signature = await framework.provider.connection.sendTransaction( + transaction, + [sessionSigner], + ); + await framework.provider.connection.confirmTransaction(signature); + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + componentWithAuthority, + ); + + expect(positionAfter.x.toNumber()).to.equal(positionBefore.x.toNumber()); + expect(positionAfter.y.toNumber()).to.equal(positionBefore.y.toNumber()); + expect(positionAfter.z.toNumber()).to.equal( + positionBefore.z.toNumber() + 1, + ); + }); + }); +} diff --git a/tests/low-level/world.ts b/tests/low-level/world.ts new file mode 100644 index 00000000..be36befc --- /dev/null +++ b/tests/low-level/world.ts @@ -0,0 +1,59 @@ +import { + anchor, + FindRegistryPda, + FindWorldPda, + BN, +} from "../../clients/bolt-sdk/lib"; + +export function world(framework) { + describe("World", () => { + it("Initialize registry", async () => { + const registryPda = FindRegistryPda({}); + const instruction = await framework.worldProgram.methods + .initializeRegistry() + .accounts({ + registry: registryPda, + payer: framework.provider.wallet.publicKey, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Initialize world", async () => { + const registryPda = FindRegistryPda({}); + const registry = + await framework.worldProgram.account.registry.fetch(registryPda); + framework.worldId = new BN(registry.worlds); + framework.worldPda = FindWorldPda({ worldId: framework.worldId }); + const instruction = await framework.worldProgram.methods + .initializeNewWorld() + .accounts({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + registry: registryPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Initialize second world", async () => { + const registryPda = FindRegistryPda({}); + const registry = + await framework.worldProgram.account.registry.fetch(registryPda); + const worldId = new BN(registry.worlds); + const worldPda = FindWorldPda({ worldId }); + const instruction = await framework.worldProgram.methods + .initializeNewWorld() + .accounts({ + payer: framework.provider.wallet.publicKey, + world: worldPda, + registry: registryPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + }); +} diff --git a/tests/main.ts b/tests/main.ts new file mode 100644 index 00000000..f119121e --- /dev/null +++ b/tests/main.ts @@ -0,0 +1,2 @@ +import "./low-level"; +import "./intermediate-level";