Skip to content

Commit

Permalink
begin ethereum plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
albertolerda committed Sep 20, 2023
1 parent d977e93 commit ff16296
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 0 deletions.
1 change: 1 addition & 0 deletions pkg/ethereum/.npmignore
38 changes: 38 additions & 0 deletions pkg/ethereum/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@slangroom/ethereum",
"version": "1.0.3",
"dependencies": {
"@slangroom/core": "workspace:*",
"@slangroom/deps": "workspace:*",
"@slangroom/ignored": "workspace:*",
"@slangroom/shared": "workspace:*",
"web3": "^4.1.2"
},
"repository": "https://github.com/dyne/slangroom",
"license": "AGPL-3.0-only",
"type": "module",
"main": "./build/cjs/src/index.js",
"types": "./build/cjs/src/index.d.ts",
"exports": {
".": {
"import": {
"types": "./build/esm/src/index.d.ts",
"default": "./build/esm/src/index.js"
},
"require": {
"types": "./build/cjs/src/index.d.ts",
"default": "./build/cjs/src/index.js"
}
},
"./*": {
"import": {
"types": "./build/esm/src/*.d.ts",
"default": "./build/esm/src/*.js"
},
"require": {
"types": "./build/cjs/src/*.d.ts",
"default": "./build/cjs/src/*.js"
}
}
}
}
4 changes: 4 additions & 0 deletions pkg/ethereum/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from '@slangroom/ethereum/lexer';
export * from '@slangroom/ethereum/parser';
export * from '@slangroom/ethereum/tokens';
export * from '@slangroom/ethereum/visitor';
12 changes: 12 additions & 0 deletions pkg/ethereum/src/lexer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { vocab } from '@slangroom/ethereum/tokens';
import { Lexer } from '@slangroom/deps/chevrotain';

const FsLexer = new Lexer(vocab);

/**
* Lexes the given statement for filesystems statements.
*
* @param statement is the statement ignored by Zenroom.
* @returns the tokens of the lexed result.
**/
export const lex = (statement: string) => FsLexer.tokenize(statement);
70 changes: 70 additions & 0 deletions pkg/ethereum/src/parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { EthereumEndpoint, EthereumBalance, vocab } from '@slangroom/ethereum/tokens';
import { lex } from '@slangroom/ethereum/lexer';
import { CstParser, type IToken } from '@slangroom/deps/chevrotain';
import { Given, I, Identifier, HaveA, Named, For, The, Read } from '@slangroom/shared/tokens';

export type EthereumEndpointStatementCtx = {
Given: [IToken];
I: [IToken];
HaveA: [IToken];
EthereumEndpoint: [IToken];
Named: [IToken];
endpointName: [IToken];
};

export type EthereumReadBalanceStatementCtx = {
Given: [IToken];
I: [IToken];
Read: [IToken];
The: [IToken];
EthereumBalance: [IToken];
For: [IToken];
addressName: [IToken];
};

class Parser extends CstParser {
constructor() {
super(vocab);
this.performSelfAnalysis();
}

ethereumEndpointStatement = this.RULE('ethereumEndpointStatement', () => {
this.CONSUME(Given);
this.CONSUME(I);
this.CONSUME(HaveA);
this.CONSUME(EthereumEndpoint);
this.CONSUME(Named);
this.CONSUME(Identifier, { LABEL: 'endpointName' });
});

ethereumReadBalanceStatement = this.RULE('ethereumReadBalanceStatement', () => {
this.CONSUME(Given);
this.CONSUME(I);
this.CONSUME(Read);
this.CONSUME(The);
this.CONSUME(EthereumBalance);
this.CONSUME(For);
this.CONSUME(Identifier, { LABEL: 'addressName' });
});
}

export const EthParser = new Parser();
export const BaseEthVisitor = EthParser.getBaseCstVisitorConstructor();

/**
* Parses the given statement for ethereum statements.
*
* @param statement is the statement ignored by Zenroom.
* @returns the CST of the lexed statement.
**/
export const parseEthereumEndpointStatement = (statement: string) => {
const lexed = lex(statement);
EthParser.input = lexed.tokens;
return EthParser.ethereumEndpointStatement();
};

export const parseEthereumReadBalanceStatement = (statement: string) => {
const lexed = lex(statement);
EthParser.input = lexed.tokens;
return EthParser.ethereumReadBalanceStatement();
};
26 changes: 26 additions & 0 deletions pkg/ethereum/src/plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { visitEthereumEndpointStatement, visitEthereumReadBalanceStatement } from '@slangroom/ethereum/visitor';
import { BeforePlugin } from '@slangroom/core/plugin';
import { Web3 } from 'web3'

let web3: Web3 | null = null

