diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 92c727286..c99a0df19 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,6 +41,7 @@ jobs: working-directory: packages/mcp build: + name: build (${{ matrix.package }}, ${{ matrix.variant }}) timeout-minutes: 90 strategy: matrix: @@ -49,6 +50,10 @@ jobs: - cairo - stellar - stylus + # This variant config creates 2 branches of the matrix a default and a compile one to run the compile tests in their own job + variant: + - default + - compile runs-on: ubuntu-latest steps: @@ -63,45 +68,29 @@ jobs: uses: foundry-rs/foundry-toolchain@82dee4ba654bd2146511f85f0d013af94670c4de #v1.4.0 # ---------------------------- - # Stellar setup + # Stellar compile setup - - name: Free up disk space - if: matrix.package == 'stellar' - run: | - sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android - df -h - - # - name: Get scaffold-stellar latest commit SHA - # if: matrix.package == 'stellar' - # id: get-scaffold-sha - # run: | - # echo "sha=$(git ls-remote https://github.com/ahalabs/scaffold-stellar HEAD | cut -f1)" >> $GITHUB_OUTPUT - - # - name: Cache Scaffold CLIs - # if: matrix.package == 'stellar' - # uses: actions/cache@v4 - # with: - # path: | - # ~/.cargo/bin/ - # ~/.cargo/registry/index/ - # ~/.cargo/registry/cache/ - # ~/.cargo/git/db/ - # key: cargo-scaffold-${{ matrix.package }}-${{ steps.get-scaffold-sha.outputs.sha }} - # restore-keys: | - # cargo-scaffold-${{ matrix.package }}- - # cargo-scaffold- - # cargo-scaffold- + - name: Cache Rust dependencies + if: matrix.package == 'stellar' && matrix.variant == 'compile' + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3 + with: + path: | + ~/.cargo/bin/ + runner/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: cargo-${{ matrix.package }} - # - name: Set up rust toolchain - # uses: actions-rust-lang/setup-rust-toolchain@v1 - # with: - # toolchain: stable, nightly - # components: clippy, rustfmt, llvm-tools-preview - # target: wasm32v1-none - # - name: Set up Stellar CLI - # if: matrix.package == 'stellar' - # uses: stellar/stellar-cli@v22.8.2 + - name: Set up rust toolchain + if: matrix.package == 'stellar' && matrix.variant == 'compile' + uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 #v1.13.0 + with: + toolchain: stable, nightly + components: clippy, rustfmt, llvm-tools-preview + target: wasm32v1-none # ---------------------------- - name: Compile TypeScript @@ -111,5 +100,20 @@ jobs: run: yarn svelte-check working-directory: packages/ui - name: Run tests - run: yarn test + if: matrix.variant == 'default' + run: yarn test '**/*.test.ts' '!**/*.compile.test.ts' + working-directory: packages/core/${{matrix.package}} + + - name: Run tests + if: matrix.variant == 'compile' + env: + RUSTFLAGS: "" + run: | + FILES=$(find ./ -type f -name '*.compile.test.ts') + if [ -z "$FILES" ]; then + echo "No compile tests found. Skipping." + exit 0 + else + yarn test '**/*.compile.test.ts' + fi working-directory: packages/core/${{matrix.package}} diff --git a/packages/core/solidity/tsconfig.json b/packages/core/solidity/tsconfig.json index e51988c6e..68fb5fd5e 100644 --- a/packages/core/solidity/tsconfig.json +++ b/packages/core/solidity/tsconfig.json @@ -13,4 +13,4 @@ "include": [ "src/**/*" ] -} +} \ No newline at end of file diff --git a/packages/core/stellar/src/fungible.compile.test.ts b/packages/core/stellar/src/fungible.compile.test.ts new file mode 100644 index 000000000..c77bf5607 --- /dev/null +++ b/packages/core/stellar/src/fungible.compile.test.ts @@ -0,0 +1,292 @@ +import test from 'ava'; + +import { buildFungible } from './fungible'; +import { runRustCompilationTest } from './utils/compile-test'; + +test.serial( + 'compilation fungible simple', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: undefined, + burnable: false, + mintable: false, + pausable: false, + upgradeable: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible full', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: true, + mintable: true, + pausable: true, + upgradeable: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible burnable', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: true, + mintable: false, + pausable: false, + upgradeable: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible mintable', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: false, + mintable: true, + pausable: false, + upgradeable: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible pausable', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: false, + mintable: false, + pausable: true, + upgradeable: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible burnable mintable', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: true, + mintable: true, + pausable: false, + upgradeable: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible burnable pausable', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: true, + mintable: false, + pausable: true, + upgradeable: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible mintable pausable', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: false, + mintable: true, + pausable: true, + upgradeable: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible upgradable simple', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: undefined, + burnable: false, + mintable: false, + pausable: false, + upgradeable: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible upgradable full', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: true, + mintable: true, + pausable: true, + upgradeable: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible upgradable burnable', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: true, + mintable: false, + pausable: false, + upgradeable: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible upgradable mintable', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: false, + mintable: true, + pausable: false, + upgradeable: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible upgradable pausable', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: false, + mintable: false, + pausable: true, + upgradeable: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible upgradable burnable mintable', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: true, + mintable: true, + pausable: false, + upgradeable: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible upgradable burnable pausable', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: true, + mintable: false, + pausable: true, + upgradeable: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation fungible upgradable mintable pausable', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: false, + mintable: true, + pausable: true, + upgradeable: true, + }, + { snapshotResult: false }, + ), +); diff --git a/packages/core/stellar/src/non-fungible.compile.test.ts b/packages/core/stellar/src/non-fungible.compile.test.ts new file mode 100644 index 000000000..6f8952f9b --- /dev/null +++ b/packages/core/stellar/src/non-fungible.compile.test.ts @@ -0,0 +1,226 @@ +import test from 'ava'; + +import { buildNonFungible } from './non-fungible'; +import { runRustCompilationTest } from './utils/compile-test'; + +test('non- fungible compile placeholder', t => t.pass()); + +test.serial( + 'compilation nonfungible simple', + runRustCompilationTest( + buildNonFungible, + { + kind: 'NonFungible', + name: 'MyNFT', + symbol: 'MNFT', + burnable: false, + enumerable: false, + consecutive: false, + pausable: false, + upgradeable: false, + mintable: false, + sequential: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation nonfungible full except sequential mintable enumerable', + runRustCompilationTest( + buildNonFungible, + { + kind: 'NonFungible', + name: 'MyNFT', + symbol: 'MNFT', + burnable: true, + enumerable: false, + consecutive: true, + pausable: true, + upgradeable: true, + mintable: false, + sequential: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation nonfungible burnable', + runRustCompilationTest( + buildNonFungible, + { + kind: 'NonFungible', + name: 'MyNFT', + symbol: 'MNFT', + burnable: true, + enumerable: false, + consecutive: false, + pausable: false, + upgradeable: false, + mintable: false, + sequential: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation nonfungible consecutive', + runRustCompilationTest( + buildNonFungible, + { + kind: 'NonFungible', + name: 'MyNFT', + symbol: 'MNFT', + burnable: false, + enumerable: false, + consecutive: true, + pausable: false, + upgradeable: false, + mintable: false, + sequential: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation nonfungible pausable', + runRustCompilationTest( + buildNonFungible, + { + kind: 'NonFungible', + name: 'MyNFT', + symbol: 'MNFT', + burnable: false, + enumerable: false, + consecutive: false, + pausable: true, + upgradeable: false, + mintable: false, + sequential: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation nonfungible upgradeable', + runRustCompilationTest( + buildNonFungible, + { + kind: 'NonFungible', + name: 'MyNFT', + symbol: 'MNFT', + burnable: false, + enumerable: false, + consecutive: false, + pausable: false, + upgradeable: true, + mintable: false, + sequential: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation nonfungible sequential', + runRustCompilationTest( + buildNonFungible, + { + kind: 'NonFungible', + name: 'MyNFT', + symbol: 'MNFT', + burnable: false, + enumerable: false, + consecutive: false, + pausable: false, + upgradeable: false, + mintable: false, + sequential: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation nonfungible burnable pausable', + runRustCompilationTest( + buildNonFungible, + { + kind: 'NonFungible', + name: 'MyNFT', + symbol: 'MNFT', + burnable: true, + enumerable: false, + consecutive: false, + pausable: true, + upgradeable: false, + mintable: false, + sequential: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation nonfungible enumerable', + runRustCompilationTest( + buildNonFungible, + { + kind: 'NonFungible', + name: 'MyNFT', + symbol: 'MNFT', + burnable: false, + enumerable: true, + consecutive: false, + pausable: false, + upgradeable: false, + mintable: false, + sequential: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation nonfungible burnable enumerable', + runRustCompilationTest( + buildNonFungible, + { + kind: 'NonFungible', + name: 'MyNFT', + symbol: 'MNFT', + burnable: true, + enumerable: true, + consecutive: false, + pausable: false, + upgradeable: false, + mintable: false, + sequential: false, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation nonfungible full except consecutive', + runRustCompilationTest( + buildNonFungible, + { + kind: 'NonFungible', + name: 'MyNFT', + symbol: 'MNFT', + burnable: true, + enumerable: true, + consecutive: false, + pausable: true, + upgradeable: true, + mintable: true, + sequential: true, + }, + { snapshotResult: false }, + ), +); diff --git a/packages/core/stellar/src/stablecoin.compile.test.ts b/packages/core/stellar/src/stablecoin.compile.test.ts new file mode 100644 index 000000000..0cfddeb08 --- /dev/null +++ b/packages/core/stellar/src/stablecoin.compile.test.ts @@ -0,0 +1,235 @@ +import test from 'ava'; + +import { runRustCompilationTest } from './utils/compile-test'; + +import { buildStablecoin } from './stablecoin'; + +test.serial( + 'compilation basic stablecoin', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation stablecoin burnable', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + burnable: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation stablecoin pausable', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + pausable: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation stablecoin burnable pausable', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + burnable: true, + pausable: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation stablecoin preminted', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + premint: '1000', + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation stablecoin premint of 0', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + premint: '0', + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation stablecoin mintable', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + mintable: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation stablecoin ownable', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + access: 'ownable', + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation stablecoin roles', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + access: 'roles', + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation stablecoin allowlist', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + limitations: 'allowlist', + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation stablecoin blocklist', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + limitations: 'blocklist', + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation stablecoin full - ownable, allowlist', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + premint: '2000', + access: 'ownable', + limitations: 'allowlist', + burnable: true, + mintable: true, + pausable: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation stablecoin full - ownable, blocklist', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + premint: '2000', + access: 'ownable', + limitations: 'blocklist', + burnable: true, + mintable: true, + pausable: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation stablecoin full - roles, allowlist', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + premint: '2000', + access: 'roles', + limitations: 'allowlist', + burnable: true, + mintable: true, + pausable: true, + }, + { snapshotResult: false }, + ), +); + +test.serial( + 'compilation stablecoin full - roles, blocklist', + runRustCompilationTest( + buildStablecoin, + { + kind: 'Stablecoin', + name: 'MyStablecoin', + symbol: 'MST', + premint: '2000', + access: 'roles', + limitations: 'blocklist', + burnable: true, + mintable: true, + pausable: true, + }, + { snapshotResult: false }, + ), +); diff --git a/packages/core/stellar/src/utils/compile-test.ts b/packages/core/stellar/src/utils/compile-test.ts new file mode 100644 index 000000000..9d1781e87 --- /dev/null +++ b/packages/core/stellar/src/utils/compile-test.ts @@ -0,0 +1,69 @@ +import { tmpdir } from 'os'; +import type { ExecutionContext } from 'ava'; +import path from 'path'; +import { promisify } from 'util'; +import { exec } from 'child_process'; +import type { GenericOptions } from '../build-generic'; +import type { Contract } from '../contract'; +import { assertLayout, snapshotZipContents, expandPathsFromFilesPaths, extractPackage } from './zip-test'; +import { mkdtemp, rm } from 'fs/promises'; +import { contractOptionsToContractName } from '../zip-shared'; +import { zipRustProject } from '../zip-rust'; + +const asyncExec = promisify(exec); + +export const runCargoTest = async (t: ExecutionContext, temporaryFolder: string) => { + const result = await asyncExec(`cd "${temporaryFolder}" && cargo test`); + + t.regex(result.stdout, /0 failed/); +}; + +type WithTemporaryFolderTestFunction = ( + ...args: [...Args, ExecutionContext, string] +) => Promise | void; + +export type MakeContract = (opt: GenericOptions) => Contract; + +export const withTemporaryFolderDo = + (testFunction: WithTemporaryFolderTestFunction) => + (...testFunctionArguments: Args) => + async (test: ExecutionContext) => { + const temporaryFolder = await mkdtemp(path.join(tmpdir(), `compilation-test-${crypto.randomUUID()}`)); + + try { + await testFunction(...testFunctionArguments, test, temporaryFolder); + } finally { + await rm(temporaryFolder, { recursive: true, force: true }); + } + }; + +export const runRustCompilationTest = withTemporaryFolderDo( + async ( + makeContract: MakeContract, + opts: GenericOptions, + testOptions: { snapshotResult: boolean }, + test: ExecutionContext, + folderPath: string, + ) => { + test.timeout(3_000_000); + + const scaffoldContractName = contractOptionsToContractName(opts?.kind || 'contract'); + + const expectedZipFiles = [ + `contracts/${scaffoldContractName}/src/contract.rs`, + `contracts/${scaffoldContractName}/src/test.rs`, + `contracts/${scaffoldContractName}/src/lib.rs`, + `contracts/${scaffoldContractName}/Cargo.toml`, + 'Cargo.toml', + 'README.md', + ]; + + const zip = await zipRustProject(makeContract(opts), opts); + + assertLayout(test, zip, expandPathsFromFilesPaths(expectedZipFiles)); + await extractPackage(zip, folderPath); + await runCargoTest(test, folderPath); + + if (testOptions.snapshotResult) await snapshotZipContents(test, zip, expectedZipFiles); + }, +); diff --git a/packages/core/stellar/src/utils/zip-test.ts b/packages/core/stellar/src/utils/zip-test.ts new file mode 100644 index 000000000..78ddc32cd --- /dev/null +++ b/packages/core/stellar/src/utils/zip-test.ts @@ -0,0 +1,55 @@ +import type { JSZipObject } from 'jszip'; +import type JSZip from 'jszip'; +import type { ExecutionContext } from 'ava'; +import { mkdir, writeFile } from 'fs/promises'; +import { join } from 'path'; + +const getItemString = + ({ files }: JSZip) => + async (key: string) => { + const obj = files[key]; + if (obj === undefined) throw Error(`Item ${key} not found in zip`); + return await asString(obj); + }; + +export const asString = async (item: JSZipObject) => Buffer.from(await item.async('arraybuffer')).toString(); + +export const snapshotZipContents = async (test: ExecutionContext, zip: JSZip, contentsPathToAsserts: string[]) => { + const itemStrings = await Promise.all(contentsPathToAsserts.map(getItemString(zip))); + test.snapshot(itemStrings); +}; + +export const assertLayout = (test: ExecutionContext, zip: JSZip, expectedLayout: string[]) => + test.deepEqual( + Object.values(zip.files) + .map(f => f.name) + .sort(), + expectedLayout.sort(), + ); + +export const expandPathsFromFilesPaths = (filePaths: string[]): string[] => + Array.from( + new Set( + filePaths.flatMap((filePath: string) => { + const parts = filePath.split('/'); + + if (parts.length === 1) return [filePath]; + + const folders = parts.slice(0, -1).map((_, idx) => parts.slice(0, idx + 1).join('/') + '/'); + + return [...folders, filePath]; + }), + ), + ).sort(); + +export const extractPackage = async (zip: JSZip, folderPath: string) => { + const items = Object.values(Object.values(zip.files)); + + for (const item of items) { + if (item.dir) { + await mkdir(join(folderPath, item.name)); + } else { + await writeFile(join(folderPath, item.name), await asString(item)); + } + } +}; diff --git a/packages/core/stellar/src/zip-rust.compile.test.ts b/packages/core/stellar/src/zip-rust.compile.test.ts new file mode 100644 index 000000000..9110bb9e2 --- /dev/null +++ b/packages/core/stellar/src/zip-rust.compile.test.ts @@ -0,0 +1,21 @@ +import { buildFungible } from './fungible'; +import test from 'ava'; +import { runRustCompilationTest } from './utils/compile-test'; + +test.serial( + 'rust zip', + runRustCompilationTest( + buildFungible, + { + kind: 'Fungible', + name: 'MyToken', + symbol: 'MTK', + premint: '2000', + burnable: true, + mintable: false, + pausable: false, + upgradeable: false, + }, + { snapshotResult: true }, + ), +); diff --git a/packages/core/stellar/src/zip-rust.compile.test.ts.md b/packages/core/stellar/src/zip-rust.compile.test.ts.md new file mode 100644 index 000000000..fde516064 --- /dev/null +++ b/packages/core/stellar/src/zip-rust.compile.test.ts.md @@ -0,0 +1,127 @@ +# Snapshot report for `src/zip-rust.compile.test.ts` + +The actual snapshot is saved in `zip-rust.compile.test.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## rust zip + +> Snapshot 1 + + [ + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Stellar Soroban Contracts ^0.4.1␊ + ␊ + ␊ + use soroban_sdk::{Address, contract, contractimpl, Env, String};␊ + use stellar_macros::default_impl;␊ + use stellar_tokens::fungible::{Base, burnable::FungibleBurnable, FungibleToken};␊ + ␊ + #[contract]␊ + pub struct MyToken;␊ + ␊ + #[contractimpl]␊ + impl MyToken {␊ + pub fn __constructor(e: &Env, recipient: Address) {␊ + Base::set_metadata(e, 18, String::from_str(e, "MyToken"), String::from_str(e, "MTK"));␊ + Base::mint(e, &recipient, 2000000000000000000000);␊ + }␊ + }␊ + ␊ + #[default_impl]␊ + #[contractimpl]␊ + impl FungibleToken for MyToken {␊ + type ContractType = Base;␊ + ␊ + }␊ + ␊ + //␊ + // Extensions␊ + //␊ + ␊ + #[default_impl]␊ + #[contractimpl]␊ + impl FungibleBurnable for MyToken {}␊ + `, + `#![cfg(test)]␊ + ␊ + extern crate std;␊ + ␊ + use soroban_sdk::{ testutils::Address as _, Address, Env, String };␊ + ␊ + use crate::contract::{ MyToken, MyTokenClient };␊ + ␊ + #[test]␊ + fn initial_state() {␊ + let env = Env::default();␊ + ␊ + let contract_addr = env.register(MyToken, (Address::generate(&env),));␊ + let client = MyTokenClient::new(&env, &contract_addr);␊ + ␊ + assert_eq!(client.name(), String::from_str(&env, "MyToken"));␊ + }␊ + ␊ + // Add more tests bellow␊ + `, + `#![no_std]␊ + #![allow(dead_code)]␊ + ␊ + mod contract;␊ + mod test;␊ + `, + `[package]␊ + name = "fungible-contract"␊ + edition.workspace = true␊ + license.workspace = true␊ + publish = false␊ + version.workspace = true␊ + ␊ + [package.metadata.stellar]␊ + cargo_inherit = true␊ + ␊ + [lib]␊ + crate-type = ["cdylib"]␊ + doctest = false␊ + ␊ + [dependencies]␊ + stellar-tokens = { workspace = true }␊ + stellar-access = { workspace = true }␊ + stellar-contract-utils = { workspace = true }␊ + stellar-macros = { workspace = true }␊ + soroban-sdk = { workspace = true }␊ + ␊ + [dev-dependencies]␊ + soroban-sdk = { workspace = true, features = ["testutils"] }␊ + `, + `[workspace]␊ + resolver = "2"␊ + members = ["contracts/*"]␊ + ␊ + [workspace.package]␊ + authors = []␊ + edition = "2021"␊ + license = "Apache-2.0"␊ + version = "0.0.1"␊ + ␊ + [workspace.dependencies]␊ + soroban-sdk = "22.0.8"␊ + stellar-tokens = "=0.4.1"␊ + stellar-access = "=0.4.1"␊ + stellar-contract-utils = "=0.4.1"␊ + stellar-macros = "=0.4.1"␊ + ␊ + `, + `# Sample Rust Contract Environment␊ + ␊ + This project demonstrates a basic Rust contract environment use case. It comes with a contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/) and a test for that contract. Make sure you have the required dependencies and keep building!␊ + ␊ + ## Go further␊ + ␊ + Continue your development journey with [Stellar CLI](https://github.com/stellar/stellar-cli).␊ + ␊ + ## Installing dependencies␊ + ␊ + - See [Rust and Stellar installation guide](https://developers.stellar.org/docs/build/smart-contracts/getting-started/setup).␊ + - See [Git installation guide](https://github.com/git-guides/install-git).␊ + `, + ] diff --git a/packages/core/stellar/src/zip-rust.compile.test.ts.snap b/packages/core/stellar/src/zip-rust.compile.test.ts.snap new file mode 100644 index 000000000..06583623d Binary files /dev/null and b/packages/core/stellar/src/zip-rust.compile.test.ts.snap differ diff --git a/packages/core/stellar/src/zip-scaffold.compile.test.ts b/packages/core/stellar/src/zip-scaffold.compile.test.ts new file mode 100644 index 000000000..47764302c --- /dev/null +++ b/packages/core/stellar/src/zip-scaffold.compile.test.ts @@ -0,0 +1,77 @@ +import type { ExecutionContext } from 'ava'; + +import { zipScaffoldProject } from './zip-scaffold'; + +import util from 'util'; +import child from 'child_process'; +import type { GenericOptions } from './build-generic'; +import { contractOptionsToContractName } from './zip-shared'; +import { runCargoTest, withTemporaryFolderDo, type MakeContract } from './utils/compile-test'; +import { assertLayout, expandPathsFromFilesPaths, extractPackage, snapshotZipContents } from './utils/zip-test'; +import { buildFungible } from './fungible'; +import test from 'ava'; + +const asyncExec = util.promisify(child.exec); + +async function runProjectSetUp(t: ExecutionContext, folderPath: string) { + const result = await asyncExec(`cd "${folderPath}" && bash setup.sh`); + + t.regex(result.stdout, /Installation complete/); +} + +const runScaffoldCompilationTest = withTemporaryFolderDo( + async (makeContract: MakeContract, opts: GenericOptions, test: ExecutionContext, folderPath: string) => { + test.timeout(3_000_000); + + const scaffoldContractName = contractOptionsToContractName(opts?.kind || 'contract'); + + const expectedZipFiles = [ + 'Cargo.toml', + `contracts/${scaffoldContractName}/src/contract.rs`, + `contracts/${scaffoldContractName}/src/test.rs`, + `contracts/${scaffoldContractName}/src/lib.rs`, + `contracts/${scaffoldContractName}/Cargo.toml`, + 'setup.sh', + 'README-WIZARD.md', + ]; + + const zip = await zipScaffoldProject(makeContract(opts), opts); + + assertLayout(test, zip, expandPathsFromFilesPaths(expectedZipFiles)); + await extractPackage(zip, folderPath); + await runCargoTest(test, folderPath); + await runProjectSetUp(test, folderPath); + + await snapshotZipContents(test, zip, expectedZipFiles); + }, +); + +test('placeholder', t => t.assert(true)); + +// test.serial( +// 'zip scaffold fungible simple', +// runScaffoldCompilationTest(buildFungible, { +// kind: 'Fungible', +// name: 'MyToken', +// symbol: 'MTK', +// premint: undefined, +// burnable: false, +// mintable: false, +// pausable: false, +// upgradeable: false, +// }), +// ); + +// test.serial( +// 'zip scaffold fungible full', +// runScaffoldCompilationTest(buildFungible, { +// kind: 'Fungible', +// name: 'MyToken', +// symbol: 'MTK', +// premint: '2000', +// burnable: true, +// mintable: true, +// pausable: true, +// upgradeable: true, +// }), +// ); diff --git a/packages/core/stellar/src/zip-scaffold.test.ts b/packages/core/stellar/src/zip-scaffold.test.ts deleted file mode 100644 index 1f4ff3914..000000000 --- a/packages/core/stellar/src/zip-scaffold.test.ts +++ /dev/null @@ -1,593 +0,0 @@ -import type { TestFn, ExecutionContext } from 'ava'; -import _test from 'ava'; - -import { zipScaffoldProject } from './zip-scaffold'; - -import { buildFungible } from './fungible'; -import { buildNonFungible } from './non-fungible'; -import { promises as fs } from 'fs'; -import path from 'path'; -import os from 'os'; -import util from 'util'; -import child from 'child_process'; -import type { Contract } from './contract'; -import { rimraf } from 'rimraf'; -import type { JSZipObject } from 'jszip'; -import type JSZip from 'jszip'; -import type { GenericOptions } from './build-generic'; -import { contractOptionsToContractName } from './zip-shared'; -const asyncExec = util.promisify(child.exec); - -interface Context { - tempFolder: string; -} - -const test = _test as TestFn; - -function assertLayout(t: ExecutionContext, zip: JSZip, opts: GenericOptions) { - const sorted = Object.values(zip.files) - .map(f => f.name) - .sort(); - - const scaffoldContractName = contractOptionsToContractName(opts?.kind || 'contract'); - - t.deepEqual(sorted, [ - 'Cargo.toml', - 'README-WIZARD.md', - 'contracts/', - `contracts/${scaffoldContractName}/`, - `contracts/${scaffoldContractName}/Cargo.toml`, - `contracts/${scaffoldContractName}/src/`, - `contracts/${scaffoldContractName}/src/contract.rs`, - `contracts/${scaffoldContractName}/src/lib.rs`, - `contracts/${scaffoldContractName}/src/test.rs`, - 'setup.sh', - ]); -} - -async function extractPackage(t: ExecutionContext, zip: JSZip) { - const tempFolder = t.context.tempFolder; - - const items = Object.values(Object.values(zip.files)); - - for (const item of items) { - if (item.dir) { - await fs.mkdir(path.join(tempFolder, item.name)); - } else { - await fs.writeFile(path.join(tempFolder, item.name), await asString(item)); - } - } -} - -async function runProjectSetUp(t: ExecutionContext) { - const result = await asyncExec(`cd "${t.context.tempFolder}" && bash setup.sh`); - - console.log(result.stdout); - console.log(result.stderr); - - t.regex(result.stdout, /Installation complete/); -} - -async function runContractTest(t: ExecutionContext) { - const result = await asyncExec(`cd "${t.context.tempFolder}" && cargo test`); - - t.regex(result.stdout, /0 failed/); -} - -async function assertContents(t: ExecutionContext, zip: JSZip, opts: GenericOptions) { - const scaffoldContractName = contractOptionsToContractName(opts?.kind || 'contract'); - - const contentComparison = [ - await getItemString(zip, `contracts/${scaffoldContractName}/src/contract.rs`), - await getItemString(zip, `contracts/${scaffoldContractName}/src/test.rs`), - await getItemString(zip, `contracts/${scaffoldContractName}/src/lib.rs`), - await getItemString(zip, `contracts/${scaffoldContractName}/Cargo.toml`), - await getItemString(zip, 'setup.sh'), - await getItemString(zip, 'README-WIZARD.md'), - ]; - - t.snapshot(contentComparison); -} - -async function getItemString({ files }: JSZip, key: string) { - const obj = files[key]; - if (obj === undefined) throw Error(`Item ${key} not found in zip`); - return await asString(obj); -} - -async function asString(item: JSZipObject) { - return Buffer.from(await item.async('arraybuffer')).toString(); -} - -test.beforeEach(async t => { - t.context.tempFolder = `${await fs.mkdtemp(path.join(os.tmpdir(), `openzeppelin-wizard-scaffold`))}`; -}); - -test.afterEach.always(async t => { - await fs.rm(t.context.tempFolder, { recursive: true, force: true }); -}); - -async function runTest(t: ExecutionContext, c: Contract, opts: GenericOptions) { - t.timeout(3_000_000); - - const zip = await zipScaffoldProject(c, opts); - - assertLayout(t, zip, opts); - await extractPackage(t, zip); - await runProjectSetUp(t); - await runContractTest(t); - await assertContents(t, zip, opts); -} - -test('contractOptionsToContractName converts PascalCase to snake_case', t => { - t.is(contractOptionsToContractName('Fungible'), 'fungible'); - t.is(contractOptionsToContractName('NonFungible'), 'non_fungible'); - t.is(contractOptionsToContractName('Pausable'), 'pausable'); - t.is(contractOptionsToContractName('Upgradeable'), 'upgradeable'); - t.is(contractOptionsToContractName('MyCustomKind'), 'my_custom_kind'); -}); - -// test('fungible simple', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: undefined, -// burnable: false, -// mintable: false, -// pausable: false, -// upgradeable: false, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible full', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: '2000', -// burnable: true, -// mintable: true, -// pausable: true, -// upgradeable: false, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible burnable', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: '2000', -// burnable: true, -// mintable: false, -// pausable: false, -// upgradeable: false, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible mintable', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: '2000', -// burnable: false, -// mintable: true, -// pausable: false, -// upgradeable: false, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible pausable', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: '2000', -// burnable: false, -// mintable: false, -// pausable: true, -// upgradeable: false, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible burnable mintable', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: '2000', -// burnable: true, -// mintable: true, -// pausable: false, -// upgradeable: false, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible burnable pausable', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: '2000', -// burnable: true, -// mintable: false, -// pausable: true, -// upgradeable: false, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible mintable pausable', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: '2000', -// burnable: false, -// mintable: true, -// pausable: true, -// upgradeable: false, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible upgradable simple', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: undefined, -// burnable: false, -// mintable: false, -// pausable: false, -// upgradeable: true, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible upgradable full', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: '2000', -// burnable: true, -// mintable: true, -// pausable: true, -// upgradeable: true, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible upgradable burnable', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: '2000', -// burnable: true, -// mintable: false, -// pausable: false, -// upgradeable: true, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible upgradable mintable', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: '2000', -// burnable: false, -// mintable: true, -// pausable: false, -// upgradeable: true, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible upgradable pausable', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: '2000', -// burnable: false, -// mintable: false, -// pausable: true, -// upgradeable: true, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible upgradable burnable mintable', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: '2000', -// burnable: true, -// mintable: true, -// pausable: false, -// upgradeable: true, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible upgradable burnable pausable', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: '2000', -// burnable: true, -// mintable: false, -// pausable: true, -// upgradeable: true, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('fungible upgradable mintable pausable', async t => { -// const opts: GenericOptions = { -// kind: 'Fungible', -// name: 'MyToken', -// symbol: 'MTK', -// premint: '2000', -// burnable: false, -// mintable: true, -// pausable: true, -// upgradeable: true, -// }; -// const c = buildFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('nonfungible simple', async t => { -// const opts: GenericOptions = { -// kind: 'NonFungible', -// name: 'MyNFT', -// symbol: 'MNFT', -// burnable: false, -// enumerable: false, -// consecutive: false, -// pausable: false, -// upgradeable: false, -// mintable: false, -// sequential: false, -// }; -// const c = buildNonFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('nonfungible full except sequential mintable enumerable', async t => { -// const opts: GenericOptions = { -// kind: 'NonFungible', -// name: 'MyNFT', -// symbol: 'MNFT', -// burnable: true, -// enumerable: false, -// consecutive: true, -// pausable: true, -// upgradeable: true, -// mintable: false, -// sequential: false, -// }; -// const c = buildNonFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('nonfungible burnable', async t => { -// const opts: GenericOptions = { -// kind: 'NonFungible', -// name: 'MyNFT', -// symbol: 'MNFT', -// burnable: true, -// enumerable: false, -// consecutive: false, -// pausable: false, -// upgradeable: false, -// mintable: false, -// sequential: false, -// }; -// const c = buildNonFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('nonfungible consecutive', async t => { -// const opts: GenericOptions = { -// kind: 'NonFungible', -// name: 'MyNFT', -// symbol: 'MNFT', -// burnable: false, -// enumerable: false, -// consecutive: true, -// pausable: false, -// upgradeable: false, -// mintable: false, -// sequential: false, -// }; -// const c = buildNonFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('nonfungible pausable', async t => { -// const opts: GenericOptions = { -// kind: 'NonFungible', -// name: 'MyNFT', -// symbol: 'MNFT', -// burnable: false, -// enumerable: false, -// consecutive: false, -// pausable: true, -// upgradeable: false, -// mintable: false, -// sequential: false, -// }; -// const c = buildNonFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('nonfungible upgradeable', async t => { -// const opts: GenericOptions = { -// kind: 'NonFungible', -// name: 'MyNFT', -// symbol: 'MNFT', -// burnable: false, -// enumerable: false, -// consecutive: false, -// pausable: false, -// upgradeable: true, -// mintable: false, -// sequential: false, -// }; -// const c = buildNonFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('nonfungible sequential', async t => { -// const opts: GenericOptions = { -// kind: 'NonFungible', -// name: 'MyNFT', -// symbol: 'MNFT', -// burnable: false, -// enumerable: false, -// consecutive: false, -// pausable: false, -// upgradeable: false, -// mintable: false, -// sequential: true, -// }; -// const c = buildNonFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('nonfungible burnable pausable', async t => { -// const opts: GenericOptions = { -// kind: 'NonFungible', -// name: 'MyNFT', -// symbol: 'MNFT', -// burnable: true, -// enumerable: false, -// consecutive: false, -// pausable: true, -// upgradeable: false, -// mintable: false, -// sequential: false, -// }; -// const c = buildNonFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('nonfungible enumerable', async t => { -// const opts: GenericOptions = { -// kind: 'NonFungible', -// name: 'MyNFT', -// symbol: 'MNFT', -// burnable: false, -// enumerable: true, -// consecutive: false, -// pausable: false, -// upgradeable: false, -// mintable: false, -// sequential: false, -// }; -// const c = buildNonFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('nonfungible burnable enumerable', async t => { -// const opts: GenericOptions = { -// kind: 'NonFungible', -// name: 'MyNFT', -// symbol: 'MNFT', -// burnable: true, -// enumerable: true, -// consecutive: false, -// pausable: false, -// upgradeable: false, -// mintable: false, -// sequential: false, -// }; -// const c = buildNonFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('nonfungible full except consecutive', async t => { -// const opts: GenericOptions = { -// kind: 'NonFungible', -// name: 'MyNFT', -// symbol: 'MNFT', -// burnable: true, -// enumerable: true, -// consecutive: false, -// pausable: true, -// upgradeable: true, -// mintable: true, -// sequential: true, -// }; -// const c = buildNonFungible(opts); -// await runTest(t, c, opts); -// }); - -// -- TODO fix build -- - -// test('nonfungible mintable', async t => { -// const opts: GenericOptions = { -// kind: 'NonFungible', -// name: 'MyNFT', -// symbol: 'MNFT', -// burnable: false, -// enumerable: false, -// consecutive: false, -// pausable: false, -// upgradeable: false, -// mintable: true, -// sequential: false, -// }; -// const c = buildNonFungible(opts); -// await runTest(t, c, opts); -// }); - -// test('nonfungible mintable upgradeable', async t => { -// const opts: GenericOptions = { -// kind: 'NonFungible', -// name: 'MyNFT', -// symbol: 'MNFT', -// burnable: false, -// enumerable: false, -// consecutive: false, -// pausable: false, -// upgradeable: true, -// mintable: true, -// sequential: false, -// }; -// const c = buildNonFungible(opts); -// await runTest(t, c, opts); -// }); - -//--