Report Bug |
Request Feature
Echpochmak, is a framework based on TONOS Client Library for TON DApp development
It provides some high-level interfaces to interact with your TON smart-contracts, allowing you to write integration and e2e tests.
- About The Project
- Content Table
- Getting Started
- Test Examples
- Usage
- Write test
- Tests are written using Mocha
- Restart tondev command
- Or set port (if you use don't default port in tondev-cli)
- The manager is in the global scope
- Create new Manager object
- Create client
- Generate keys
- Load contract
- Referring to the contract
- Deploy contract
- Deploy with parameters
- Or use custom keys
- Add contract from address
- Run contract
- Run contract(no sign)
- RunLocal contract
- Run contract with message
- Use Giver
- Contract fields
- Helpers
- Asserts
- Wallet Contract
- Custom Contract Object
- Contributing
- License
- Contact
- Install
Echpochmak
npm install -g echpochmak
You can see examples of tests here
To run all tests:
echpochmak-cli test -p ./path/to/tests/folder
Synopsis
test [options]
Options
Option | Description | Default |
---|---|---|
-p, --path | Path to test folder | |
-b, --bail | Enable bail | false |
-v, --verbose | Wite debug info | false |
-t, --timeout | Timeout time for test | 0 |
--version (boolean) | Show version | |
--help (boolean) | Show help information |
If you need help, use
echpochmak-cli --help
beforeEach(async () => {
await restart(); //Recreate and start containers
});
restart command does not set a port for the container, but only uses it for long polling
beforeEach(async () => {
await restart(8080); //Default 80
});
const manager = new Manager();
beforeEach(async () => {
manager = new Manager();
default
['net.ton.dev']
Signature
async createClient(servers: string[] = ['net.ton.dev'])
await manager.createClient(['http://localhost:8080/graphql']);
let keys = await manager.createKeysAndReturn();
Signature
public async loadContract( contractPath: string, abiPath: string, options: loadOptions )
await manager.loadContract(
'./tests/contract/15_MessageReceiver.tvc', // tvc
'./tests/contract/15_MessageReceiver.abi.json' // abi
);
await manager.loadContract(
'./tests/contract/15_MessageReceiver.tvc', // tvc
'./tests/contract/15_MessageReceiver.abi.json' // abi
);
// Custom name
await manager.loadContract(
'./tests/contract/15_MessageSender.tvc',
'./tests/contract/15_MessageSender.abi.json',
{ contractName: 'sender ' } // Name
);
// Custom keys
await manager.loadContract(
'./tests/contract/15_MessageSender.tvc',
'./tests/contract/15_MessageSender.abi.json',
{ keys: anyKeys } // keys
);
Get future/current address
let address = manager.contracts['15_MessageReceiver'].address;
loadOption signature
export interface loadOptions {
keys?: any;
contractName?: string;
}
manager.contracts['Contract_name']; // Taken from the name of the tvc file without extension
Signature
async deployContract(constructorParams = {}, giveGram = true, keys?)
await manager.contracts['15_MessageReceiver'].deployContract();
await manager.contracts['15_MessageSender'].deployContract();
await manager.contracts['9_PiggyBank'].deployContract({
own: manager.contracts['9_PiggyBank_Owner'].address,
lim: 1000000,
});
This method allows you to provide: constructor parameters, constructor headers and init parameters of your deployable smart contract
await manager.contracts['someName'].complicatedDeploy(
constructorParams,
constructorHeader,
initParams,
)
await manager.contracts['9_PiggyBank'].complicatedDeploy(
{
own: manager.contracts['9_PiggyBank_Owner'].address,
lim: 1000000,
},
{
pubkey: keypair.public,
expire: new Date()
},
{
randomNonce: '0x' + crypto.createHash('sha256').update(crypto.randomBytes(32)).digest('hex')
}
);
### Or use custom keys
```js
await manager.contracts['9_PiggyBank'].deployContract({}, keys);
Signature
async addContractFromAddress( address: string, abiPath: string, contractName: string, keyPair? )
await manager.addContractFromAddress(
'address',
'abiPath',
'contractName',
keyPair // HERE, the keys are indicated, if nothing is specified, those inside the contract object are used
);
Signature
async runContract(functionName, input, keyPair?)
await manager.contracts['15_MessageSender'].runContract('sendMessage', {
anotherContract: manager.contracts['15_MessageReceiver'].address,
});
await manager.contracts['15_MessageSender'].runContract(
'sendMessage',
{
anotherContract: manager.contracts['15_MessageReceiver'].address,
},
null // HERE, the keys are indicated, if nothing is specified, those inside the contract object are used (see below);
);
Signature
async runLocal(functionName, input, keyPair?)
await manager.contracts['15_MessageSender'].runLocal('getData', {});
Signature
public async runWithMessage(functionName, input, keyPair?)
const msg = await manager.contracts['15_MessageSender'].runWithMessage(
'sendMessage',
{
anotherContract: manager.contracts['15_MessageReceiver'].address,
}
);
console.log(`Tokens were sent. Transaction id is ${msg.transaction.id}`);
console.log(`Run fees are ${JSON.stringify(msg.fees, null, 2)}`);
Signature
async giveToAddress(address, amount?: number)
manager.giveToAddress(manager.contracts['15_MessageReceiver'].address); // give 1000000000000000 gram
manager.contracts['15_MessageSender'].address; // Contract address
manager.contracts['15_MessageSender'].contractPath; // Contract path
manager.contracts['15_MessageSender'].isDeployed; // boolean
manager.contracts['15_MessageSender'].contractPackage; //{ abi,imageBase64 }
manager.helpers.<helper>;
Signature
public static async deployCheck(address, client)
await manager.helpers.deployCheck(contractAddress, manager.client);
Signature
public static async getAccountBalance(address, client)
await manager.helpers.getAccountBalance(contractAddress, manager.client);
Signature
public static async hasOnBounced(address, timestamp: number, client)
timestamp
- The time when the transaction preceding OnBounced was made, guarantees the assistant will wait for the last message OnBounced
await manager.helpers.hasOnBounced(contractAddress, manager.client);
Signature
public static async lastMessage( address, client, fields = 'src, dst, bounce, bounced, value' )
let tx = await manager.helpers.lastTransaction(contractAddress, manager.client);
Signature
public static async lastMessage( address, client, fields = 'src, dst, bounce, bounced, value' )
let msg = await manager.helpers.lastMessage(contractAddress, manager.client);
Signature
public static async balanceHasChanged( address, client, oldValue, type: hasChangedValue )
await manager.helpers.balanceHasChanged(
contractAddress,
manager.client,
100000,
hasChangedValue.big
);
hasChangedValue enum
export enum hasChangedValue {
big,
small,
}
Signature
static async getRunFees( contract: Contract, functionName, input, client, keyPair )
const fees = await manager.helpers.getRunFees(
manager.contracts['15_MessageSender'],
'send',
manager.client,
keys
);
Signature
static async getDeployFees( contract: Contract, constructorParams, newAccount: boolean, client, keyPair )
const fees = await manager.helpers.getDeployFees(
manager.contracts['15_MessageSender'],
{},
true,
manager.client,
keys
);
Signature
assertError = async (asyncFn: any, code: number, message?)
Example
await assertError(
async () => {
await manager.contracts['9_PiggyBank'].runContract('getData', {});
},
3025,
'getData'
);
A simple wallet contract, you may find it useful when testing fallback functions
Signature
async createWallet(keys?)
let wallet = await manager.createWallet();
let wallet = await manager.createWallet(keys);
await wallet.deploy();
Signature
async sendTransaction(dest: string, value: number, bounce: boolean)
await wallet.sendTransaction(
(dest: string),
(value: number),
(bounce: boolean)
);
wallet.address; // wallet address
wallet.contractPath; // wallet path
wallet.isDeployed; // boolean
wallet.contractPackage; //{ abi,imageBase64 }
CustomObject.example.js
const BaseContract = require('echpochmak').BaseContract; // Import BaseContract class
class BankContract extends BaseContract {
async getData() {
// new
return await this.runLocal('getData', {}); // Using 'contract' object fields
}
}
module.exports = BankContract;
Import contract object
const BankContract = require('./CustomObject.example');
Create object (Constructor used from BaseContract class)
const customBankObject = await new BankContract(
'./tests/contract/9_PiggyBank.tvc',
'./tests/contract/9_PiggyBank.abi.json',
manager.client,
await manager.createKeysAndReturn()
);
Add object in manager
manager.addCustomContract(customBankObject, 'customBank');
Use custom fields
const res = await manager.contracts['customBank'].getData();
BaseContract class
Spoiler warning
/* eslint-disable @typescript-eslint/no-explicit-any */
import { readFileSync } from 'fs';
import { resolve } from 'path';
import { deploy } from '../Deploy/Deploy';
export abstract class BaseContract {
public contractPackage: any;
public contractPath: string;
public isDeployed = false;
public address: any;
protected client: any;
protected keys: any;
constructor(
contractPath: string,
abiPath: string,
client,
keys: any,
noPath = false
) {
// read contract .tvc file
let imageBase64 = '';
if (!noPath) {
const buff = readFileSync(resolve(contractPath));
// convert tvc code into base64
imageBase64 = buff.toString('base64');
}
// eslint-disable-next-line @typescript-eslint/no-var-requires
const abi = require(resolve(abiPath));
this.contractPackage = { abi, imageBase64 };
this.client = client;
this.keys = keys;
this.isDeployed = false;
}
public async deployContract(constructorParams = {}, giveGram = true, keys?) {
this.address = await deploy(
this.client,
this.contractPackage,
keys || this.keys,
constructorParams,
giveGram
);
this.isDeployed = true;
}
public async runWithMessage(functionName, input, keyPair?) {
if (!this.isDeployed) {
throw new Error('Contract not deployed');
}
if (!keyPair) {
keyPair = this.keys;
}
const runMessage = await this.client.contracts.createRunMessage({
address: this.address,
abi: this.contractPackage.abi,
functionName,
input,
keyPair,
});
const messageProcessingState = await this.client.contracts.sendMessage(
runMessage.message
);
const result = await this.client.contracts.waitForRunTransaction(
runMessage,
messageProcessingState
);
return result;
}
public async runLocal(functionName, input, keyPair?) {
if (!this.isDeployed) {
throw new Error('Contract not deployed');
}
if (!keyPair) {
keyPair = this.keys;
}
const response = await this.client.contracts.runLocal({
address: this.address,
abi: this.contractPackage.abi,
functionName,
input,
keyPair,
});
return response;
}
public async futureAddress() {
const futureAddress = (
await this.client.contracts.getDeployData({
abi: this.contractPackage.abi,
imageBase64: this.contractPackage.imageBase64,
publicKeyHex: this.keys.public,
})
).address;
return futureAddress;
}
public async runContract(functionName, input, keyPair?) {
if (!this.isDeployed) {
throw new Error('Contract not deployed');
}
if (!keyPair) {
keyPair = this.keys;
}
const response = await this.client.contracts.run({
address: this.address,
abi: this.contractPackage.abi,
functionName: functionName,
input: input,
keyPair: keyPair, //there is no pubkey key check in the contract so we can leave it empty
});
return response.output;
}
}
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
Distributed under the Apache 2.0 License. See LICENSE
for more information.