export const GivenIHaveAEthereumEndpointNamed = new BeforePlugin(async ({ statement, params }) => {
const endpointName = visitEthereumEndpointStatement(statement);
console.log(endpointName)
// TODO: if `visit()` fails, exit (return)

const endpoint = ((params?.data || {})[endpointName] || (params?.keys || {})[endpointName] || endpointName) as string;
web3 = new Web3(endpoint);
});

export const GivenIReadTheEhtereumBalanceFor = new BeforePlugin(async ({ statement, params }) => {
const addressName = visitEthereumReadBalanceStatement(statement);
// TODO: if `visit()` fails, exit (return)

const address = ((params?.data || {})[addressName] || (params?.keys || {})[addressName] || addressName) as string;
if(web3 == null) throw Error("No connection to a client")
const balance = await web3.eth.getBalance(address);
return { ethereum_balance: balance.toString() }

});
export const allPlugins = new Set([GivenIHaveAEthereumEndpointNamed]);
19 changes: 19 additions & 0 deletions pkg/ethereum/src/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Whitespace, Comment, Identifier, Given, I, Named, HaveA } from '@slangroom/shared';
import { createToken } from '@slangroom/deps/chevrotain';

/**
* The "save the" statement, used to write files to the filesystems.
*/
export const EthereumEndpoint = createToken({
name: 'EthereumEndpoint',
pattern: /ethereum endpoint/,
});

export const EthereumBalance = createToken({
name: 'EthereumBalance',
pattern: /ethereum balance/,
});
/**
* Vocabulary to perform filesystems actions.
*/
export const vocab = [Whitespace, Comment, Given, I, Identifier, EthereumEndpoint, EthereumBalance, Named, HaveA];
41 changes: 41 additions & 0 deletions pkg/ethereum/src/visitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { BaseEthVisitor, parseEthereumReadBalanceStatement, parseEthereumEndpointStatement,
type EthereumEndpointStatementCtx, type EthereumReadBalanceStatementCtx } from '@slangroom/ethereum/parser';
import type { CstNode } from '@slangroom/deps/chevrotain';

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface Visitor {
visitEthereumEndpointConnect(params: CstNode): string;
visitEthereumReadBalance(params: CstNode): string;
}

class Visitor extends BaseEthVisitor {
constructor() {
super();
this.validateVisitor();
}

ethereumEndpointStatement(ctx: EthereumEndpointStatementCtx) {
return ctx.endpointName[0].image.slice(1, -1)
}
ethereumReadBalanceStatement(ctx: EthereumReadBalanceStatementCtx) {
return ctx.addressName[0].image.slice(1, -1)
}
}

export const EthVisitor = new Visitor();

/**
* Visits the given statement for filesystems statements.
*
* @param statement is the statement ignored by Zenroom.
* @returns the AST of the parsed CST.
**/
export const visitEthereumEndpointStatement = (statement: string) => {
const cst = parseEthereumEndpointStatement(statement);
return EthVisitor.visitEthereumEndpointConnect(cst);
};

export const visitEthereumReadBalanceStatement = (statement: string) => {
const cst = parseEthereumReadBalanceStatement(statement);
return EthVisitor.visitEthereumReadBalance(cst);
};
23 changes: 23 additions & 0 deletions pkg/ethereum/test/plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import test from 'ava';
import { allPlugins } from '@slangroom/ethereum/plugins';
import { Slangroom } from '@slangroom/core';

test('Read the balance of an array of addresses', async (t) => {
const slang = new Slangroom(allPlugins);
const contract = `Rule unknown ignore
Scenario ethereum
Given I have a ethereum endpoint named 'fabchain'
Given I read the ethereum balance for 'my_address'
Given I have a 'wei value' named 'ethereum balance'
Then print the 'ethereum balance' as 'wei value'
`;
{
const params = { data: { endpoint: 'http://78.47.38.223:9485', my_address: '2D010920b43aFb54f8d5fB51c9354FbC674b28Fc' } };
const zout = await slang.execute(contract, params);
const ethereumBalance = zout.result['ethereum_balance'] as string;
t.is(ethereumBalance, '1000000000000000000000')
}
});
1 change: 1 addition & 0 deletions pkg/ethereum/tsconfig.json
25 changes: 25 additions & 0 deletions pkg/shared/src/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,28 @@ export const Identifier = createToken({
name: 'Identifier',
pattern: /'(?:[^\\']|\\(?:[bfnrtv'\\/]|u[0-9a-fA-F]{4}))*'/,
});

export const Named = createToken({
name: 'Named',
pattern: /named/i,
});

export const HaveA = createToken({
name: 'HaveA',
pattern: /have (a|an)/i,
});

export const The = createToken({
name: 'The',
pattern: /the/i,
});

export const For = createToken({
name: 'For',
pattern: /for/i,
});

export const Read = createToken({
name: 'Read',
pattern: /read/i,
});

0 comments on commit ff16296

Please sign in to comment.