Răsfoiți Sursa

Anchor test program

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 3 ani în urmă
părinte
comite
5333e5f40e

+ 24 - 12
.github/workflows/test.yml

@@ -220,35 +220,47 @@ jobs:
 
   solana:
     name: Solana Integration test
-    runs-on: ubuntu-20.04
-    container: ubuntu:20.04
+    runs-on: ubuntu-latest
+    container: ghcr.io/hyperledger/solang:ci
     needs: linux-x86-64
-    services:
-      solana:
-        image: solanalabs/solana:v1.9.15
-        ports:
-          - 8899
-          - 8900
     steps:
     - name: Checkout sources
       uses: actions/checkout@v2
-    - uses: actions/setup-node@v1
+    - uses: actions/setup-node@v3
       with:
         node-version: '14'
+    - name: Rust Stable
+      run: rustup default stable
+    - name: Setup yarn
+      run: npm install -g yarn
     - uses: actions/download-artifact@master
       with:
         name: solang-linux-x86-64
         path: bin
-    - run: |
+    - name: Solang Compiler
+      run: |
         chmod 755 ./bin/solang
         echo "$(pwd)/bin" >> $GITHUB_PATH
+    - name: Build Anchor test program
+      run: |
+        yarn install
+        anchor build
+        anchor test
+      working-directory: ./integration/anchor
+    - run: nohup solana-test-validator -q &
     - run: npm install
       working-directory: ./integration/solana
+    - name: Deploy Anchor program
+      run: |
+        solana -ul airdrop -k id.json 10
+        anchor deploy
+      working-directory: ./integration/anchor
+    - name: Create Solidity interface file for Anchor
+      run: solang idl ../anchor/target/idl/anchor.json
+      working-directory: ./integration/solana
     - name: Build Solang contracts
       run: npm run build
       working-directory: ./integration/solana
-    - name: Set github env
-      run: echo "RPC_URL=http://solana:8899/" >> $GITHUB_ENV
     - name: Deploy and test contracts
       run: npm run test
       working-directory: ./integration/solana

+ 13 - 0
integration/anchor/.gitignore

@@ -0,0 +1,13 @@
+
+.anchor
+.DS_Store
+target/bpfel-unknown-unknown
+target/debug
+target/idl
+target/release
+target/types
+target/deploy/*.so
+**/*.rs.bk
+node_modules
+test-ledger
+yarn.lock

+ 8 - 0
integration/anchor/.prettierignore

@@ -0,0 +1,8 @@
+
+.anchor
+.DS_Store
+target
+node_modules
+dist
+build
+test-ledger

+ 15 - 0
integration/anchor/Anchor.toml

@@ -0,0 +1,15 @@
+[features]
+seeds = false
+skip-lint = false
+[programs.localnet]
+anchor = "z7FbDfQDfucxJz5o8jrGLgvSbdoeSqX5VrxBb5TVjHq"
+
+[registry]
+url = "https://api.apr.dev"
+
+[provider]
+cluster = "localnet"
+wallet = "id.json"
+
+[scripts]
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

+ 13 - 0
integration/anchor/Cargo.toml

@@ -0,0 +1,13 @@
+[workspace]
+members = [
+    "programs/*"
+]
+
+[profile.release]
+overflow-checks = true
+lto = "fat"
+codegen-units = 1
+[profile.release.build-override]
+opt-level = 3
+incremental = false
+codegen-units = 1

+ 1 - 0
integration/anchor/id.json

@@ -0,0 +1 @@
+[95,177,155,238,169,94,167,151,190,138,244,231,178,206,127,162,222,2,218,144,48,157,140,53,3,245,68,234,0,52,206,111,247,41,228,226,6,2,72,238,153,164,129,142,85,222,98,186,109,176,77,230,168,43,9,188,234,101,196,25,239,70,0,118]

+ 20 - 0
integration/anchor/package.json

@@ -0,0 +1,20 @@
+{
+    "scripts": {
+        "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
+        "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
+    },
+    "dependencies": {
+        "@project-serum/anchor": "^0.25.0"
+    },
+    "devDependencies": {
+        "chai": "^4.3.4",
+        "mocha": "^9.0.3",
+        "ts-mocha": "^10.0.0",
+        "@types/bn.js": "^5.1.0",
+        "@types/chai": "^4.3.0",
+        "@types/mocha": "^9.0.0",
+        "typescript": "^4.3.5",
+        "prettier": "^2.6.2",
+        "expect": "^29.0.2"
+    }
+}

