Example subgraph, ethCall test to drive implementation (#18)

* Setup example subgraph.

* Implement eth_call in subgraph.

* eth-call test stub

Co-authored-by: nabarun <nabarun@deepstacksoft.com>
This commit is contained in:
Ashwin Phatak 2021-09-28 12:28:43 +05:30 committed by nabarun
parent f078db688a
commit d247815ce2
13 changed files with 3270 additions and 81 deletions

View File

@ -3,3 +3,6 @@ node_modules
# Don't lint build output.
dist
# Don't lint generated code from graph-cli.
generated

View File

@ -0,0 +1,27 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import path from 'path';
import { instantiate } from './index';
describe('eth-call wasm tests', () => {
let exports: any;
it('should load the subgraph example wasm', async () => {
const filePath = path.resolve(__dirname, '../test/subgraph/example1/build/Example1/Example1.wasm');
const instance = await instantiate(filePath);
exports = instance.exports;
});
it('should execute exported function', () => {
const { _start, testEthCall } = 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();
testEthCall();
});
});

View File

@ -36,10 +36,10 @@ describe('wasm loader tests', () => {
it('should instantiate a class in wasm from JS', async () => {
const { Foo, FooID, __getString, __new, __pin, __unpin } = exports;
const newFooPtr = __pin(__new(FooID));
const newFoo = Foo.wrap(newFooPtr);
const newStrPtr = newFoo.getString();
expect(__getString(newStrPtr)).to.equal('hello world!');
__unpin(newFooPtr);
const fooPtr = __pin(__new(FooID));
const foo = Foo.wrap(fooPtr);
const strPtr = foo.getString();
expect(__getString(strPtr)).to.equal('hello world!');
__unpin(fooPtr);
});
});

View File

@ -5,82 +5,94 @@
import fs from 'fs/promises';
import loader from '@assemblyscript/loader';
const imports = {
index: {
'store.get': () => {
console.log('store.get');
},
'store.set': () => {
console.log('store.set');
},
'typeConversion.stringToH160': () => {
console.log('typeConversion.stringToH160');
},
'typeConversion.bytesToHex': () => {
console.log('typeConversion.bytesToHex');
},
// 'typeConversion.bytesToString': () => {
// console.log('typeConversion.bytesToString');
// },
'typeConversion.bigIntToString': () => {
console.log('typeConversion.bigIntToString');
},
// 'bigDecimal.fromString': () => {
// console.log('bigDecimal.fromString');
// },
// 'bigDecimal.times': () => {
// console.log('bigDecimal.times');
// },
'bigDecimal.dividedBy': () => {
console.log('bigDecimal.dividedBy');
},
// 'bigDecimal.plus': () => {
// console.log('bigDecimal.plus');
// },
// 'bigDecimal.minus': () => {
// console.log('bigDecimal.minus');
// },
'bigInt.plus': () => {
console.log('bigInt.plus');
},
'bigInt.minus': () => {
console.log('bigInt.minus');
},
'bigInt.times': () => {
console.log('bigInt.times');
},
'bigInt.dividedBy': () => {
console.log('bigInt.dividedBy');
},
// 'bigInt.mod': () => {
// console.log('bigInt.mod');
// },
'bigInt.fromString': () => {
console.log('bigInt.fromString');
},
'log.log': () => {
console.log('log.log');
},
// 'dataSource.create': () => {
// console.log('dataSource.create');
// },
'dataSource.address': () => {
console.log('dataSource.address');
}
},
ethereum: {
'ethereum.call': () => {
console.log('ethereum.call');
}
}
};
export const instantiate = async (filePath: string): Promise<loader.ResultObject & { exports: any }> => {
const buffer = await fs.readFile(filePath);
return loader.instantiate(buffer, imports);
const imports = {
index: {
'store.get': () => {
console.log('store.get');
},
'store.set': () => {
console.log('store.set');
},
'typeConversion.stringToH160': () => {
console.log('typeConversion.stringToH160');
},
'typeConversion.bytesToHex': () => {
console.log('typeConversion.bytesToHex');
},
// 'typeConversion.bytesToString': () => {
// console.log('typeConversion.bytesToString');
// },
'typeConversion.bigIntToString': () => {
console.log('typeConversion.bigIntToString');
},
// 'bigDecimal.fromString': () => {
// console.log('bigDecimal.fromString');
// },
// 'bigDecimal.times': () => {
// console.log('bigDecimal.times');
// },
'bigDecimal.dividedBy': () => {
console.log('bigDecimal.dividedBy');
},
// 'bigDecimal.plus': () => {
// console.log('bigDecimal.plus');
// },
// 'bigDecimal.minus': () => {
// console.log('bigDecimal.minus');
// },
'bigInt.plus': () => {
console.log('bigInt.plus');
},
'bigInt.minus': () => {
console.log('bigInt.minus');
},
'bigInt.times': () => {
console.log('bigInt.times');
},
'bigInt.dividedBy': () => {
console.log('bigInt.dividedBy');
},
// 'bigInt.mod': () => {
// console.log('bigInt.mod');
// },
'bigInt.fromString': () => {
console.log('bigInt.fromString');
},
'log.log': (_: number, msg: number) => {
console.log('console.log', __getString(msg));
},
// 'dataSource.create': () => {
// console.log('dataSource.create');
// },
'dataSource.address': () => {
console.log('dataSource.address');
}
},
ethereum: {
'ethereum.call': () => {
console.log('ethereum.call');
return null;
}
},
conversion: {
'typeConversion.stringToH160': () => {
console.log('typeConversion.stringToH160');
}
}
};
const instance = await loader.instantiate(buffer, imports);
const exports = instance.exports;
const { __getString } = exports;
return instance;
};

View File

@ -0,0 +1,10 @@
pragma solidity >=0.4.22 <0.8.0;
contract Example {
event Test(string param1, uint param2);
function getMethod() public view virtual returns (string memory)
{
return 'test';
}
}

View File

@ -0,0 +1,34 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "string",
"name": "param1",
"type": "string"
},
{
"indexed": false,
"internalType": "uint256",
"name": "param2",
"type": "uint256"
}
],
"name": "Test",
"type": "event"
},
{
"inputs": [],
"name": "getMethod",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
]

