hydra icon indicating copy to clipboard operation
hydra copied to clipboard

Initial work on EVM type generation

Open bedeho opened this issue 5 years ago • 3 comments

Background

We currently expect that the Hydra tooling will be expanded to allow developers wanting to write APIs for Smart Contracts running on the EVM (on Substrate) to write their event and method handlers in a type safe way. This will likely be facilitated by making Hydra itself work with plugins that can run custom code generation code at the time of setup.

Goal

It is still not totally nailed down exactly how Hydra will run plugins, hence the interface between Hydra and the EVM plugin described above is not available at this time. However, the fundamental task of being able to generate Typescript types for methods and events using the ABI of a contract on the Substrate EVM can still be tackled right now.

In fact, @Lezek123 has already provided some initial guidance on how this could work in the issue https://github.com/Joystream/joystream/issues/1816 under the section Query node (and general event/metadata handling).

Using this as a starting point it should be possible to come up with the internal parts of the future plugin.

Specifically, what is needed is some Typescript library that can take

  • a list of event names
  • a list of method names
  • a Solidity ABI for a contract

and can spit out a set of Typescript types modeling these.

Remember unit tests with example contract.

bedeho avatar Dec 04 '20 12:12 bedeho

Initial attempt

Here is my initial attempt for generation evm types.

Type generation

I use typechain library to generate typescript types from contract ABI. As an example I took ChannelCreated event definition from joystream content directory smart contracts which looks like this:

event ChannelCreated(uint64 _id, string[][] _metadata)

After type generation we will have a typescript interface for the ChannelCreated event which will look like this:

export interface ChannelCreated {
  name: "ChannelCreated";
  args: {
    _id: BN;
    _metadata: string[][];
    0: BN;
    1: string[][];
  };
}

So we can say typechain generate all the necessary types for a given contract ABI. I am not sure though but the reason that arguments are repeated as indexed is because of nameless solidity event arguments.

Decode evm log

I use abi-decoder library to decode the evm.Log event, this library uses contract ABI to decode the event. I deployed a sample contract with the ChannelCreated event on the moonbeam node and I did some transactions to emit the event. As a result, I got the following emitted evm.Log event which is hex-encoded:

{
  "address": "0x63a1519ee99d1121780fffa1726ed2ecc6d1611b",
  "topics": ["0xcd88e06db770600c5e0d9519f3bed66b1b58a7ddbe316178e0385fad0bb4fc10"],
  "data":
   "0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000668616e646c650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4d79206e6577206368616e6e656c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000b6465736372697074696f6e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010536f6d652066756e6e7920737475666600000000000000000000000000000000",
}

Here is the decoded ChannelCreated evm.Log event:

{
  name: 'ChannelCreated',
  events: [
    { name: '_id', type: 'uint64', value: '1' },
    { name: '_metadata', type: 'string[2][]', value: [Array] }
  ],
  address: '0x63a1519ee99d1121780fffa1726ed2ecc6d1611b'
}

Type assertion

As a final step, I create the following object from the decoded event which is fine type do type assertion:

// TODO: field validation; make sure all the fields exist!

{
  name: 'ChannelCreated',
  args: {
    0: '1',
    1: [
      ['handle', 'My new channel'],
      ['description', 'Some funny stuff'],
    ],
    _id: '1',
    _metadata: [
      ['handle', 'My new channel'],
      ['description', 'Some funny stuff'],
    ],
  },
} as ChannelCreated

Mappings

The event handler for ChannelCreated event probably would look like this:

async function handleChannelCreated(db: DB, event: ChannelCreated) {
  const channel = new Channel()
  channel.id = event.args._id
  channel.handle = event.args._metadata[0]
  channel.description = event.args._metadata[1]

  await db.save<Channel>(channel)
}

metmirr avatar Dec 17 '20 19:12 metmirr

Thanks for the write-up! Overall the data transformation pipeline seems clear.

I am curious about the details on how the data transformation from SubstrateEvent to ChannelCreated is going to look like. TheGraph does it like this, but in our case, we don't have EthereumEvent interface ready.

dzhelezov avatar Dec 18 '20 09:12 dzhelezov

Looks like a great start!

  1. Correct me if I am wrong but is the Type assertion step currently manual? Seems like getting that to work manually would be very productive next step.

  2. Can you show an example of this for contract method invocations? or have you not tried that?

bedeho avatar Dec 18 '20 09:12 bedeho