+ 19 - 0
integration/anchor/programs/anchor/Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "anchor"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "anchor"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = "0.25.0"

+ 2 - 0
integration/anchor/programs/anchor/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 155 - 0
integration/anchor/programs/anchor/src/lib.rs

@@ -0,0 +1,155 @@
+use anchor_lang::prelude::*;
+
+declare_id!("z7FbDfQDfucxJz5o8jrGLgvSbdoeSqX5VrxBb5TVjHq");
+
+/// Anchor example for testing with Solang.
+/// This doc comment exist for testing metadata doc comments
+#[program]
+pub mod anchor {
+    use super::*;
+
+    // a few primitives
+    pub fn initialize(
+        ctx: Context<Initialize>,
+        data1: bool,
+        data2: i32,
+        data3: u64,
+        data4: Pubkey,
+    ) -> Result<()> {
+        let my_accounts = &mut ctx.accounts.my_account;
+        my_accounts.data1 = data1;
+        my_accounts.data2 = data2;
+        my_accounts.data3 = data3;
+        my_accounts.data4 = data4;
+        Ok(())
+    }
+
+    /// string test
+    pub fn strings(_ctx: Context<NoAccountsNeeded>, input: String, data: u8) -> Result<String> {
+        Ok(format!("input:{} data:{}", input, data))
+    }
+
+    /// bytes test
+    pub fn bytes(_ctx: Context<NoAccountsNeeded>, input: Vec<u8>, data: u64) -> Result<Vec<u8>> {
+        let mut input = input;
+        input[data as usize] = !input[data as usize];
+        Ok(input)
+    }
+
+    /// Sum all fields of input and start
+    pub fn sum(_ctx: Context<NoAccountsNeeded>, input: Vec<u64>, start: u64) -> Result<u64> {
+        Ok(input.iter().fold(start, |acc, x| acc + x))
+    }
+
+    pub fn sector001(_ctx: Context<NoAccountsNeeded>) -> Result<Sector> {
+        Ok(Sector {
+            suns: 1,
+            mclass: vec![Planet::Earth],
+            calldata: *b"0123456789012",
+        })
+    }
+
+    pub fn has_planet(
+        _ctx: Context<NoAccountsNeeded>,
+        sector: Sector,
+        planet: Planet,
+    ) -> Result<bool> {
+        Ok(sector.mclass.contains(&planet))
+    }
+
+    pub fn states(ctx: Context<State>) -> Result<returns> {
+        let my_account = &ctx.accounts.my_account;
+
+        Ok(returns {
+            default: my_account.data1,
+            delete: my_account.data2,
+            fallback: my_account.data3,
+            assembly: my_account.data4,
+        })
+    }
+
+    pub fn multi_dimensional(
+        _ctx: Context<NoAccountsNeeded>,
+        arr: [[u16; 3]; 4],
+    ) -> Result<[[u16; 4]; 3]> {
+        let mut res = [[0u16; 4]; 3];
+
+        #[allow(clippy::needless_range_loop)]
+        for x in 0..4 {
+            for y in 0..3 {
+                res[y][x] = arr[x][y];
+            }
+        }
+
+        Ok(res)
+    }
+}
+
+/// Planets
+/// in our
+/// Solar System
+#[allow(non_camel_case_types)]
+#[derive(AnchorSerialize, AnchorDeserialize, PartialEq, Eq)]
+pub enum Planet {
+    Mercury,
+    Venus,
+    /// 3rd rock from the sun
+    Earth,
+    Mars,
+    Jupiter,
+    Saturn,
+    Uranus,
+    Neptune,
+    /// Solidity keyword!
+    anonymous,
+}
+
+/// Can a sector have multiple
+/// solar systems?
+#[derive(AnchorSerialize, AnchorDeserialize)]
+pub struct Sector {
+    /// A binary system can have multiple suns
+    suns: u64,
+    /// Which planets can support life?
+    mclass: Vec<Planet>,
+    /// Just
+    /// so
+    /// Random field with solidity keyword
+    calldata: [u8; 13],
+}
+
+/// Anchor requires that multiple return values must be put into a struct
+#[allow(non_camel_case_types)]
+#[derive(AnchorSerialize, AnchorDeserialize)]
+pub struct returns {
+    default: bool,
+    delete: i32,
+    fallback: u64,
+    assembly: Pubkey,
+}
+
+#[derive(Accounts)]
+pub struct Initialize<'info> {
+    #[account(init, payer = user, space = 8 + 64)]
+    pub my_account: Account<'info, MyAccount>,
+    #[account(mut)]
+    pub user: Signer<'info>,
+    pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+pub struct NoAccountsNeeded {}
+
+#[account]
+pub struct MyAccount {
+    pub data1: bool,
+    pub data2: i32,
+    pub data3: u64,
+    pub data4: Pubkey,
+}
+
+#[derive(Accounts)]
+pub struct State<'info> {
+    #[account()]
+    pub my_account: Account<'info, MyAccount>,
+}