View File

@ -0,0 +1,54 @@
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
import {
ethereum,
JSONValue,
TypedMap,
Entity,
Bytes,
Address,
BigInt
} from "@graphprotocol/graph-ts";
export class Test extends ethereum.Event {
get params(): Test__Params {
return new Test__Params(this);
}
}
export class Test__Params {
_event: Test;
constructor(event: Test) {
this._event = event;
}
get param1(): string {
return this._event.parameters[0].value.toString();
}
get param2(): BigInt {
return this._event.parameters[1].value.toBigInt();
}
}
export class Example1 extends ethereum.SmartContract {
static bind(address: Address): Example1 {
return new Example1("Example1", address);
}
getMethod(): string {
let result = super.call("getMethod", "getMethod():(string)", []);
return result[0].toString();
}
try_getMethod(): ethereum.CallResult<string> {
let result = super.tryCall("getMethod", "getMethod():(string)", []);
if (result.reverted) {
return new ethereum.CallResult();
}
let value = result.value;
return ethereum.CallResult.fromValue(value[0].toString());
}
}

View File

@ -0,0 +1,77 @@
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
import {
TypedMap,
Entity,
Value,
ValueKind,
store,
Address,
Bytes,
BigInt,
BigDecimal
} from "@graphprotocol/graph-ts";
export class ExampleEntity extends Entity {
constructor(id: string) {
super();
this.set("id", Value.fromString(id));
this.set("count", Value.fromBigInt(BigInt.zero()));
this.set("param1", Value.fromString(""));
this.set("param2", Value.fromBigInt(BigInt.zero()));
}
save(): void {
let id = this.get("id");
assert(id != null, "Cannot save ExampleEntity entity without an ID");
if (id) {
assert(
id.kind == ValueKind.STRING,
"Cannot save ExampleEntity entity with non-string ID. " +
'Considering using .toHex() to convert the "id" to a string.'
);
store.set("ExampleEntity", id.toString(), this);
}
}
static load(id: string): ExampleEntity | null {
return changetype<ExampleEntity | null>(store.get("ExampleEntity", id));
}
get id(): string {
let value = this.get("id");
return value!.toString();
}
set id(value: string) {
this.set("id", Value.fromString(value));
}
get count(): BigInt {
let value = this.get("count");
return value!.toBigInt();
}
set count(value: BigInt) {
this.set("count", Value.fromBigInt(value));
}
get param1(): string {
let value = this.get("param1");
return value!.toString();
}
set param1(value: string) {
this.set("param1", Value.fromString(value));
}
get param2(): BigInt {
let value = this.get("param2");
return value!.toBigInt();
}
set param2(value: BigInt) {
this.set("param2", Value.fromBigInt(value));
}
}

