Skip to content

windranger-io/windranger-tools-ethers

Repository files navigation

Contract Tools for Ethers+Typechain

Strongly-typed zero-code handling for events and contracts. This is a tooling package for solidity-template but it is also usable independently.

This package contains tooling for tests and deploys and it provides:

  • strictly typed access to contracts by contract names
  • strictly typed handing of events

Typed handling of events also supports extraction & filtering of events emitted by contracts called inside a transaction.

Pre-requisite

This tooling requires ethers and ethers typechain for contracts.

Installation

npm install --save-dev @windranger-io/windranger-tools-ethers
# or
yarn add --dev @windranger-io/windranger-tools-ethers

Installation of event type handler

This tooling requires access to ethers typechains of a project. For this, the following files should be added to the project.

File events.ts

import {BaseContract} from 'ethers'
import {wrapEventType} from '@windranger-io/windranger-tools-ethers'
import {TypedEvent, TypedEventFilter} from '<path to typechain root>/common'

/*
 * This method MUST be local to handle typing information properly.
 */
export const eventOf = <
    C extends BaseContract,
    N extends keyof C['filters'] & string,
    E extends ReturnType<C['filters'][N]> extends TypedEventFilter<
        infer R extends TypedEvent
    >
        ? R
        : never
>(
    emitter: C,
    name: N
) =>
    wrapEventType<
        E extends TypedEvent<infer A, object> ? A : never,
        E extends TypedEvent<unknown[], infer O extends object> ? O : never
    >(name, emitter)

File contracts.ts

import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'
import {ethers, upgrades} from 'hardhat'

import {wrapImportedFactories} from '@windranger-io/windranger-tools-ethers/dist/tools/contract-wrappers'
import * as types from '<path to typechain root>'

const typedFactories = wrapImportedFactories(types, signer(0))

/**
 * Deploys a contract, that may or may not have constructor parameters.
 *
 * @param name the case sensitive name of the contract in the Solidity file.
 */
export const deployContract = typedFactories.deploy.bind(typedFactories)

/**
 * Deploys an admin proxy with the contract as the implementation behind.
 * The contract may or may not have constructor parameters.
 *
 * @param name the case sensitive name of the contract in the Solidity file.
 */
export const deployContractWithProxy = typedFactories.deployWithDelegate(
    async (factory, deployArgs) => {
        const contract = await upgrades.deployProxy(factory, [...deployArgs], {
            kind: 'uups'
        })
        return contract.deployed()
    }
)

/**
 * Upgrades an implementation contract that has a proxy contract in front.
 *
 * @param name the case sensitive name of the contract in the Solidity file.
 * @param address existing address of a proxy with the implementation behind.
 */
export const upgradeContract = typedFactories.attachWithDelegate(
    async (factory, address) => {
        const contract = await upgrades.upgradeProxy(address, factory)
        return contract.deployed()
    }
)

async function signer(index: number): Promise<SignerWithAddress> {
    const signers = await ethers.getSigners()
    return signers[index]
}

Usage

Typed access to contracts by name

The given code above adds methods deployContract, deployContractWithProxy and upgradeContract with the following properties:

  • name is checked at compile time (in IDE / during transpile) to be a deploy-able contract
  • result has a typechain type based on the name, e.g. for the name ‘Tub’, result will be Tub
  • arguments for deploy are also a strictly typed tuple that matches the deploy function and checked at compile time:
    • (breaking change) for deploy without arguments an empty array should be provided explicitly.
import * from './contracts'

// This function will deploy a contract of a type Box.
// The result will have type Box or a compile-time error when there is no such contract.
// And the list of arguments is also type-checked to match declared deploy args.
const box = await deployContract('Box', [])

// This function will deploy a contract of a type Box and use it as a proxy implementation.
// The result will have type Box or a compile-time error when there is no such contract.
// And the list of arguments is also type-checked to match declared deploy args.
// The contract will be deployed under a proxy.
const boxV1 = await deployContractWithProxy('Box', [], admin)

// This function will use OpenZeppelin to deploy a contract of a type BoxV2 and use it as a new implementation for the given proxy.
// The result will have type BoxV2 or a compile-time error when there is no such contract.
const boxV2 = await upgradeContract('BoxV2', boxV1.address)

Typed access to events

To use typed event access and filtering, first invoke

eventOf(<typed contract>, '<event name>') where

  • The first argument must be a contract of a type generated by ethers typechain.
  • The second argument is name of an event. This name is checked at compile time.

The result of this function is a helper that facilitates extraction and filtering of events from transactions.

Here is a simple example of use.

const box = await deployContract('Box', [])
const storedEvent = eventOf(box, 'Stored')

...

// This method will read the event log from someContract.doSomething() transaction
// and will find all events of type Stored emitted by box.address.
// The fould list of **decoded and typed** events is passed to the callback.
// Transaction receipt is returned, so other events can be extracted from the same call.
const receipt = await storedEvent.waitAll(
    someContract.doSomething(), 
    (events) => {
        expect(events.length).eq(2)
        expect(events[0].value).eq('Sample Value 1')
        expect(events[1].value).eq('Sample Value 2')
    }
);

Advanced use

Additional documentation is provided here.

Detailed examples of use in tests are provided here.

About

No description, website, or topics provided.

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published