Skip to content

Commit

Permalink
BREAKING feat(core): use human readable abis (#486)
Browse files Browse the repository at this point in the history
## Description

Make the type used by EVMts human readable abis 

## Testing

Explain the quality checks that have been done on the code changes

## Additional Information

- [ ] I read the [contributing docs](../docs/contributing.md) (if this
is your first contribution)

Your ENS/address:

---------

Co-authored-by: Will Cory <[email protected]>
  • Loading branch information
roninjin10 and Will Cory authored Sep 29, 2023
1 parent b8a8440 commit a1b10f2
Show file tree
Hide file tree
Showing 32 changed files with 511 additions and 1,088 deletions.
11 changes: 11 additions & 0 deletions .changeset/chatty-jobs-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@evmts/core": major
---

Changed EvmtsContract to use human readable ABIs by default

Before
<img width="429" alt="image" src="https://github.com/evmts/evmts-monorepo/assets/35039927/74ce632f-bc39-4cc7-85ee-1f6cd0014005">

After
<img width="527" alt="image" src="https://github.com/evmts/evmts-monorepo/assets/35039927/4f4ea9a6-adfb-4751-9446-dd721118f3a9">
12 changes: 6 additions & 6 deletions bundlers/bundler/src/bundler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ describe(bundler.name, () => {
},
},
"code": "import { evmtsContractFactory } from '@evmts/core'
const _TestContract = {\\"name\\":\\"TestContract\\",\\"abi\\":[]} as const
const _TestContract = {\\"name\\":\\"TestContract\\",\\"humanReadableAbi\\":[]} as const
export const TestContract = evmtsContractFactory(_TestContract)",
"modules": {
"module1": {
Expand Down Expand Up @@ -561,7 +561,7 @@ describe(bundler.name, () => {
},
},
"code": "import { evmtsContractFactory } from '@evmts/core'
const _TestContract = {\\"name\\":\\"TestContract\\",\\"abi\\":[]} as const
const _TestContract = {\\"name\\":\\"TestContract\\",\\"humanReadableAbi\\":[]} as const
export const TestContract = evmtsContractFactory(_TestContract)",
"modules": {
"module1": {
Expand Down Expand Up @@ -652,7 +652,7 @@ describe(bundler.name, () => {
},
},
"code": "const { evmtsContractFactory } = require('@evmts/core')
const _TestContract = {\\"name\\":\\"TestContract\\",\\"abi\\":[]}
const _TestContract = {\\"name\\":\\"TestContract\\",\\"humanReadableAbi\\":[]}
module.exports.TestContract = evmtsContractFactory(_TestContract)",
"modules": {
"module1": {
Expand Down Expand Up @@ -743,7 +743,7 @@ describe(bundler.name, () => {
},
},
"code": "const { evmtsContractFactory } = require('@evmts/core')
const _TestContract = {\\"name\\":\\"TestContract\\",\\"abi\\":[]}
const _TestContract = {\\"name\\":\\"TestContract\\",\\"humanReadableAbi\\":[]}
module.exports.TestContract = evmtsContractFactory(_TestContract)",
"modules": {
"module1": {
Expand Down Expand Up @@ -834,7 +834,7 @@ describe(bundler.name, () => {
},
},
"code": "import { evmtsContractFactory } from '@evmts/core'
const _TestContract = {\\"name\\":\\"TestContract\\",\\"abi\\":[]}
const _TestContract = {\\"name\\":\\"TestContract\\",\\"humanReadableAbi\\":[]}
export const TestContract = evmtsContractFactory(_TestContract)",
"modules": {
"module1": {
Expand Down Expand Up @@ -925,7 +925,7 @@ describe(bundler.name, () => {
},
},
"code": "import { evmtsContractFactory } from '@evmts/core'
const _TestContract = {\\"name\\":\\"TestContract\\",\\"abi\\":[]}
const _TestContract = {\\"name\\":\\"TestContract\\",\\"humanReadableAbi\\":[]}
export const TestContract = evmtsContractFactory(_TestContract)",
"modules": {
"module1": {
Expand Down
12 changes: 6 additions & 6 deletions bundlers/bundler/src/runtime/generateEvmtsBody.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ describe('generateEvmtsBody', () => {
it('should generate correct body for cjs module', () => {
const result = generateEvmtsBody(artifacts, 'cjs')
expect(result).toMatchInlineSnapshot(`
"const _MyContract = {\\"name\\":\\"MyContract\\",\\"abi\\":[]}
"const _MyContract = {\\"name\\":\\"MyContract\\",\\"humanReadableAbi\\":[]}
/**
* MyContract
* @property balanceOf(address) Returns the amount of tokens owned by account
*/
module.exports.MyContract = evmtsContractFactory(_MyContract)
const _AnotherContract = {\\"name\\":\\"AnotherContract\\",\\"abi\\":[]}
const _AnotherContract = {\\"name\\":\\"AnotherContract\\",\\"humanReadableAbi\\":[]}
/**
* MyContract
* @property balanceOf(address) Returns the amount of tokens owned by account
Expand All @@ -52,13 +52,13 @@ describe('generateEvmtsBody', () => {
it('should generate correct body for mjs module', () => {
const result = generateEvmtsBody(artifacts, 'mjs')
expect(result).toMatchInlineSnapshot(`
"const _MyContract = {\\"name\\":\\"MyContract\\",\\"abi\\":[]}
"const _MyContract = {\\"name\\":\\"MyContract\\",\\"humanReadableAbi\\":[]}
/**
* MyContract
* @property balanceOf(address) Returns the amount of tokens owned by account
*/
export const MyContract = evmtsContractFactory(_MyContract)
const _AnotherContract = {\\"name\\":\\"AnotherContract\\",\\"abi\\":[]}
const _AnotherContract = {\\"name\\":\\"AnotherContract\\",\\"humanReadableAbi\\":[]}
/**
* MyContract
* @property balanceOf(address) Returns the amount of tokens owned by account
Expand All @@ -70,13 +70,13 @@ describe('generateEvmtsBody', () => {
it('should generate correct body for ts module', () => {
const result = generateEvmtsBody(artifacts, 'ts')
expect(result).toMatchInlineSnapshot(`
"const _MyContract = {\\"name\\":\\"MyContract\\",\\"abi\\":[]} as const
"const _MyContract = {\\"name\\":\\"MyContract\\",\\"humanReadableAbi\\":[]} as const
/**
* MyContract
* @property balanceOf(address) Returns the amount of tokens owned by account
*/
export const MyContract = evmtsContractFactory(_MyContract)
const _AnotherContract = {\\"name\\":\\"AnotherContract\\",\\"abi\\":[]} as const
const _AnotherContract = {\\"name\\":\\"AnotherContract\\",\\"humanReadableAbi\\":[]} as const
/**
* MyContract
* @property balanceOf(address) Returns the amount of tokens owned by account
Expand Down
3 changes: 2 additions & 1 deletion bundlers/bundler/src/runtime/generateEvmtsBody.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Artifacts } from '../solc/resolveArtifactsSync'
import { generateDtsBody } from './generateEvmtsBodyDts'
import { formatAbi } from 'abitype'

type ModuleType = 'cjs' | 'mjs' | 'ts' | 'dts'

Expand All @@ -14,7 +15,7 @@ export const generateEvmtsBody = (
.flatMap(([contractName, { abi, userdoc = {} }]) => {
const contract = JSON.stringify({
name: contractName,
abi,
humanReadableAbi: formatAbi(abi),
})

const natspec = Object.entries(userdoc.methods ?? {}).map(
Expand Down
2 changes: 1 addition & 1 deletion bundlers/bundler/src/runtime/generateEvmtsBodyDts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('generateDtsBody', () => {

it('should generate correct body with etherscan links', () => {
expect(generateDtsBody(artifacts)).toMatchInlineSnapshot(`
"const _abiMyContract = [{\\"type\\":\\"constructor\\",\\"inputs\\":[],\\"stateMutability\\":\\"payable\\"}] as const;
"const _abiMyContract = [\\"constructor() payable\\"] as const;
const _nameMyContract = \\"MyContract\\" as const;
/**
* MyContract EvmtsContract
Expand Down
7 changes: 5 additions & 2 deletions bundlers/bundler/src/runtime/generateEvmtsBodyDts.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { Artifacts } from '../solc/resolveArtifactsSync'
import { formatAbi } from 'abitype'

export const generateDtsBody = (artifacts: Artifacts) => {
return Object.entries(artifacts)
.flatMap(([contractName, { abi, userdoc = {} }]) => {
const contract = {
name: contractName,
abi,
humanReadableAbi: formatAbi(abi),
}
const natspec = Object.entries(userdoc.methods ?? {}).map(
([method, { notice }]) => ` * @property ${method} ${notice}`,
Expand All @@ -14,7 +15,9 @@ export const generateDtsBody = (artifacts: Artifacts) => {
natspec.unshift(` * @notice ${userdoc.notice}`)
}
return [
`const _abi${contractName} = ${JSON.stringify(contract.abi)} as const;`,
`const _abi${contractName} = ${JSON.stringify(
contract.humanReadableAbi,
)} as const;`,
`const _name${contractName} = ${JSON.stringify(
contractName,
)} as const;`,
Expand Down
13 changes: 6 additions & 7 deletions core/src/EvmtsContract.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import type { Events } from './event/Event'
import type { Read } from './read/Read'
import type { Write } from './write/Write'
import type { Abi, FormatAbi } from 'abitype'
import type { ParseAbi } from 'abitype'

export type EvmtsContract<
TName extends string,
TAbi extends Abi,
THumanReadableAbi = FormatAbi<TAbi>,
THumanReadableAbi extends ReadonlyArray<string>,
> = {
abi: TAbi
abi: ParseAbi<THumanReadableAbi>
humanReadableAbi: THumanReadableAbi
name: TName
events: Events<TName, TAbi>
read: Read<TName, TAbi>
write: Write<TName, TAbi>
events: Events<TName, THumanReadableAbi>
read: Read<TName, THumanReadableAbi>
write: Write<TName, THumanReadableAbi>
}
29 changes: 17 additions & 12 deletions core/src/event/Event.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,52 @@
import type {
Abi,
ExtractAbiEvent,
ExtractAbiEventNames,
FormatAbi,
ParseAbi,
} from 'abitype'
import type { BlockNumber, BlockTag, CreateEventFilterParameters } from 'viem'
import type { MaybeExtractEventArgsFromAbi } from 'viem/dist/types/types/contract'

export type ValueOf<T> = T[keyof T]

export type Events<TName extends string, TAbi extends Abi> = {
[TEventName in ExtractAbiEventNames<TAbi>]: (<
export type Events<
TName extends string,
THumanReadableAbi extends readonly string[],
> = {
[TEventName in ExtractAbiEventNames<ParseAbi<THumanReadableAbi>>]: (<
TStrict extends boolean = false,
TFromBlock extends BlockNumber | BlockTag | undefined = undefined,
TToBlock extends BlockNumber | BlockTag | undefined = undefined,
>(
params: Pick<
CreateEventFilterParameters<
ExtractAbiEvent<TAbi, TEventName>,
TAbi,
ExtractAbiEvent<ParseAbi<THumanReadableAbi>, TEventName>,
ParseAbi<THumanReadableAbi>,
TStrict,
TFromBlock,
TToBlock,
TEventName,
MaybeExtractEventArgsFromAbi<TAbi, TEventName>
MaybeExtractEventArgsFromAbi<ParseAbi<THumanReadableAbi>, TEventName>
>,
'fromBlock' | 'toBlock' | 'args' | 'strict'
>,
) => CreateEventFilterParameters<
ExtractAbiEvent<TAbi, TEventName>,
TAbi,
ExtractAbiEvent<ParseAbi<THumanReadableAbi>, TEventName>,
ParseAbi<THumanReadableAbi>,
TStrict,
TFromBlock,
TToBlock,
TEventName,
MaybeExtractEventArgsFromAbi<TAbi, TEventName>
MaybeExtractEventArgsFromAbi<ParseAbi<THumanReadableAbi>, TEventName>
> & {
evmtsContractName: TName
eventName: TEventName
abi: [ExtractAbiEvent<TAbi, TEventName>]
abi: [ExtractAbiEvent<ParseAbi<THumanReadableAbi>, TEventName>]
}) & {
eventName: TEventName
abi: [ExtractAbiEvent<TAbi, TEventName>]
humanReadableAbi: FormatAbi<[ExtractAbiEvent<TAbi, TEventName>]>
humanReadableAbi: FormatAbi<
[ExtractAbiEvent<ParseAbi<THumanReadableAbi>, TEventName>]
>
abi: [ExtractAbiEvent<ParseAbi<THumanReadableAbi>, TEventName>]
}
}
32 changes: 16 additions & 16 deletions core/src/event/eventFactory.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { evmtsContractFactory } from '../evmtsContractFactory'
import { dummyAbi } from '../test/fixtures'
import { eventsFactory } from './eventFactory'
import { formatAbi } from 'abitype'
import { describe, expect, it } from 'vitest'

const contract = evmtsContractFactory({
abi: dummyAbi,
humanReadableAbi: formatAbi(dummyAbi),
name: 'DummyContract',
})

const dummyAbiNoEvent = dummyAbi.filter((abi) => abi.type !== 'event')

const contractNoEvent = evmtsContractFactory({
abi: dummyAbiNoEvent,
humanReadableAbi: formatAbi(dummyAbiNoEvent),
name: 'DummyContract',
})

Expand All @@ -30,20 +31,19 @@ describe(eventsFactory.name, () => {
expect(eventFilterParams.fromBlock).toMatchInlineSnapshot('"latest"')
expect(eventFilterParams.strict).toMatchInlineSnapshot('false')
expect(eventFilterParams.abi).toMatchInlineSnapshot(`
[
{
"inputs": [
{
"indexed": false,
"name": "data",
"type": "string",
},
],
"name": "exampleEvent",
"type": "event",
},
]
`)
[
{
"inputs": [
{
"name": "data",
"type": "string",
},
],
"name": "exampleEvent",
"type": "event",
},
]
`)
})

it('should return an empty object when the provided abi includes no events', () => {
Expand Down
5 changes: 3 additions & 2 deletions core/src/evmtsContractFactory.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { evmtsContractFactory } from './evmtsContractFactory'
import { dummyAbi } from './test/fixtures'
import { formatAbi, parseAbi } from 'abitype'
import { describe, expect, it } from 'vitest'

describe(evmtsContractFactory.name, () => {
const contract = evmtsContractFactory({
abi: dummyAbi,
humanReadableAbi: formatAbi(dummyAbi),
name: 'DummyContract',
})

Expand All @@ -13,7 +14,7 @@ describe(evmtsContractFactory.name, () => {
})

it('should contain the ABI', () => {
expect(contract.abi).toEqual(dummyAbi)
expect(contract.abi).toEqual(parseAbi(formatAbi(dummyAbi)))
})

it('should generate human readable ABI', () => {
Expand Down
23 changes: 13 additions & 10 deletions core/src/evmtsContractFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,26 @@ import type { EvmtsContract } from './EvmtsContract'
import { eventsFactory } from './event/eventFactory'
import { readFactory } from './read/readFactory'
import { writeFactory } from './write/writeFactory'
import type { Abi } from 'abitype'
import { formatAbi } from 'abitype'
import { parseAbi } from 'abitype'

export const evmtsContractFactory = <TName extends string, TAbi extends Abi>({
abi,
export const evmtsContractFactory = <
TName extends string,
THumanReadableAbi extends readonly string[],
>({
humanReadableAbi,
name,
}: Pick<EvmtsContract<TName, TAbi>, 'name' | 'abi'>): EvmtsContract<
TName,
TAbi
> => {
}: Pick<
EvmtsContract<TName, THumanReadableAbi>,
'name' | 'humanReadableAbi'
>): EvmtsContract<TName, THumanReadableAbi> => {
const abi = parseAbi(humanReadableAbi as any)
const methods = abi.filter((field) => {
return field.type === 'function'
})
return {
name,
abi,
humanReadableAbi: formatAbi(abi),
abi: abi as any,
humanReadableAbi,
// TODO make this more internally typesafe
events: eventsFactory({ abi }) as any,
// TODO make this more internally typesafe
Expand Down
3 changes: 3 additions & 0 deletions core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export * from './EvmtsContract'
export * from './evmtsContractFactory'
// reexported because they are needed to be able to
// instanciate an EvmtsContract with a json abi
export { formatAbi, parseAbi } from 'abitype'
Loading

1 comment on commit a1b10f2

@vercel
Copy link

@vercel vercel bot commented on a1b10f2 Sep 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

evmts-docs – ./

evmts-docs-evmts.vercel.app
evmts-docs-git-main-evmts.vercel.app
evmts.dev

Please sign in to comment.