Add basic test layout.

This commit is contained in:
Jacques Wagener 2019-12-21 00:37:40 +02:00
parent 5de34f913c
commit 0a3990465c
No known key found for this signature in database
GPG Key ID: C294D1025DA0E923
8 changed files with 289 additions and 4 deletions

View File

@ -81,8 +81,16 @@ ee-examples:
$(WASM32_NIMC) --out:examples/ee/helloworld.wasm examples/ee/helloworld.nim
$(WASM32_NIMC) --out:examples/ee/block_echo.wasm examples/ee/block_echo.nim
.PHONY: test-ee
test-ee: ee-examples
cd tests/ee/; \
./test.sh
.PHONY: substrate-examples
substrate-examples:
$(WASM32_NIMC) --out:examples/substrate/hello_world.wasm examples/substrate/hello_world.nim
.PHONY: test-substrate
test-substrate: substrate-examples
cd tests/substrate; \
SUBSTRATE_PATH="${HOME}/.cargo/bin/substrate" ./test.sh

View File

@ -1,15 +1,21 @@
import utils
import macros
{.push cdecl, importc.}
proc ext_address*()
proc ext_block_number*()
proc ext_gas_left*()
proc ext_gas_price*()
proc ext_get_storage*(key_ptr: pointer): int32
proc ext_println*(str_ptr: pointer, str_len: int32)
proc ext_now*()
proc ext_println*(str_ptr: pointer, str_len: int32) # experimental; will be removed.
proc ext_random_seed*()
proc ext_scratch_read*(dest_ptr: pointer, offset: int32, len: int32)
proc ext_scratch_size*(): int32
proc ext_scratch_write(src_ptr: pointer, len: int32)
proc ext_scratch_write*(src_ptr: pointer, len: int32)
proc ext_set_rent_allowance*(value_ptr: pointer, value_len: int32)
proc ext_set_storage*(key_ptr: pointer, value_non_null: int32, value_ptr: int32, value_len: int32)
proc ext_value_transferred*()
{.pop.}

22
tests/substrate/test.sh Executable file
View File

@ -0,0 +1,22 @@
#!/bin/bash
if [ -z "$SUBSTRATE_PATH" ]; then
echo "Please specify the path to substrate in the SUBSTRATE_PATH environment variable"
exit 1
fi
if [ ! -f "$SUBSTRATE_PATH" ]; then
echo "$SUBSTRATE_PATH doesn't exist"
exit 2
fi
# Purge dev chain and then spin up the substrate node in background
$SUBSTRATE_PATH purge-chain --dev -y
$SUBSTRATE_PATH --dev &
SUBSTRATE_PID=$!
# # Execute tests
yarn && yarn test --verbose
# # Kill the spawned substrate node
kill -9 $SUBSTRATE_PID

View File

@ -0,0 +1,9 @@
import BN from "bn.js";
export const WSURL = "ws://127.0.0.1:9944";
export const DOT: BN = new BN("1000000000000000");
export const CREATION_FEE: BN = DOT.muln(200);
export const GAS_REQUIRED = 50000;
export const ALICE = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
export const BOB = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty";

View File

@ -0,0 +1,98 @@
// Adapted from https://github.com/paritytech/srml-contracts-waterfall/
import { ApiPromise, SubmittableResult, WsProvider } from "@polkadot/api";
import { Abi } from '@polkadot/api-contract';
import testKeyring from "@polkadot/keyring/testing";
import { u8aToHex } from "@polkadot/util";
import { randomAsU8a } from "@polkadot/util-crypto";
import { KeyringPair } from "@polkadot/keyring/types";
import { Option } from "@polkadot/types";
import { Address, ContractInfo, Hash } from "@polkadot/types/interfaces";
import { ALICE, CREATION_FEE, WSURL } from "./consts";
import {
callContract,
instantiate,
getContractStorage,
putCode
} from "./utils";
// This is a test account that is going to be created and funded each test.
const keyring = testKeyring({ type: "sr25519" });
const alicePair = keyring.getPair(ALICE);
let testAccount: KeyringPair;
let api: ApiPromise;
beforeAll((): void => {
jest.setTimeout(30000);
});
beforeEach(
async (done): Promise<() => void> => {
api = await ApiPromise.create({ provider: new WsProvider(WSURL) });
testAccount = keyring.addFromSeed(randomAsU8a(32));
return api.tx.balances
.transfer(testAccount.address, CREATION_FEE.muln(3))
.signAndSend(alicePair, (result: SubmittableResult): void => {
if (
result.status.isFinalized &&
result.findRecord("system", "ExtrinsicSuccess")
) {
console.log("New test account has been created.");
done();
}
});
}
);
describe("Nimplay Hello World", () => {
test("Raw Flipper contract", async (done): Promise<void> => {
// See https://github.com/paritytech/srml-contracts-waterfall/issues/6 for info about
// how to get the STORAGE_KEY of an instantiated contract
const STORAGE_KEY = (new Uint8Array(32)).fill(2);
// Deploy contract code on chain and retrieve the code hash
const codeHash = await putCode(
api,
testAccount,
"../../../examples/substrate/hello_world.wasm"
);
expect(codeHash).toBeDefined();
// Instantiate a new contract instance and retrieve the contracts address
// Call contract with Action: 0x00 = Action::Flip()
const address: Address = await instantiate(
api,
testAccount,
codeHash,
"0x00",
CREATION_FEE
);
expect(address).toBeDefined();
const initialValue: Uint8Array = await getContractStorage(
api,
address,
STORAGE_KEY
);
expect(initialValue).toBeDefined();
expect(initialValue.toString()).toEqual("0x00");
await callContract(api, testAccount, address, "0x00");
const newValue = await getContractStorage(api, address, STORAGE_KEY);
expect(newValue.toString()).toEqual("0x01");
await callContract(api, testAccount, address, "0x00");
const flipBack = await getContractStorage(api, address, STORAGE_KEY);
expect(flipBack.toString()).toEqual("0x00");
done();
});
});

