hardhat-scilla-plugin v3.8.1
hardhat-scilla-plugin
Hardhat plugin to test Scilla contracts.
What
This plugin is used to test scilla contracts in hardhat. It tries to be like ethers.js:
- You can deploy contracts using their names.
- You can call transitions like a normal function call.
- You can get field easily.
- You can use custom chai matchers to expect scilla events.
Installation
pnpm install hardhat-scilla-pluginImport the plugin in your hardhat.config.js:
require("hardhat-scilla-plugin");Or if you are using TypeScript, in your hardhat.config.ts:
import "hardhat-scilla-plugin";Running Scilla
In order to check, and extract data from, Scilla contracts, we use binaries from the Scilla distribution itself.
By default, we pull these from the zilliqa/scilla container in docker hub, using Scilla v0.13.3, but if you want to run them from your local machine, you can set the USE_NATIVE_SCILLA environment variable to run them from your PATH. If want to run scilla-checker with USE_NATIVE_SCILLA set, you will need to give the -libDir argument to tell it where to find the Scilla standard library.
If you want to set USE_NATIVE_SCILLA, you need to have scilla-fmt and scilla-checker binaries from the Scilla project on your PATH. You can build them by following the instructions in the scilla project repository.
Tasks
This plugin adds the scilla-check task to Hardhat:
Hardhat version 2.16.0
Usage: hardhat [GLOBAL OPTIONS] scilla-check --libdir <STRING> [...contracts]
OPTIONS:
--libdir Path to Scilla stdlib
POSITIONAL ARGUMENTS:
contracts An optional list of files to check (default: [])
scilla-check: Parsing scilla contracts and performing a number of static checks including typechecking.
For global options help run: hardhat helpEnvironment extensions
This plugin extends the Hardhat Runtime Environment by adding an scillaContracts field
whose type is ScillaContracts.
Usage
Scilla testing can be done in the same way ethers.js is used for solidity. It's possible to deploy a scilla contract by its name and call its transitions just like a normal function call. It's also possible to get a field value through a function call. In the below sections, all of these topics are covered in detail.
Deploy a contract
To deploy a contract all you need to know is its name:
import {ScillaContract, initZilliqa} from "hardhat-scilla-plugin";
const privateKeys = ["254d9924fc1dcdca44ce92d80255c6a0bb690f867abde80e626fbfef4d357004"];
const network_url = "http://localhost:5555";
const chain_id = 1;
initZilliqa(network_url, chain_id, privateKeys);
let contract: ScillaContract = await hre.deployScillaContract("SetGet");
let contract: ScillaContract = await hre.deployScillaContract("HelloWorld", "Hello World"); // Contract with initial parameters.You can override the following parameters while deploying a contract:
TxParams {
version: number;
toAddr: string;
amount: BN;
gasPrice: BN;
gasLimit: Long;
code?: string;
data?: string;
receipt?: TxReceipt;
nonce?: number;
pubKey?: string;
signature?: string;
}let contract: ScillaContract = await hre.deployScillaContract("HelloWorld", "Hello World", {gasLimit: 8000}); // Override a parameterAlternatively, you can deploy them using the contractDeployer object injected to hre:
const contract = await hre.contractDeployer
.withName("Codehash")
.deploy();
const contract = await this.hre.contractDeployer
.withName("HelloWorld")
.withContractParams("Hello world!")
.deploy();
const contract = await this.hre.contractDeployer
.withName("HelloWorld")
.withContractParams("sss")
.withContractCompression() // To enable contract compression.
.deploy();In the same way, you can deploy your libraries with their names:
let library: ScillaContract = await hre.deployScillaLibrary("MyLibrary", false);Pass true as the second parameter if you want your library's contract gets compressed before deployment.
and finally, here is how you can deploy a contract importing a user-defined library:
contract2 = await hre.deployScillaWithLib("TestContract2",
[{name: "MutualLib", address: mutualLibAddress}]Or:
const contract = await this.hre.contractDeployer
.withName("TestContract2")
.withUserDefinedLibraries(
[{name: "MutualLib", address: mutualLibAddress}]
)
.deploy();To change the deployer of the contract, you can send an instance of Account class to hre.setActiveAccount.
Change the default parameters when deploying a contract
You can call
hre.setScillaDefaults( obj )to set the defaults used when deploying a Scilla contract. Parameters supported are:
gasPrice- a string denoting the gas price inLi(to match theinitZilliqause).gasLimit- a string denoting the gas limit (inQa, to matchinitZilliqause)attempts- a number denoting the number of attempts to make to check whether a transaction has been acceptedtimeout- the space between attempts, in milliseconds.
Connect to an existing Scilla contract
Call
hre.interactWithScillaContract(address)To:
- Retrieve the code for a contract from the configured chain.
- Parse it.
- Construct a proxy contract object for it.
- Return that object, or
undefinedif we failed.
address should be a string, and the function returns ScillaContract | undefined.
Call a transition
It's not harder than calling a normal function in typescript.
Let's assume we have a transition named Set which accepts a number as its parameter. Here is how to call it:
await contract.Set(12);Call a transition with a custom nonce
await contract.Set(12, {nonce: 12});It's possible to override the following properties:
export interface TxParams {
version: number;
toAddr: string;
amount: BN;
gasPrice: BN;
gasLimit: Long;
code?: string;
data?: string;
receipt?: TxReceipt;
nonce?: number;
pubKey?: string;
signature?: string;
}await contract.Set(12, {nonce: 12, amount: new BN(1000)});call a transition with a new account
You can call connect on a contract to change its default account which is used to execute transitions.
await contract.connect(newAccount).Set(123);Get field value
If a given contract has a filed named msg is possible to get its current value using a function call to msg()
const msg = await contract.msg();Expect a result
Chai matchers can be used to expect a value:
it("Should set state correctly", async function () {
const VALUE = 12;
await contract.Set(VALUE);
expect(await contract.value()).to.be.eq(VALUE);
});There are two custom chai matchers specially developed to expect scilla events. eventLog and eventLogWithParams.
Use eventLog if you just need to expect event name:
import chai from "chai";
import {scillaChaiEventMatcher} from "hardhat-scilla-plugin";
chai.use(scillaChaiEventMatcher);
it("Should contain event data if emit function is called", async function () {
const tx = await contract.emit();
expect(tx).to.have.eventLog("Emit");
});Otherwise, if you need to deeply expect an event, you should use eventLogWithParams. The first parameter is again the event name. The rest are parameters of the expected event. If you expect to have an event like getHello sending a parameter named msg with a "hello world" value:
import chai from "chai";
import {scillaChaiEventMatcher} from "hardhat-scilla-plugin";
chai.use(scillaChaiEventMatcher);
it("Should send getHello() event when getHello() transition is called", async function () {
const tx = await contract.getHello();
expect(tx).to.have.eventLogWithParams("getHello()", {value: "hello world", vname: "msg"});
});You can even expect data type of the parameter(s):
expect(tx).to.have.eventLogWithParams("getHello()", {value: "hello world", vname: "msg", type: "String"});Type should be a valid Scilla type.
But if you just want to expect on the value of a event parameter do this:
expect(tx).to.have.eventLogWithParams("getHello()", {value: "hello world"});For easier value matching, some value conversions are done under the hood.
- 32/64 bit integer values are converted to
Number - 128/256 bit integer values are converted to
BigNumber Optionis converted to its inner value if exists any, ornullotherwise.Boolis converted to underlying boolean value.
for more tests please take look at scilla tests.
TODO
- Support formatting complex data types such as
MapandList
Scilla checker task
To run scilla-checker on all of the scilla contracts in the contracts directory run:
npx hardhat scilla-check --libdir path_to_stdlibalternatively, you can check a specific file(s):
npx hardhat scilla-check --libdir path_to_stdlib contracts/scilla/helloWorld.scillaTODO
- Add
scilla-fmttask
Plugin development
Running internal tests
If you want to monitor your requests:
mitmweb --mode reverse:https://dev-api.zilliqa.com --modify-headers /~q/Host/dev-api.zilliqa.com --no-web-open-browser --listen-port 5600 --web-port 8600
export ZILLIQA_API_URL=http://localhost:5600/Set ZILLIQA_API_URL to the URL of a network to test - or to eg. http://localhost:5600 if you're proxying as above.
Set ZILLIQA_NETWORK to the name of the network to test against - see test/fixture-projects/hardhat-proxy/hardhat.config.ts for details.
pnpm testWill run all tests that don't require an external network (so that test passes will be deterministic).
pnpm test-liveWill run just the tests that do require an external network.
pnpm test-allWill run both sets of tests.
Publishing the plugin
In order to publish the plugin to npmjs.com, follow these steps:
1. Increase the plugin version in package.json
2. Run npm login and enter your credentials.
3. Run pnpm install
3. Run pnpm publish. This command will run pnpm build && pnpm test beforehand.
7 months ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago