This repository provides a robust framework for developing and testing smart contracts on the OPNet blockchain. The framework includes essential tools and guidelines for ensuring your contracts are functional, secure, and performant.
The OP_NET Smart Contract Testing Framework is designed to facilitate the development and testing of smart contracts. It includes utilities, test cases, and a structured environment to ensure that your contracts work as intended under various conditions.
Ensure the following are installed before using the framework:
- Node.js
- npm or Yarn
- TypeScript
- Rust
Clone the repository and install the dependencies:
git clone https://github.com/@btc-vision/unit-test-framework.git
cd unit-test-framework
npm install
Before running the tests, you need to compile your contracts and test files. Use the following command:
npm run build
Or, alternatively:
gulp
This will compile your TypeScript files into JavaScript, and the output will be located in the build/
directory.
For more advanced documentation please click here.
Here's an example of what your test file might look like:
import { opnet, OPNetUnit } from '../opnet/unit/OPNetUnit.js';
import { Assert } from '../opnet/unit/Assert.js';
import { MyCustomContract } from '../contracts/MyCustomContract.ts';
await opnet('MyCustomContract Tests', async (vm: OPNetUnit) => {
vm.beforeEach(async () => {
// Initialize your contract here...
});
vm.afterEach(async () => {
// Clean up after each test...
});
await vm.it('should correctly execute a function', async () => {
// Your test logic here...
Assert.expect(someValue).toEqual(expectedValue);
});
});
Here's an example of a basic contract that users must implement to interact with their own contracts:
import { CallResponse, ContractRuntime } from '../opnet/modules/ContractRuntime.js';
import { Address, BinaryReader, BinaryWriter } from '@btc-vision/transaction';
export class MyCustomContract extends ContractRuntime {
// Implementation details...
}
Let's create a simple token contract that follows the OP_20 standard (similar to ERC20 in Ethereum). This contract will allow minting, transferring, and checking the balance of tokens.
File: /src/contracts/SimpleToken.ts
import { ContractRuntime, CallResponse } from '../opnet/modules/ContractRuntime.js';
import { Address, BinaryReader, BinaryWriter } from '@btc-vision/transaction';
import { Blockchain } from '../blockchain/Blockchain.js';
export class SimpleToken extends ContractRuntime {
private readonly mintSelector: number = Number(`0x${this.abiCoder.encodeSelector('mint')}`);
private readonly transferSelector: number = Number(`0x${this.abiCoder.encodeSelector('transfer')}`);
private readonly balanceOfSelector: number = Number(`0x${this.abiCoder.encodeSelector('balanceOf')}`);
constructor(
address: Address,
public readonly decimals: number,
gasLimit: bigint = 300_000_000_000n,
) {
super(address, 'bcrt1pe0slk2klsxckhf90hvu8g0688rxt9qts6thuxk3u4ymxeejw53gs0xjlhn', gasLimit);
this.preserveState();
}
public async mint(to: Address, amount: bigint): Promise<void> {
const calldata = new BinaryWriter();
calldata.writeAddress(to);
calldata.writeU256(amount);
const result = await this.readMethod(
this.mintSelector,
Buffer.from(calldata.getBuffer()),
this.deployer,
this.deployer,
);
if (!result.response) {
this.dispose();
throw result.error;
}
const reader = new BinaryReader(result.response);
if (!reader.readBoolean()) {
throw new Error('Mint failed');
}
}
public async transfer(from: Address, to: Address, amount: bigint): Promise<void> {
const calldata = new BinaryWriter();
calldata.writeAddress(to);
calldata.writeU256(amount);
const result = await this.readMethod(
this.transferSelector,
Buffer.from(calldata.getBuffer()),
from,
from,
);
if (!result.response) {
this.dispose();
throw result.error;
}
const reader = new BinaryReader(result.response);
if (!reader.readBoolean()) {
throw new Error('Transfer failed');
}
}
public async balanceOf(owner: Address): Promise<bigint> {
const calldata = new BinaryWriter();
calldata.writeAddress(owner);
const result = await this.readMethod(
this.balanceOfSelector,
Buffer.from(calldata.getBuffer()),
);
if (!result.response) {
this.dispose();
throw result.error;
}
const reader = new BinaryReader(result.response);
return reader.readU256();
}
}
Now let's create a unit test for the SimpleToken
contract. We'll test minting tokens, transferring tokens, and
checking the balance.
File: /src/tests/simpleTokenTest.ts
import { opnet, OPNetUnit } from '../opnet/unit/OPNetUnit.js';
import { Assert } from '../opnet/unit/Assert.js';
import { Blockchain } from '../blockchain/Blockchain.js';
import { SimpleToken } from '../contracts/SimpleToken.js';
import { Address } from '@btc-vision/transaction';
const decimals = 18;
const totalSupply = 1000000n * (10n ** BigInt(decimals));
const deployer: Address = Blockchain.generateRandomAddress();
const receiver: Address = Blockchain.generateRandomAddress();
await opnet('SimpleToken Contract', async (vm: OPNetUnit) => {
let token: SimpleToken;
vm.beforeEach(async () => {
Blockchain.dispose();
token = new SimpleToken(deployer, decimals);
Blockchain.register(token);
await Blockchain.init();
});
vm.afterEach(async () => {
token.dispose();
});
await vm.it('should mint tokens correctly', async () => {
await token.mint(receiver, totalSupply);
const balance = await token.balanceOf(receiver);
Assert.expect(balance).toEqual(totalSupply);
});
await vm.it('should transfer tokens correctly', async () => {
await token.mint(deployer, totalSupply);
const transferAmount = 100000n * (10n ** BigInt(decimals));
await token.transfer(deployer, receiver, transferAmount);
const balanceDeployer = await token.balanceOf(deployer);
const balanceReceiver = await token.balanceOf(receiver);
Assert.expect(balanceDeployer).toEqual(totalSupply - transferAmount);
Assert.expect(balanceReceiver).toEqual(transferAmount);
});
await vm.it('should return correct balances', async () => {
await token.mint(receiver, totalSupply);
const balance = await token.balanceOf(receiver);
Assert.expect(balance).toEqual(totalSupply);
const balanceDeployer = await token.balanceOf(deployer);
Assert.expect(balanceDeployer).toEqual(0n);
});
});
-
SimpleToken Contract: This contract implements a simple token with minting, transferring, and balance checking functions.
- mint: Mints tokens to a specified address.
- transfer: Transfers tokens from one address to another.
- balanceOf: Returns the balance of a specified address.
-
simpleTokenTest.ts: This test suite covers the main functionality of the
SimpleToken
contract.-
beforeEach: Initializes a new instance of the
SimpleToken
contract before each test case. - afterEach: Disposes of the contract instance after each test case.
-
Test Cases:
- should mint tokens correctly: Tests that tokens are correctly minted to a given address.
- should transfer tokens correctly: Tests that tokens are correctly transferred from one address to another.
- should return correct balances: Tests that the balance checking function returns the expected results.
-
beforeEach: Initializes a new instance of the
Contributions are welcome! To contribute:
- Fork the repository.
- Create a new branch (
git checkout -b feature/your-feature
). - Commit your changes (
git commit -am 'Add new feature'
). - Push to the branch (
git push origin feature/your-feature
). - Open a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.