View File

@ -0,0 +1,16 @@
{
"name": "example1",
"license": "UNLICENSED",
"scripts": {
"codegen": "graph codegen",
"build": "graph build",
"deploy": "graph deploy --node https://api.studio.thegraph.com/deploy/ example1",
"create-local": "graph create --node http://localhost:8020/ example1",
"remove-local": "graph remove --node http://localhost:8020/ example1",
"deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 example1"
},
"dependencies": {
"@graphprotocol/graph-cli": "0.22.0",
"@graphprotocol/graph-ts": "0.22.0"
}
}

View File

@ -0,0 +1,6 @@
type ExampleEntity @entity {
id: ID!
count: BigInt!
param1: String! # string
param2: BigInt! # uint256
}

View File

@ -0,0 +1,64 @@
import { Address, log } from '@graphprotocol/graph-ts';
import {
Example1
} from '../generated/Example1/Example1';
// export function handleTest (event: Test): void {
// // Entities can be loaded from the store using a string ID; this ID
// // needs to be unique across all entities of the same type
// let entity = ExampleEntity.load(event.transaction.from.toHex());
// // Entities only exist after they have been saved to the store;
// // `null` checks allow to create entities on demand
// if (!entity) {
// entity = new ExampleEntity(event.transaction.from.toHex());
// // Entity fields can be set using simple assignments
// entity.count = BigInt.fromI32(0);
// }
// // BigInt and BigDecimal math are supported
// // entity.count = entity.count + BigInt.fromI32(1)
// // Entity fields can be set based on event parameters
// entity.param1 = event.params.param1;
// entity.param2 = event.params.param2;
// // Entities can be written to the store with `.save()`
// entity.save();
// // Note: If a handler doesn't require existing field values, it is faster
// // _not_ to load the entity from the store. Instead, create it fresh with
// // `new Entity(...)`, set the fields that should be updated and save the
// // entity back to the store. Fields that were not set or unset remain
// // unchanged, allowing for partial updates to be applied.
// // It is also possible to access smart contracts from mappings. For
// // example, the contract that has emitted the event can be connected to
// // with:
// //
// // let contract = Contract.bind(event.address)
// //
// // The following functions can then be called on this contract to access
// // state variables and other data:
// //
// // - contract.getMethod(...)
// }
export function testEthCall (): void {
log.debug('In test eth call', []);
// Bind the contract to the address that emitted the event.
// TODO: Address.fromString throws error in WASM module instantiation.
const contractAddress = Address.fromString('0xafAd925B5eAE1E370196cBa39893E858ff7257d5');
const contract = Example1.bind(contractAddress);
// Access functions by calling them.
const res = contract.try_getMethod();
if (res.reverted) {
log.debug('Contract eth call reverted', []);
} else {
log.debug('Contract eth call result', []);
}
}

View File

@ -0,0 +1,23 @@
specVersion: 0.0.2
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: Example1
network: mainnet
source:
address: "0xafAd925B5eAE1E370196cBa39893E858ff7257d5"
abi: Example1
mapping:
kind: ethereum/events
apiVersion: 0.0.5
language: wasm/assemblyscript
entities:
- Test
abis:
- name: Example1
file: ./abis/Example1.json
eventHandlers:
- event: Test(string,uint256)
handler: handleTest
file: ./src/mapping.ts

File diff suppressed because it is too large Load Diff