View File

@ -0,0 +1,109 @@
import { ApiPromise, SubmittableResult } from "@polkadot/api";
import { KeyringPair } from "@polkadot/keyring/types";
import { Option, StorageData } from "@polkadot/types";
import { Address, ContractInfo, Hash } from "@polkadot/types/interfaces";
import BN from "bn.js";
import fs from "fs";
import path from "path";
const blake = require('blakejs')
import { GAS_REQUIRED } from "./consts";
export async function sendAndReturnFinalized(signer: KeyringPair, tx: any) {
return new Promise(function(resolve, reject) {
tx.signAndSend(signer, (result: SubmittableResult) => {
if (result.status.isFinalized) {
// Return result of the submittable extrinsic after the transfer is finalized
resolve(result as SubmittableResult);
}
if (
result.status.isDropped ||
result.status.isInvalid ||
result.status.isUsurped
) {
reject(result as SubmittableResult);
console.error("ERROR: Transaction could not be finalized.");
}
});
});
}
export async function putCode(
api: ApiPromise,
signer: KeyringPair,
fileName: string,
gasRequired: number = GAS_REQUIRED
): Promise<Hash> {
const wasmCode = fs
.readFileSync(path.join(__dirname, fileName))
.toString("hex");
const tx = api.tx.contracts.putCode(gasRequired, `0x${wasmCode}`);
const result: any = await sendAndReturnFinalized(signer, tx);
console.log('result', result)
const record = result.findRecord("contracts", "CodeStored");
if (!record) {
console.error("ERROR: No code stored after executing putCode()");
}
// Return code hash.
console.log(record);
return record.event.data[0];
}
export async function instantiate(
api: ApiPromise,
signer: KeyringPair,
codeHash: Hash,
inputData: any,
endowment: BN,
gasRequired: number = GAS_REQUIRED
): Promise<Address> {
const tx = api.tx.contracts.instantiate(
endowment,
gasRequired,
codeHash,
inputData
);
const result: any = await sendAndReturnFinalized(signer, tx);
const record = result.findRecord("contracts", "Instantiated");
if (!record) {
console.error("ERROR: No new instantiated contract");
}
// Return the address of instantiated contract.
return record.event.data[1];
}
export async function callContract(
api: ApiPromise,
signer: KeyringPair,
contractAddress: Address,
inputData: any,
gasRequired: number = GAS_REQUIRED,
endowment: number = 0
): Promise<void> {
const tx = api.tx.contracts.call(
contractAddress,
endowment,
gasRequired,
inputData
);
await sendAndReturnFinalized(signer, tx);
}
export async function getContractStorage(
api: ApiPromise,
contractAddress: Address,
storageKey: Uint8Array
): Promise<StorageData> {
const contractInfo = await api.query.contracts.contractInfoOf(
contractAddress
);
// Return the value of the contracts storage
const storageKeyBlake2b = blake.blake2bHex(storageKey, null, 32);
return await api.rpc.state.getChildStorage(
(contractInfo as Option<ContractInfo>).unwrap().asAlive.trieId,
'0x' + storageKeyBlake2b
);
}

View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"lib": ["dom", "es2018"],
"resolveJsonModule": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"allowJs": true,
"esModuleInterop": true
},
"exclude": [
"contracts/rust",
"lib",
"node_modules",
]
}

9
tests/substrate/utils.ts Normal file
View File

@ -0,0 +1,9 @@
import BN from "bn.js";
export const WSURL = "ws://127.0.0.1:9944";
export const DOT: BN = new BN("1000000000000000");
export const CREATION_FEE: BN = DOT.muln(200);
export const GAS_REQUIRED = 50000;
export const ALICE = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
export const BOB = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty";