Skip to content
This repository has been archived by the owner on Aug 24, 2023. It is now read-only.

Commit

Permalink
chore(ts-tests): add e2e contracts (#66)
Browse files Browse the repository at this point in the history
#### What this PR does / why we need it:

Develop and test on a simple smart contract functionality on
substrate-based blockchain.

#### Which issue(s) does this PR fixes?:
<!--
(Optional) Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

#### Additional comments?:

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
  • Loading branch information
canonbrother and dependabot[bot] authored Sep 12, 2022
1 parent f761111 commit 31bb3b8
Show file tree
Hide file tree
Showing 13 changed files with 3,476 additions and 5,012 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ target
netlify.toml
*.md
Dockerfile
ts-tests
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ build
node_modules
.contented
dist
.turbo
.turbo

### Hardhat
cache
artifacts
3 changes: 2 additions & 1 deletion ts-tests/.prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "none",
"singleQuote": true
"printWidth": 120
}
58 changes: 23 additions & 35 deletions ts-tests/__tests__/balances.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ethers, BigNumber } from 'ethers';
import { MetaDContainer } from '../containers';
import {
GENESIS_ACCOUNT,
Expand All @@ -9,6 +10,13 @@ import {
const container = new MetaDContainer();
const TEST_ACCOUNT = '0x1111111111111111111111111111111111111111';

const value = BigNumber.from(512); // 512, must be higher than ExistentialDeposit
const gasPrice = BigNumber.from(1_000_000_000); // 1000000000

// GENESIS_ACCOUNT_BALANCE - (21000 * gasPrice) - value;
const expectedGenesisBalance = BigNumber.from(GENESIS_ACCOUNT_BALANCE).sub(gasPrice.mul(21_000)).sub(value);
const expectedTestBalance = value.sub(EXISTENTIAL_DEPOSIT);

beforeAll(async () => {
await container.start();
});
Expand All @@ -18,45 +26,25 @@ afterAll(async () => {
});

it('should genesis balance setup correctly', async () => {
const bal = await container.web3.eth.getBalance(GENESIS_ACCOUNT);
expect(bal).toStrictEqual(GENESIS_ACCOUNT_BALANCE);
const bal = await container.ethers.getBalance(GENESIS_ACCOUNT);
expect(bal.toString()).toStrictEqual(GENESIS_ACCOUNT_BALANCE);
});

it('should transfer balance', async () => {
const value = '0x200'; // 512, must be higher than ExistentialDeposit
const gasPrice = '0x3B9ACA00'; // 1000000000
const tx = await container.web3.eth.accounts.signTransaction(
{
from: GENESIS_ACCOUNT,
to: TEST_ACCOUNT,
value: value,
gasPrice: gasPrice,
gas: '0x100000'
},
GENESIS_ACCOUNT_PRIVATE_KEY
);
await container.call('eth_sendRawTransaction', [tx?.rawTransaction]);

// GENESIS_ACCOUNT_BALANCE - (21000 * gasPrice) - value;
const expectedGenesisBalance = (
BigInt(GENESIS_ACCOUNT_BALANCE) -
BigInt(21000) * BigInt(gasPrice) -
BigInt(value)
).toString();
const expectedTestBalance = (Number(value) - EXISTENTIAL_DEPOSIT).toString();
expect(
await container.web3.eth.getBalance(GENESIS_ACCOUNT, 'pending')
).toStrictEqual(expectedGenesisBalance);
expect(
await container.web3.eth.getBalance(TEST_ACCOUNT, 'pending')
).toStrictEqual(expectedTestBalance);
const wallet = new ethers.Wallet(GENESIS_ACCOUNT_PRIVATE_KEY, container.ethers);

await wallet.sendTransaction({
to: TEST_ACCOUNT,
value: value,
gasPrice: gasPrice,
gasLimit: 0x100000
});

await container.generate();

expect(await container.web3.eth.getBalance(GENESIS_ACCOUNT)).toStrictEqual(
expectedGenesisBalance
);
expect(await container.web3.eth.getBalance(TEST_ACCOUNT)).toStrictEqual(
expectedTestBalance
);
const gBal = await container.ethers.getBalance(GENESIS_ACCOUNT);
expect(gBal).toStrictEqual(expectedGenesisBalance);

const tBal = await container.ethers.getBalance(TEST_ACCOUNT);
expect(tBal).toStrictEqual(expectedTestBalance);
});
33 changes: 12 additions & 21 deletions ts-tests/__tests__/blocks.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BigNumber } from 'ethers';
import { MetaDContainer } from '../containers';

const container = new MetaDContainer();
Expand All @@ -11,38 +12,28 @@ afterAll(async () => {
});

it('should generate', async () => {
const b0 = await container.web3.eth.getBlockNumber();
const b0 = await container.ethers.getBlockNumber();
expect(b0).toStrictEqual(0);

const block = await container.web3.eth.getBlock(0);
const block = await container.ethers.getBlock(0);
expect(block).toStrictEqual({
author: '0x0000000000000000000000000000000000000000',
baseFeePerGas: 1000000000,
difficulty: '0',
extraData: '0x',
gasLimit: 75000000,
gasUsed: 0,
hash: expect.any(String),
logsBloom:
'0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
miner: '0x0000000000000000000000000000000000000000',
nonce: '0x0000000000000000',
parentHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
number: 0,
parentHash:
'0x0000000000000000000000000000000000000000000000000000000000000000',
receiptsRoot: expect.any(String),
sha3Uncles: expect.any(String),
size: 505,
stateRoot: expect.any(String),
timestamp: 0,
totalDifficulty: '0',
nonce: '0x0000000000000000',
difficulty: 0,
gasLimit: BigNumber.from(75_000_000),
gasUsed: BigNumber.from(0),
extraData: '0x',
transactions: [],
transactionsRoot: expect.any(String),
uncles: []
baseFeePerGas: BigNumber.from(1_000_000_000),
_difficulty: BigNumber.from(0)
});

await container.generate();

const b1 = await container.web3.eth.getBlockNumber();
const b1 = await container.ethers.getBlockNumber();
expect(b1).toStrictEqual(1);
});
62 changes: 62 additions & 0 deletions ts-tests/__tests__/contract.tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { MetaDContainer } from '../containers';
import { GENESIS_ACCOUNT, GENESIS_ACCOUNT_PRIVATE_KEY, CONTRACT_ADDRESS } from '../utils/constant';
import Test from '../artifacts/contracts/Test.sol/Test.json';
import { ethers } from 'ethers';

const container = new MetaDContainer();

beforeAll(async () => {
await container.start();
});

afterAll(async () => {
await container.stop();
});

it('should create and call contract', async () => {
// create contract
const wallet = new ethers.Wallet(GENESIS_ACCOUNT_PRIVATE_KEY, container.ethers);

const factory = new ethers.ContractFactory(Test.abi, Test.bytecode, wallet);

const contract = await factory.deploy();
expect(contract.address).toStrictEqual(CONTRACT_ADDRESS);

await container.generate();

// call contract
expect(await contract.name()).toStrictEqual('Meta');
expect(await contract.owner()).toStrictEqual(GENESIS_ACCOUNT);

// test environmental
const currentBlock = (await contract.getCurrentBlock()).toNumber();
expect(currentBlock).toStrictEqual(1);

const blockHash = await contract.getBlockHash(currentBlock);
expect(blockHash).toStrictEqual(expect.any(String));

const gasLimit = (await contract.getGasLimit()).toNumber();
expect(gasLimit).toStrictEqual(75000000);

// test functional
const mul = (await contract.mul(3, 7)).toNumber();
expect(mul).toStrictEqual(21);

const c0 = (await contract.getCount()).toNumber();
expect(c0).toStrictEqual(0);

await contract.incr();
await container.generate();

const c1 = (await contract.getCount()).toNumber();
expect(c1).toStrictEqual(1);

await contract.setCount(25);
await container.generate();

const c25 = (await contract.getCount()).toNumber();
expect(c25).toStrictEqual(25);

const promise = contract.max10(11);
await expect(promise).rejects.toThrow('Value must not be greater than 10');
});
12 changes: 4 additions & 8 deletions ts-tests/containers/MetaDContainer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,17 @@ afterAll(async () => {
});

it('should have 0 hashrate', async function () {
expect(await container.web3.eth.getHashrate()).toStrictEqual(0);
expect(await container.call('eth_hashrate', [])).toStrictEqual('0x0');
});

it('should have chainId', async function () {
expect(await container.web3.eth.getChainId()).toStrictEqual(CHAIN_ID);
expect(Number(await container.call('net_version', []))).toStrictEqual(CHAIN_ID);
});

it('should have no account', async function () {
expect(await container.web3.eth.getAccounts()).toStrictEqual([]);
expect(await container.call('eth_accounts', [])).toStrictEqual([]);
});

it('block author should be 0x0000000000000000000000000000000000000000', async function () {
// This address `0x1234567890` is hardcoded into the runtime find_author
// as we are running manual sealing consensus.
expect(await container.web3.eth.getCoinbase()).toStrictEqual(
'0x0000000000000000000000000000000000000000'
);
expect(await container.call('eth_coinbase', [])).toStrictEqual('0x0000000000000000000000000000000000000000');
});
90 changes: 34 additions & 56 deletions ts-tests/containers/MetaDContainer.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import { ethers } from 'ethers';
import Web3 from 'web3';
import { JsonRpcResponse } from 'web3-core-helpers';
import {
GenericContainer,
StartedTestContainer,
Network,
StartedNetwork
} from 'testcontainers';
import { GenericContainer, StartedTestContainer, Network, StartedNetwork } from 'testcontainers';
import { CHAIN_ID } from '../utils/constant';
import { HttpProvider, WebsocketProvider } from 'web3-core';
import { META_LOG } from '../utils/constant';

type MetaDNetwork = 'mainnet' | 'testnet';

Expand Down Expand Up @@ -48,7 +41,6 @@ export class MetaDContainer {
startOptions?: StartOptions;
protected network?: StartedNetwork;

web3!: Web3;
ethers!: ethers.providers.JsonRpcProvider;

constructor(
Expand All @@ -65,9 +57,7 @@ export class MetaDContainer {
'--no-telemetry', // disable connecting to substrate telemtry server
'--no-prometheus', // do not expose a Prometheus exporter endpoint
'--no-grandpa',
// TODO(canonbrother): set up chain spec exclusively for test
// '--chain= ./spec.json',
// `-l${FRONTIER_LOG}`,
`-l${META_LOG}`,
`--port=${opts.port}`,
`--rpc-port=${opts.rpcPort}`,
`--ws-port=${opts.wsPort}`,
Expand All @@ -76,52 +66,45 @@ export class MetaDContainer {
'--sealing=manual',
'--force-authoring', // enable authoring even when offline
'--rpc-cors=all',
'--alice', // shortcut for `--name Alice --validator` with session keys for `Alice` added to keystore
'--alice', // shortcut for `--name Alice --validator` with session keys for `Alice` added to keystore, required by manual sealing to author the blocks
'--tmp' // run a temporary node
];
}

async start(startOptions: StartOptions = {}): Promise<void> {
this.network = await new Network().start();

this.startOptions = Object.assign(
MetaDContainer.MetaDPorts[this.metaDNetwork],
startOptions
);
this.startOptions = Object.assign(MetaDContainer.MetaDPorts[this.metaDNetwork], startOptions);
const timeout = this.startOptions.timeout ?? 100_000;

this.startedContainer = await this.genericContainer
.withName(this.generateName())
.withNetworkMode(this.network.getName())
.withCmd(this.getCmd(this.startOptions))
.withExposedPorts(
...Object.values(MetaDContainer.MetaDPorts[this.metaDNetwork])
)
.withExposedPorts(...Object.values(MetaDContainer.MetaDPorts[this.metaDNetwork]))
.withStartupTimeout(timeout)
.start();

this.web3 =
this.ethers =
this.provider !== 'http'
? new Web3(
? new ethers.providers.JsonRpcProvider(
`ws://127.0.0.1:${this.startedContainer.getMappedPort(
MetaDContainer.MetaDPorts[this.metaDNetwork].wsPort
)}`
)}`,
{
chainId: CHAIN_ID,
name: 'meta'
}
)
: new Web3(
: new ethers.providers.JsonRpcProvider(
`http://127.0.0.1:${this.startedContainer.getMappedPort(
MetaDContainer.MetaDPorts[this.metaDNetwork].rpcPort
)}`
)}`,
{
chainId: CHAIN_ID,
name: 'meta'
}
);

this.ethers = new ethers.providers.StaticJsonRpcProvider(
`http://127.0.0.1:${this.startedContainer.getMappedPort(
MetaDContainer.MetaDPorts[this.metaDNetwork].rpcPort
)}`,
{
chainId: CHAIN_ID,
name: 'meta'
}
);
}

