Skip to content

Commit

Permalink
Merge pull request #170 from algorand-devrel/feat/events
Browse files Browse the repository at this point in the history
feat/events
  • Loading branch information
joe-p authored Oct 17, 2023
2 parents e74dec7 + 373bbcf commit 9318b69
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 6 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@microsoft/tsdoc": "^0.14.2",
"argparse": "^2.0.1",
"dotenv": "^16.3.1",
"js-sha512": "^0.8.0",
"node-fetch": "2",
"source-map": "^0.7.4",
"ts-morph": "^20.0.0",
Expand Down
50 changes: 46 additions & 4 deletions src/lib/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import * as tsdoc from '@microsoft/tsdoc';
import { Project } from 'ts-morph';
import path from 'path';
import { readFileSync } from 'fs';
// eslint-disable-next-line camelcase
import { sha512_256 } from 'js-sha512';
import langspec from '../langspec.json';
import { VERSION } from '../version';
import { optimizeTeal } from './optimize';
Expand Down Expand Up @@ -1098,6 +1100,8 @@ export default class Compiler {

private tealscriptImport!: string;

private events: Record<string, string[]> = {};

constructor(
content: string,
className: string,
Expand Down Expand Up @@ -3648,12 +3652,14 @@ export default class Compiler {
}

private processPropertyDefinition(node: ts.PropertyDeclaration) {
if (node.initializer === undefined || !ts.isCallExpression(node.initializer)) throw new Error();

const klass = node.initializer.expression.getText();
if (node.initializer === undefined) throw Error();

if (['BoxMap', 'GlobalStateMap', 'LocalStateMap', 'BoxKey', 'GlobalStateKey', 'LocalStateKey'].includes(klass)) {
if (
ts.isCallExpression(node.initializer)
&& ['BoxMap', 'GlobalStateMap', 'LocalStateMap', 'BoxKey', 'GlobalStateKey', 'LocalStateKey'].includes(node.initializer.expression.getText())
) {
let props: StorageProp;
const klass = node.initializer.expression.getText();
const type = klass.toLocaleLowerCase().replace('state', '').replace('map', '').replace('key', '') as StorageType;
const typeArgs = node.initializer.typeArguments;

Expand Down Expand Up @@ -3730,6 +3736,11 @@ export default class Compiler {
}

this.storageProps[node.name.getText()] = props;
} else if (ts.isNewExpression(node.initializer) && node.initializer.expression.getText() === 'EventLogger') {
if (!ts.isTupleTypeNode(node.initializer.typeArguments![0])) throw Error();

this.events[node.name.getText()] = node
.initializer.typeArguments![0]!.elements.map((t) => t.getText()) || [];
} else {
throw new Error();
}
Expand Down Expand Up @@ -3827,6 +3838,37 @@ export default class Compiler {
return;
}

// If this is an event
if (
ts.isPropertyAccessExpression(chain[0])
&& this.events[chain[0].name.getText()]
) {
const name = chain[0].name.getText();
const types = this.events[name];

if (!ts.isPropertyAccessExpression(chain[1]) || !ts.isCallExpression(chain[2])) throw Error(`Unsupported ${ts.SyntaxKind[chain[1].kind]} ${chain[1].getText()}`);

if (chain[1].name.getText() !== 'log') throw Error(`Unsupported event method ${chain[1].name.getText()}`);

const argTypes = this.getABITupleString(types.map((t) => this.getABIType(t)).join(','))
.replace(/account/g, 'address')
.replace(/(application|asset)/g, 'uint64');

const signature = `${name}(${argTypes})`;

const selector = sha512_256(Buffer.from(signature)).slice(0, 8);

this.typeHint = `[${types.map((t) => this.getABIType(t)).join(',')}]`;
this.pushVoid(chain[2], `byte 0x${selector} // ${signature}`);
this.processArrayElements(chain[2].arguments, chain[2]);
this.pushVoid(chain[2], 'concat');

this.pushVoid(chain[2], 'log');

chain.splice(0, 3);
return;
}

// If this is a storage property (ie. GlobalMap, BoxKey, etc.)
if (
ts.isPropertyAccessExpression(chain[0])
Expand Down
9 changes: 9 additions & 0 deletions tests/contracts/artifacts/GeneralTest.abi.json
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,15 @@
"desc": ""
}
},
{
"name": "events",
"args": [],
"desc": "",
"returns": {
"type": "void",
"desc": ""
}
},
{
"name": "createApplication",
"desc": "",
Expand Down
24 changes: 23 additions & 1 deletion tests/contracts/artifacts/GeneralTest.approval.teal
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,27 @@ bzeroFunction:
log
retsub

// events()void
abi_route_events:
// execute events()void
callsub events
int 1
return

events:
proto 0 0

// tests/contracts/general.algo.ts:98
// this.myEvent.log(this.app, 1)
byte 0x87528196 // myEvent(uint64,uint64)
txna Applications 0
itob
byte 0x0000000000000001
concat
concat
log
retsub

abi_route_createApplication:
int 1
return
Expand Down Expand Up @@ -648,6 +669,7 @@ call_NoOp:
method "fromID()void"
method "tmpl()void"
method "bzeroFunction()void"
method "events()void"
txna ApplicationArgs 0
match abi_route_txnTypeEnum abi_route_txnGroupLength abi_route_asserts abi_route_verifyTxnFromArg abi_route_verifyTxnFromTxnGroup abi_route_verifyTxnCondition abi_route_verifyTxnIncludedIn abi_route_verifyTxnNotIncludedIn abi_route_submitPendingGroup abi_route_methodWithTxnArgs abi_route_nestedTernary abi_route_shift abi_route_fromBytes abi_route_fromID abi_route_tmpl abi_route_bzeroFunction
match abi_route_txnTypeEnum abi_route_txnGroupLength abi_route_asserts abi_route_verifyTxnFromArg abi_route_verifyTxnFromTxnGroup abi_route_verifyTxnCondition abi_route_verifyTxnIncludedIn abi_route_verifyTxnNotIncludedIn abi_route_submitPendingGroup abi_route_methodWithTxnArgs abi_route_nestedTernary abi_route_shift abi_route_fromBytes abi_route_fromID abi_route_tmpl abi_route_bzeroFunction abi_route_events
err
16 changes: 15 additions & 1 deletion tests/contracts/artifacts/GeneralTest.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions tests/contracts/general.algo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,10 @@ class GeneralTest extends Contract {
const z: [uint64, uint<8>] = bzero<[uint64, uint<8>]>();
log(x + y + z);
}

myEvent = new EventLogger<[Application, number]>();

events(): void {
this.myEvent.log(this.app, 1);
}
}
7 changes: 7 additions & 0 deletions types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,13 @@ declare class Application {
global(key: BytesLike): any
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
declare class EventLogger<ArgumentTypes extends any[]> {
constructor()

log(...args: ArgumentTypes): void
}

declare type BoxValue<ValueType> = {
value: ValueType
delete: () => void
Expand Down

0 comments on commit 9318b69

Please sign in to comment.