+ 1 - 0
integration/anchor/target/deploy/anchor-keypair.json

@@ -0,0 +1 @@
+[35,63,41,124,25,172,141,233,108,165,165,90,72,55,247,5,164,1,93,106,53,65,40,180,39,11,136,207,119,11,42,11,14,161,44,189,165,144,141,253,72,225,0,54,150,104,202,189,112,204,57,95,200,217,59,208,125,72,84,44,177,130,208,184]

+ 68 - 0
integration/anchor/tests/anchor.ts

@@ -0,0 +1,68 @@
+import expect from 'expect';
+import * as anchor from "@project-serum/anchor";
+import { Program } from "@project-serum/anchor";
+import { Anchor } from "../target/types/anchor";
+
+describe("anchor", () => {
+  // Configure the client to use the local cluster.
+  const provider = anchor.AnchorProvider.env();
+
+  anchor.setProvider(provider);
+
+  const program = anchor.workspace.Anchor as Program<Anchor>;
+
+  it("test anchor program with anchor tests", async () => {
+    // The Account to create.
+    const myAccount = anchor.web3.Keypair.generate();
+
+    const program = anchor.workspace.Anchor;
+
+    const myPubkey = new anchor.web3.PublicKey("AddressLookupTab1e1111111111111111111111111");
+
+    const { SystemProgram } = anchor.web3;
+
+    // Add your test here.
+    const tx = await program.methods.initialize(true, -102, (new anchor.BN(0xdeadcafebeef)), myPubkey).accounts({
+      myAccount: myAccount.publicKey,
+      user: provider.wallet.publicKey,
+      systemProgram: SystemProgram.programId,
+    }).signers([myAccount]).rpc();
+
+    // string est
+    expect(await program.methods.strings("Hello, World!", 102).view()).toBe("input:Hello, World! data:102");
+
+    // sum test
+    const sumtest = await program.methods.sum([new anchor.BN(3), new anchor.BN(5), new anchor.BN(7)], new anchor.BN(1)).view();
+
+    expect(sumtest.toNumber()).toBe(1 + 3 + 5 + 7);
+
+    // sector001
+    let sector001 = await program.methods.sector001().view();
+    expect(sector001.suns.toNumber()).toBe(1);
+    expect(sector001.mclass.length).toBe(1);
+    expect(sector001.mclass[0]).toMatchObject({ "earth": {} });
+    expect(sector001.calldata.toString()).toEqual(Uint8Array.from([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50]).toString());
+
+    // has_planet
+    sector001.mclass.push({ "mars": {} });
+
+    const has_planet = await program.methods.hasPlanet(sector001, { "mars": {} }).view();
+
+    expect(has_planet).toBe(true)
+
+    // states
+    const states = await program.methods.states().accounts({
+      myAccount: myAccount.publicKey
+    }).view();
+
+    expect(states.default).toBe(true);
+    expect(states.delete).toBe(-102);
+    expect(states.fallback.toNumber()).toBe(0xdeadcafebeef);
+    expect(states.assembly.toString()).toBe('AddressLookupTab1e1111111111111111111111111');
+
+    // multidimensional
+    const arr = await program.methods.multiDimensional([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]).view();
+
+    expect(arr).toStrictEqual([[1, 4, 7, 10], [2, 5, 8, 11], [3, 6, 9, 12]]);
+  });
+});

+ 10 - 0
integration/anchor/tsconfig.json