async stop(): Promise<void> {
Expand All @@ -130,26 +113,12 @@ export class MetaDContainer {
}

async call(method: string, params: any[]): Promise<any> {
return new Promise<JsonRpcResponse>((resolve, reject) => {
(this.web3.currentProvider as HttpProvider | WebsocketProvider).send(
{
jsonrpc: '2.0',
id: Math.floor(Math.random() * 100000000000000),
method,
params
},
(error: Error | null, response: JsonRpcResponse | undefined) => {
if (error) {
reject(
`Failed to send custom request (${method} (${params.join(
','
)})): ${error.message || error.toString()}`
);
}
resolve(response?.result);
}
);
});
try {
return this.ethers.send(method, params);
} catch (err: any) {
const { error } = JSON.parse(err.body);
throw new MetaDRpcError(error);
}
}

// Create a block and finalize it.
Expand All @@ -168,3 +137,12 @@ export class MetaDContainer {
return `${MetaDContainer.PREFIX}-${this.metaDNetwork}-${rand}`;
}
}

/**
* RPC error from container
*/
export class MetaDRpcError extends Error {
constructor(error: { code: number; message: string }) {
super(`MetaDRpcError: ' ${error.message}', code: ${error.code}`);
}
}
Loading

0 comments on commit 31bb3b8

Please sign in to comment.