Skip to content

Commit

Permalink
feat!(ethereum): make it work
Browse files Browse the repository at this point in the history
  • Loading branch information
denizenging committed Sep 21, 2023
1 parent f48adb5 commit 70b596d
Show file tree
Hide file tree
Showing 7 changed files with 892 additions and 71 deletions.
22 changes: 18 additions & 4 deletions pkg/ethereum/src/lexer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import { vocab } from '@slangroom/ethereum/tokens';
import { ethereumEndpointVocab, ethereumBalanceVocab } from '@slangroom/ethereum/tokens';
import { Lexer } from '@slangroom/deps/chevrotain';

const FsLexer = new Lexer(vocab);
const EthereumEndpointLexer = new Lexer(ethereumEndpointVocab);
const EthereumBalanceLexer = new Lexer(ethereumBalanceVocab);

/**
* Lexes the given statement for filesystems statements.
* Lexes the given statement for Ethereum endpoint statements:
*
* Given I have a ethereum endpoint named 'x'
*
* @param statement is the statement ignored by Zenroom.
* @returns the tokens of the lexed result.
**/
export const lex = (statement: string) => FsLexer.tokenize(statement);
export const lexEthereumEndpoint = (statement: string) => EthereumEndpointLexer.tokenize(statement);

/**
* Lexes the given statement for reading the Ethereum balance:
*
* Given I read the ethereum balance for 'x'
*
* @param statement is the statement ignored by Zenroom.
* @returns the tokens of the lexed result.
**/

export const lexEthereumBalance = (statement: string) => EthereumBalanceLexer.tokenize(statement);
60 changes: 41 additions & 19 deletions pkg/ethereum/src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { EthereumEndpoint, EthereumBalance, vocab } from '@slangroom/ethereum/tokens';
import { lex } from '@slangroom/ethereum/lexer';
import {
ethereumEndpointVocab,
ethereumBalanceVocab,
Ethereum,
Endpoint,
Balance,
} from '@slangroom/ethereum/tokens';
import { lexEthereumEndpoint, lexEthereumBalance } 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';
import { Given, I, Identifier, Have, A, Named, For, The, Read } from '@slangroom/shared/tokens';

export type EthereumEndpointStatementCtx = {
Given: [IToken];
I: [IToken];
HaveA: [IToken];
EthereumEndpoint: [IToken];
Have: [IToken];
A: [IToken];
Ethereum: [IToken];
Endpoint: [IToken];
Named: [IToken];
endpointName: [IToken];
};
Expand All @@ -17,39 +25,53 @@ export type EthereumReadBalanceStatementCtx = {
I: [IToken];
Read: [IToken];
The: [IToken];
EthereumBalance: [IToken];
Ethereum: [IToken];
Balance: [IToken];
For: [IToken];
addressName: [IToken];
};

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

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

class EthereumBalanceParser extends CstParser {
constructor() {
super(ethereumBalanceVocab);
this.performSelfAnalysis();
}

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

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

export const ethereumBalanceParser = new EthereumBalanceParser();
export const BaseEthereumBalanceParser = ethereumBalanceParser.getBaseCstVisitorConstructor();

/**
* Parses the given statement for ethereum statements.
Expand All @@ -58,13 +80,13 @@ export const BaseEthVisitor = EthParser.getBaseCstVisitorConstructor();
* @returns the CST of the lexed statement.
**/
export const parseEthereumEndpointStatement = (statement: string) => {
const lexed = lex(statement);
EthParser.input = lexed.tokens;
return EthParser.ethereumEndpointStatement();
const lexed = lexEthereumEndpoint(statement);
ethereumEndpointParser.input = lexed.tokens;
return ethereumEndpointParser.ethereumEndpointStatement();
};

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

let web3: Web3 | null = null
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)
if (!endpointName) return;

const endpoint = ((params?.data || {})[endpointName] || (params?.keys || {})[endpointName] || endpointName) as string;
web3 = new Web3(endpoint);
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)
if (!addressName) return;

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

return { ethereum_balance: balance.toString() };
});
export const allPlugins = new Set([GivenIHaveAEthereumEndpointNamed]);

export const allPlugins = new Set([
GivenIHaveAEthereumEndpointNamed,
GivenIReadTheEhtereumBalanceFor,
]);
71 changes: 59 additions & 12 deletions pkg/ethereum/src/tokens.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,66 @@
import { Whitespace, Comment, Identifier, Given, I, Named, HaveA } from '@slangroom/shared';
import {
Whitespace,
Comment,
Identifier,
Given,
I,
Named,
For,
The,
Have,
Read,
A,
} 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 Ethereum = createToken({
name: 'Ethereum',
pattern: /ethereum/i,
});

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

export const EthereumBalance = createToken({
name: 'EthereumBalance',
pattern: /ethereum balance/,
export const Balance = createToken({
name: 'Balance',
pattern: /balance/i,
});

/**
* The vocabulary for the statements that specifies endpoints for Ethereum:
*
* Given I have a ethereum endpoint named 'x'
*/
export const ethereumEndpointVocab = [
Whitespace,
Comment,
Given,
I,
Have,
A,
Ethereum,
Endpoint,
Named,
Identifier,
];

/**
* Vocabulary to perform filesystems actions.
* The vocabulary for statements that reads the balance of a given Ethereum
* address:
*
* Given I read the ethereum balance for 'x'
*/
export const vocab = [Whitespace, Comment, Given, I, Identifier, EthereumEndpoint, EthereumBalance, Named, HaveA];
export const ethereumBalanceVocab = [
Whitespace,
Comment,
Given,
I,
Read,
The,
Ethereum,
Balance,
For,
Identifier,
];
48 changes: 31 additions & 17 deletions pkg/ethereum/src/visitor.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,55 @@
import { BaseEthVisitor, parseEthereumReadBalanceStatement, parseEthereumEndpointStatement,
type EthereumEndpointStatementCtx, type EthereumReadBalanceStatementCtx } from '@slangroom/ethereum/parser';
import {
BaseEthereumEndpointParser,
BaseEthereumBalanceParser,
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;
interface EthereumBalanceVisitor {
visit(params: CstNode): string;
}

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

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

export const ethereumEndpointVisitor = new EthereumEndpointVisitor();

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

class EthereumBalanceVisitor extends BaseEthereumBalanceParser {
constructor() {
super();
this.validateVisitor();
}

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

export const EthVisitor = new Visitor();
export const ethereumBalanceVisitor = new EthereumBalanceVisitor();

/**
* 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);
return ethereumEndpointVisitor.visit(cst);
};

export const visitEthereumReadBalanceStatement = (statement: string) => {
const cst = parseEthereumReadBalanceStatement(statement);
return EthVisitor.visitEthereumReadBalance(cst);
return ethereumBalanceVisitor.visit(cst);
};
13 changes: 9 additions & 4 deletions pkg/ethereum/test/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@ test('Read the balance of an array of addresses', async (t) => {
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 ethereum endpoint named 'endpoint'
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 params = {
data: {
endpoint: 'http://78.47.38.223:9485',
my_address: '0x2D010920b43aFb54f8d5fB51c9354FbC674b28Fc',
},
};
const zout = await slang.execute(contract, params);
const ethereumBalance = zout.result['ethereum_balance'] as string;
t.is(ethereumBalance, '1000000000000000000000')
t.is(ethereumBalance, '1000000000000000000000');
}
});
Loading

0 comments on commit 70b596d

Please sign in to comment.