@@ -0,0 +1,10 @@
+{
+  "compilerOptions": {
+    "types": ["mocha", "chai"],
+    "typeRoots": ["./node_modules/@types"],
+    "lib": ["es2015"],
+    "module": "commonjs",
+    "target": "es6",
+    "esModuleInterop": true
+  }
+}

+ 78 - 0
integration/solana/call_anchor.sol

@@ -0,0 +1,78 @@
+import 'solana';
+import 'anchor.sol';
+
+contract call_anchor {
+    address data;
+
+    constructor(address a) {
+        data = a;
+    }
+
+    function test(address payer) public returns (uint64) {
+        AccountMeta[3] am = [
+            AccountMeta({pubkey: data, is_writable: true, is_signer: true}),
+            AccountMeta({pubkey: payer, is_writable: true, is_signer: true}),
+            AccountMeta({pubkey: address"11111111111111111111111111111111", is_writable: false, is_signer: false})
+        ];
+
+        // init
+        anchor.initialize{accounts: am}(true, -102, 0xdeadcafebead, address"AddressLookupTab1e1111111111111111111111111");
+
+        print("initialize done");
+
+        AccountMeta[1] am2 = [
+            AccountMeta({pubkey: data, is_writable: false, is_signer: false})
+        ];
+
+        //string test
+        string res1 = anchor.strings{accounts: am2}("Hello, World!", 42);
+
+        require(res1 == "input:Hello, World! data:42", "strings fail");
+
+        print("string done");
+
+        // bytes test
+        bytes res2 = anchor._bytes{accounts: am2}(hex"0102030405", 2);
+
+        require(res2 == hex"0102fc0405", "bytes fail");
+
+        print("bytes done");
+
+        // sum test
+        uint64 res3 = anchor.sum{accounts: am2}([1,3,5,7], 11);
+
+        require(res3 == 27, "sum fail");
+
+        print("sum done");
+
+        // sector001
+        Sector res4 = anchor.sector001{accounts: am2}();
+
+        require(res4.suns == 1, "suns fail");
+        require(res4.mclass[0] == Planet.Earth, "mclass fail");
+
+        print("sector001 done");
+
+        bool res5 = anchor.hasPlanet{accounts: am2}(res4, Planet.Earth);
+        require(res5, "has_planet fail");
+
+        bool res6 = anchor.hasPlanet{accounts: am2}(res4, Planet.Venus);
+        require(!res6, "has_planet fail");
+
+        print("hasPlanet done");
+
+        _returns ret7 = anchor.states{accounts: am2}();
+
+        require(ret7._default == true, "field 1");
+        require(ret7._delete == -102, "field 2");
+        require(ret7._fallback == 0xdeadcafebead, "field 3");
+        require(ret7._assembly == address"AddressLookupTab1e1111111111111111111111111", "field 4");
+
+        uint16[4][3] ret8 = anchor.multiDimensional{accounts: am2}([[1000, 2000, 3000], [4000, 5000, 6000], [ 7000, 8000,9000],[10000, 11000,12000]]);
+        require(ret8[0][3] == 10000, "array 1");
+        require(ret8[1][2] == 8000, "array 2");
+        require(ret8[2][0] == 3000, "array 3");
+
+        return 11;
+    }
+}

+ 23 - 0
integration/solana/call_anchor.spec.ts

@@ -0,0 +1,23 @@
+import expect from 'expect';
+import { loadContract } from './setup';
+import { publicKeyToHex } from '@solana/solidity';
+import { SystemProgram, PublicKey, Keypair } from '@solana/web3.js';
+
+
+describe('Call Anchor program from Solidity via IDL', function () {
+    this.timeout(500000);
+
+    it('call_anchor', async function () {
+        // This program instantiates an anchor program, calls various functions on it and checks the return values
+
+        const data = Keypair.generate();
+
+        const programId = new PublicKey("z7FbDfQDfucxJz5o8jrGLgvSbdoeSqX5VrxBb5TVjHq");
+
+        let { contract, payer } = await loadContract('call_anchor', 'call_anchor.abi', [publicKeyToHex(data.publicKey)]);
+
+        let { result } = await contract.functions.test(publicKeyToHex(payer.publicKey), { accounts: [programId, SystemProgram.programId], signers: [data, payer] });
+
+        expect(result.toNumber()).toEqual(11);
+    });
+});