# hardhat-scilla-plugin

[Hardhat](https://hardhat.org) 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

```bash
pnpm install hardhat-scilla-plugin
```

Import the plugin in your `hardhat.config.js`:

```js
require("hardhat-scilla-plugin");
```

Or if you are using TypeScript, in your `hardhat.config.ts`:

```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](https://github.com/Zilliqa/scilla/) 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 help
```

## Environment 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:

```typescript
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:
```typescript
TxParams {
    version: number;
    toAddr: string;
    amount: BN;
    gasPrice: BN;
    gasLimit: Long;
    code?: string;
    data?: string;
    receipt?: TxReceipt;
    nonce?: number;
    pubKey?: string;
    signature?: string;
}
```
```typescript
let contract: ScillaContract = await hre.deployScillaContract("HelloWorld", "Hello World", {gasLimit: 8000}); // Override a parameter
```

Alternatively, you can deploy them using the `contractDeployer` object injected to `hre`:
```typescript
  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:
```typescript
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:
```typescript
contract2 = await hre.deployScillaWithLib("TestContract2",
      [{name: "MutualLib", address: mutualLibAddress}]
```
Or:
```typescript
  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 in `Li` (to match the `initZilliqa` use).
 * `gasLimit` - a string denoting the gas limit (in `Qa`, to match `initZilliqa` use)
 * `attempts` - a number denoting the number of attempts to make to check whether a transaction has been accepted
 * `timeout` - 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 `undefined` if 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:

```typescript
await contract.Set(12);
```

### Call a transition with a custom nonce
```typescript
await contract.Set(12, {nonce: 12});
```
It's possible to override the following properties:

```typescript
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;
}
```

```typescript
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.

```typescript
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()`

```typescript
const msg = await contract.msg();
```

### Expect a result

Chai matchers can be used to expect a value:

```typescript
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:

```typescript
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:

```typescript
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):

```typescript
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:

```typescript
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`
* `Option` is converted to its inner value if exists any, or `null` otherwise.
* `Bool` is converted to underlying boolean value.

for more tests please take look at [scilla tests](https://github.com/Zilliqa/Zilliqa/tree/master/tests/EvmAcceptanceTests/test/scilla).

### TODO

- Support formatting complex data types such as `Map` and `List`

### Scilla checker task

To run `scilla-checker` on all of the scilla contracts in the [contracts directory](./contracts/) run:

```bash
npx hardhat scilla-check --libdir path_to_stdlib
```

alternatively, you can check a specific file(s):

```bash
npx hardhat scilla-check --libdir path_to_stdlib contracts/scilla/helloWorld.scilla
```

### TODO

- Add `scilla-fmt` task

# 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.

```sh
pnpm test
```

Will run all tests that don't require an external network (so that test passes will be deterministic).

```sh
pnpm test-live
```

Will run just the tests that do require an external network.

```sh
pnpm test-all
```

Will run both sets of tests.

## Publishing the plugin

In order to publish the plugin to [npmjs.com](https://www.npmjs.com/package/hardhat-scilla-plugin), follow these steps:
1. Increase the plugin version in [package.json](./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. 
