Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
Merge pull request #3422 from trufflesuite/abi-utils
Browse files Browse the repository at this point in the history
Define new package @truffle/abi-utils
  • Loading branch information
gnidan authored Oct 12, 2020
2 parents 37c9fd5 + f891db1 commit 5d591c8
Show file tree
Hide file tree
Showing 29 changed files with 3,098 additions and 236 deletions.
4 changes: 4 additions & 0 deletions packages/abi-utils/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/*
yarn.lock
package-lock.json
dist
110 changes: 110 additions & 0 deletions packages/abi-utils/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# `@truffle/abi-utils`

Contains a few utilities for dealing with ABIs.

## Contents

This package contains a few different components:

- [Normalize ABIs](#normalize-abis)
- [TypeScript types](#typescript-types)
- [Arbitrary random ABIs](#arbitrary-random-abis)

## Normalize ABIs

> ```typescript
> // handle function entries omitting "type" from JSON
> const isFunctionEntry = entry.type === "function" || !("type" in entry);
>
> // handle: v--- new way v--- old way v--- default
> const isPayable = entry.stateMutability === "payable" || entry.payable || false;
>
> // handle "outputs" possibly being undefined
> const outputs = entry.outputs || [];
> ```
_^ Have you ever had to do this sort of thing?_ :scream:
Solidity's official [JSON ABI specification](https://solidity.readthedocs.io/en/v0.7.3/abi-spec.html)
is rather permissive, since it remains backwards compatible with older
versions of the language and because it permits omitting fields with default
values. This can get annoying if you're programmatically processing ABIs.
:information_source: This package provides a `normalize` function to purge
these kinds of inconsistencies.
```javascript
const { normalize } = require("@truffle/abi-utils");
```
```typescript
import { normalize } from "@truffle/abi-utils";
```

Specifically, this normalizes by:
- Ensuring every ABI entry has a `type` field, since it's optional for
`type: "function"`
- Populating default value `[]` for function `outputs` field
- Removing all instances of the legacy `payable` and `constant` fields
- Replacing those two fields with the newer `stateMutability` field

To use, provide the ABI as a JavaScript array, as the sole argument to the
function:

```typescript
// accepts ABIs from Solidity versions back to 0.4.12 or earlier!
const abi = normalize([{"type": "constructor"/*, ...*/}/*, ...*/);

// don't even worry about it
const isFunctionEntry = entry.type === "function";
const isPayable = entry.stateMutability === "payable";
```
## TypeScript types
This package exports the following types for **normalized** ABIs.
- `Abi`, to represent the full ABI array
- `Entry`, to represent items in ABI arrays
- `FunctionEntry`, to represent named functions
- `ConstructorEntry`, to represent constructors
- `FallbackEntry`, to represent old or new fallback functions
- `ReceiveEntry`, to represent receive functions
- `Parameter`, to represent parameters defined in entry inputs or outputs
- `EventParameter`, to represent event parameters
To use these, you should first call [`normalize`](#normalize-abis), described
above.
```typescript
import * as Abi from "@truffle/abi-utils";

const abi: Abi.Abi = [{"type": "constructor"/*, ...*/}/*, ...*/];
const parameter: Abi.Parameter = {"type": "tuple[]", "components": [/*...*/]};
// etc.
```
## Arbitrary random ABIs
_Do you need to test all the different kinds of ABIs, including testing your
support for the various quirks across different Solidity versions?_ :flushed:
You can use this package for generating all sorts of random ABIs, random ABI
events, random ABI parameter values, etc.
This package provides [fast-check](https://github.com/dubzzz/fast-check)
arbitraries for property-based testing methodologies. If you're not familiar
with fast-check or property-based testing, please see the link above for more
information.
```typescript
import * as fc from "fast-check";
import { Arbitrary } from "@truffle/abi-utils";

// generate 10 random ABIs
const randomAbis = fc.sample(Arbitrary.Abi(), 10);
```
See this package's [internal tests for `normalize`](./lib/normalize.test.ts)
for example usage in automated tests.
4 changes: 4 additions & 0 deletions packages/abi-utils/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node"
};
57 changes: 57 additions & 0 deletions packages/abi-utils/lib/arbitrary.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { testProp } from "jest-fast-check";
import * as abiSchema from "@truffle/contract-schema/spec/abi.spec.json";
import { matchers } from "jest-json-schema";

expect.extend(matchers);

import * as Arbitrary from "./arbitrary";

// helper function to ensure that, when we match against a given subschema,
// we also include all the definitions, in case the subschema references those
const withDefinitions = (schema: object) => ({
definitions: abiSchema.definitions,
...schema
});

const arbitraries = {
Parameter: {
arbitrary: Arbitrary.Parameter(),
schema: withDefinitions(abiSchema.definitions.Parameter)
},
EventParameter: {
arbitrary: Arbitrary.EventParameter(),
schema: withDefinitions(abiSchema.definitions.EventParameter)
},
EventEntry: {
arbitrary: Arbitrary.EventEntry(),
schema: withDefinitions(abiSchema.definitions.Event)
},
FunctionEntry: {
arbitrary: Arbitrary.FunctionEntry(),
schema: withDefinitions(abiSchema.definitions.NormalFunction)
},
ConstructorEntry: {
arbitrary: Arbitrary.ConstructorEntry(),
schema: withDefinitions(abiSchema.definitions.ConstructorFunction)
},
ReceiveEntry: {
arbitrary: Arbitrary.ReceiveEntry(),
schema: withDefinitions(abiSchema.definitions.ReceiveFunction)
},
FallbackEntry: {
arbitrary: Arbitrary.FallbackEntry(),
schema: withDefinitions(abiSchema.definitions.FallbackFunction)
},
Abi: {
arbitrary: Arbitrary.Abi(),
schema: abiSchema
}
};

for (const [name, { arbitrary, schema }] of Object.entries(arbitraries)) {
describe(`Arbitrary.${name}`, () => {
testProp("validates schema", [arbitrary], value => {
expect(value).toMatchSchema(schema);
});
});
}
Loading

0 comments on commit 5d591c8

Please sign in to comment.