Initial work on EVM type generation
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.
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)
}
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.
Looks like a great start!
-
Correct me if I am wrong but is the
Type assertionstep currently manual? Seems like getting that to work manually would be very productive next step. -
Can you show an example of this for contract method invocations? or have you not tried that?