Signed-off-by: Sean Young <sean@mess.org>
@@ -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
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
+ 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
+ solana -ul airdrop -k id.json 10
+ anchor deploy
+ - 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
- - name: Set github env
- run: echo "RPC_URL=http://solana:8899/" >> $GITHUB_ENV
- name: Deploy and test contracts
run: npm run test
@@ -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
@@ -0,0 +1,8 @@
+target
+dist
+build
@@ -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"
+[workspace]
+members = [
+ "programs/*"
+]
+[profile.release]
+overflow-checks = true
+lto = "fat"
+codegen-units = 1
+[profile.release.build-override]
+opt-level = 3
+incremental = false
@@ -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]
@@ -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"
+ }
+}
@@ -0,0 +1,19 @@
+[package]
+name = "anchor"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2021"
+[lib]
+crate-type = ["cdylib", "lib"]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+[dependencies]
+anchor-lang = "0.25.0"
@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []
@@ -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(
+ 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
+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>,
+pub struct NoAccountsNeeded {}
+#[account]
+pub struct MyAccount {
+ pub data1: bool,
+ pub data2: i32,
+ pub data3: u64,
+ pub data4: Pubkey,
+pub struct State<'info> {
+ #[account()]
+[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]
@@ -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]]);
+ });
+});
@@ -0,0 +1,10 @@
+ "compilerOptions": {
+ "types": ["mocha", "chai"],
+ "typeRoots": ["./node_modules/@types"],
+ "lib": ["es2015"],
+ "module": "commonjs",
+ "target": "es6",
+ "esModuleInterop": true
@@ -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");
+ uint64 res3 = anchor.sum{accounts: am2}([1,3,5,7], 11);
+ require(res3 == 27, "sum fail");
+ print("sum done");
+ 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;
@@ -0,0 +1,23 @@
+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);