Implement ethereum ABI encode and decode in subgraph (#103)

* Implement ethereum ABI encode in subgraph

* Implement ethereum ABI decoding host API

* Implement ABI encode decode for array type

* Implement ABI encode decode for bytes type
This commit is contained in:
2021-12-30 13:12:32 +05:30
committed by GitHub
parent 9b1aa29afd
commit 8b913af93f
11 changed files with 306 additions and 17 deletions
+1 -1
View File
@@ -71,7 +71,7 @@ describe('call handler in mapping code', () => {
db,
indexer,
provider,
{ event: { block: dummyEventData.block } },
{ block: dummyEventData.block },
filePath,
dummyGraphData
);
+3 -3
View File
@@ -88,7 +88,7 @@ describe('eden wasm loader tests', async () => {
db,
indexer,
provider,
{ event: { block: dummyEventData.block } },
{ block: dummyEventData.block },
filePath,
data
));
@@ -203,7 +203,7 @@ describe('eden wasm loader tests', async () => {
({ exports } = await instantiate(db,
indexer,
provider,
{ event: { block: dummyEventData.block } },
{ block: dummyEventData.block },
filePath,
data
));
@@ -316,7 +316,7 @@ describe('eden wasm loader tests', async () => {
db,
indexer,
provider,
{ event: { block: dummyEventData.block } },
{ block: dummyEventData.block },
filePath,
data
));
+70
View File
@@ -0,0 +1,70 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import path from 'path';
import { expect } from 'chai';
import { BaseProvider } from '@ethersproject/providers';
import { instantiate } from './loader';
import { getTestDatabase, getTestIndexer, getTestProvider } from '../test/utils';
import { Database } from './database';
import { Indexer } from '../test/utils/indexer';
describe('ethereum ABI encode decode', () => {
let exports: any;
let db: Database;
let indexer: Indexer;
let provider: BaseProvider;
let encoded: string;
before(async () => {
db = getTestDatabase();
indexer = getTestIndexer();
provider = getTestProvider();
});
it('should load the subgraph example wasm', async () => {
const filePath = path.resolve(__dirname, '../test/subgraph/example1/build/Example1/Example1.wasm');
const instance = await instantiate(
db,
indexer,
provider,
{},
filePath
);
exports = instance.exports;
const { _start } = exports;
// Important to call _start for built subgraphs on instantiation!
// TODO: Check api version https://github.com/graphprotocol/graph-node/blob/6098daa8955bdfac597cec87080af5449807e874/runtime/wasm/src/module/mod.rs#L533
_start();
});
it('should encode data', async () => {
const { testEthereumEncode, __getString } = exports;
const encodedPtr = await testEthereumEncode();
encoded = __getString(encodedPtr);
expect(encoded)
.to
.equal('0x0000000000000000000000000000000000000000000000000000000000000420583bc7e1bc4799a225663353b82eb36d925399e6ef2799a6a95909f5ab8ac945000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003f0000000000000000000000000000000000000000000000000000000000000001');
});
it('should decode data', async () => {
const { testEthereumDecode, __getString, __getArray, __newString } = exports;
const encodedString = await __newString(encoded);
const decodedArrayPtr = await testEthereumDecode(encodedString);
const decodedArray = __getArray(decodedArrayPtr);
const [addressString, bytesString, bigInt1String, bigInt2String, boolString] = decodedArray.map((value: any) => __getString(value));
expect(addressString).to.equal('0x0000000000000000000000000000000000000420');
expect(bytesString).to.equal('0x583bc7e1bc4799a225663353b82eb36d925399e6ef2799a6a95909f5ab8ac945');
expect(bigInt1String).to.equal('62');
expect(bigInt2String).to.equal('63');
expect(boolString).to.equal('true');
});
});
+1 -1
View File
@@ -49,7 +49,7 @@ describe('eth-call wasm tests', () => {
db,
indexer,
provider,
{ event: { block: dummyEventData.block } },
{ block: dummyEventData.block },
filePath,
data
);
+2 -2
View File
@@ -32,7 +32,7 @@ describe('wasm loader tests', () => {
db,
indexer,
provider,
{ event: {} },
{},
filePath
);
@@ -109,7 +109,7 @@ describe('wasm loader tests', () => {
db,
indexer,
provider,
{ event: {} },
{},
module
);
+24 -1
View File
@@ -23,7 +23,8 @@ import {
Block,
fromEthereumValue,
toEthereumValue,
resolveEntityFieldConflicts
resolveEntityFieldConflicts,
getEthereumTypes
} from './utils';
import { Database } from './database';
@@ -194,6 +195,27 @@ export const instantiate = async (
console.log('eth_call error', err);
return null;
}
},
'ethereum.encode': async (token: number) => {
const ethValue = await ethereum.Value.wrap(token);
const data = await fromEthereumValue(instanceExports, ethValue);
const type = await getEthereumTypes(instanceExports, ethValue);
const encoded = utils.defaultAbiCoder.encode([type], [data]);
const encodedString = await __newString(encoded);
return ByteArray.fromHexString(encodedString);
},
'ethereum.decode': async (types: number, data: number) => {
const typesString = __getString(types);
const byteArray = await ByteArray.wrap(data);
const bytesHex = await byteArray.toHex();
const dataString = __getString(bytesHex);
const [decoded] = utils.defaultAbiCoder.decode([typesString], dataString);
return toEthereumValue(instanceExports, utils.ParamType.from(typesString), decoded);
}
},
conversion: {
@@ -593,6 +615,7 @@ export const instantiate = async (
const Address: any = instanceExports.Address as any;
const ethereum: any = instanceExports.ethereum as any;
const Entity: any = instanceExports.Entity as any;
const ByteArray: any = instanceExports.ByteArray as any;
return instance;
};
+1 -1
View File
@@ -42,7 +42,7 @@ describe('numbers wasm tests', () => {
db,
indexer,
provider,
{ event: {} },
{},
filePath
);
exports = instance.exports;
@@ -31,7 +31,7 @@ describe('typeConversion wasm tests', () => {
db,
indexer,
provider,
{ event: {} },
{},
filePath
);
exports = instance.exports;
+132 -5
View File
@@ -63,6 +63,80 @@ export interface EventData {
eventIndex: number;
}
export const getEthereumTypes = async (instanceExports: any, value: any): Promise<any> => {
const {
__getArray,
Bytes,
ethereum
} = instanceExports;
const kind = await value.kind;
switch (kind) {
case EthereumValueKind.ADDRESS:
return 'address';
case EthereumValueKind.BOOL:
return 'bool';
case EthereumValueKind.STRING:
return 'string';
case EthereumValueKind.BYTES:
return 'bytes';
case EthereumValueKind.FIXED_BYTES: {
const bytesPtr = await value.toBytes();
const bytes = await Bytes.wrap(bytesPtr);
const length = await bytes.length;
return `bytes${length}`;
}
case EthereumValueKind.INT:
return 'int256';
case EthereumValueKind.UINT: {
return 'uint256';
}
case EthereumValueKind.ARRAY: {
const valuesPtr = await value.toArray();
const [firstValuePtr] = await __getArray(valuesPtr);
const firstValue = await ethereum.Value.wrap(firstValuePtr);
const type = await getEthereumTypes(instanceExports, firstValue);
return `${type}[]`;
}
case EthereumValueKind.FIXED_ARRAY: {
const valuesPtr = await value.toArray();
const values = await __getArray(valuesPtr);
const firstValue = await ethereum.Value.wrap(values[0]);
const type = await getEthereumTypes(instanceExports, firstValue);
return `${type}[${values.length}]`;
}
case EthereumValueKind.TUPLE: {
let values = await value.toTuple();
values = await __getArray(values);
const typePromises = values.map(async (value: any) => {
value = await ethereum.Value.wrap(value);
return getEthereumTypes(instanceExports, value);
});
const types = await Promise.all(typePromises);
return `tuple(${types.join(',')})`;
}
default:
break;
}
};
/**
* Method to get value from graph-ts ethereum.Value wasm instance.
* @param instanceExports
@@ -71,9 +145,12 @@ export interface EventData {
*/
export const fromEthereumValue = async (instanceExports: any, value: any): Promise<any> => {
const {
__getArray,
__getString,
BigInt,
Address
Address,
Bytes,
ethereum
} = instanceExports;
const kind = await value.kind;
@@ -91,9 +168,15 @@ export const fromEthereumValue = async (instanceExports: any, value: any): Promi
return Boolean(bool);
}
case EthereumValueKind.STRING: {
const stringPtr = await value.toString();
return __getString(stringPtr);
}
case EthereumValueKind.BYTES:
case EthereumValueKind.FIXED_BYTES: {
const bytes = await value.toBytes();
const bytesPtr = await value.toBytes();
const bytes = await Bytes.wrap(bytesPtr);
const bytesStringPtr = await bytes.toHexString();
return __getString(bytesStringPtr);
}
@@ -107,6 +190,31 @@ export const fromEthereumValue = async (instanceExports: any, value: any): Promi
return BigNumber.from(bigIntString);
}
case EthereumValueKind.ARRAY:
case EthereumValueKind.FIXED_ARRAY: {
const valuesPtr = await value.toArray();
const values = __getArray(valuesPtr);
const valuePromises = values.map(async (value: any) => {
value = await ethereum.Value.wrap(value);
return fromEthereumValue(instanceExports, value);
});
return Promise.all(valuePromises);
}
case EthereumValueKind.TUPLE: {
let values = await value.toTuple();
values = await __getArray(values);
const valuePromises = values.map(async (value: any) => {
value = await ethereum.Value.wrap(value);
return fromEthereumValue(instanceExports, value);
});
return Promise.all(valuePromises);
}
default:
break;
}
@@ -131,7 +239,26 @@ export const toEthereumValue = async (instanceExports: any, output: utils.ParamT
id_of_type: getIdOfType
} = instanceExports;
const { type } = output;
const { type, baseType, arrayChildren } = output;
// For array type.
if (baseType === 'array') {
const arrayEthereumValueId = await getIdOfType(TypeId.ArrayEthereumValue);
// Get values for array elements.
const ethereumValuePromises = value.map(
async (value: any) => toEthereumValue(
instanceExports,
arrayChildren,
value
)
);
const ethereumValues: any[] = await Promise.all(ethereumValuePromises);
const ethereumValuesArray = await __newArray(arrayEthereumValueId, ethereumValues);
return ethereum.Value.fromArray(ethereumValuesArray);
}
// For tuple type.
if (type === 'tuple') {
@@ -140,10 +267,10 @@ export const toEthereumValue = async (instanceExports: any, output: utils.ParamT
// Get values for struct elements.
const ethereumValuePromises = output.components
.map(
async (component: utils.ParamType) => toEthereumValue(
async (component: utils.ParamType, index) => toEthereumValue(
instanceExports,
component,
value[component.name]
value[index]
)
);