Skip to content
This repository has been archived by the owner on Sep 5, 2023. It is now read-only.

event APIs #882

Merged
merged 30 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
41a528a
keep 256 bits of event topic
rjnrohit Jan 18, 2023
acd08f7
use util fn
rjnrohit Jan 18, 2023
35df14d
lint
rjnrohit Jan 18, 2023
63a2880
avoid repetitive hashing
rjnrohit Jan 18, 2023
308a34b
checkpoint
swapnilraj Jan 18, 2023
eb315b5
add behv tests for array of strings: ensure eles are padded
rjnrohit Jan 19, 2023
aa92464
Merge branch 'develop' into event_topic
rjnrohit Jan 19, 2023
c74950d
lint
rjnrohit Jan 19, 2023
513030e
remove 251 bits masking
rjnrohit Jan 19, 2023
53b1926
rmv warp_keccak_felt
rjnrohit Jan 19, 2023
33182a6
address PR comment
rjnrohit Jan 23, 2023
fb111e9
Merge remote-tracking branch 'origin/develop' into event_topic
rjnrohit Jan 23, 2023
43f9372
add pattern matching
rjnrohit Jan 23, 2023
1a38790
add pattern matching
rjnrohit Jan 25, 2023
5fc8584
address PR comments
rjnrohit Jan 26, 2023
faede3d
Merge remote-tracking branch 'origin/develop' into checkpoint_event
rjnrohit Jan 30, 2023
641dc2a
Merge remote-tracking branch 'origin/event_topic' into checkpoint_event
rjnrohit Feb 1, 2023
4ebb376
Merge remote-tracking branch 'origin/develop' into checkpoint_event
rjnrohit Feb 1, 2023
82a6ed4
WIP
rjnrohit Feb 2, 2023
c21cf57
complete event APIs
rjnrohit Feb 3, 2023
8e4d9f2
lint
rjnrohit Feb 3, 2023
4996058
change
rjnrohit Feb 6, 2023
8f296a7
warp+ve
rjnrohit Feb 6, 2023
1bdc4a5
Merge remote-tracking branch 'origin/develop' into checkpoint_event
rjnrohit Feb 7, 2023
bb34136
add member fns
rjnrohit Feb 7, 2023
483bbcc
cleanup
swapnilraj Feb 8, 2023
0167434
Merge branch 'develop' into checkpoint_event
swapnilraj Feb 8, 2023
1a45277
remove indexed complex structure from event testing
swapnilraj Feb 8, 2023
c6799e9
add test for decode Warp
rjnrohit Feb 9, 2023
5c1c08b
address comments
rjnrohit Feb 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 54 additions & 37 deletions src/cairoUtilFuncGen/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,38 +76,47 @@ export class EventFunction extends StringIndexedFuncGen {
return existing.name;
}

const [params, keysInsertions, dataInsertions] = node.vParameters.vParameters.reduce(
([params, keysInsertions, dataInsertions], param, index) => {
const paramType = generalizeType(safeGetNodeType(param, this.ast.inference))[0];
const cairoType = CairoType.fromSol(paramType, this.ast, TypeConversionContext.Ref);

params.push({ name: `param${index}`, type: cairoType.toString() });

if (param.indexed) {
// An indexed parameter should go to the keys array
if (isValueType(paramType)) {
// If the parameter is a value type, we can just add it to the keys array
// as it is, as we do regular abi encoding
keysInsertions.push(
this.generateSimpleEncodingCode(paramType, 'keys', `param${index}`),
);
const [params, keysInsertions, dataParams, dataParamTypes] =
node.vParameters.vParameters.reduce(
([params, keysInsertions, dataParams, dataParamTypes], param, index) => {
const paramType = generalizeType(safeGetNodeType(param, this.ast.inference))[0];
const cairoType = CairoType.fromSol(paramType, this.ast, TypeConversionContext.Ref);

params.push({ name: `param${index}`, type: cairoType.toString() });

if (param.indexed) {
// An indexed parameter should go to the keys array
if (isValueType(paramType)) {
// If the parameter is a value type, we can just add it to the keys array
// as it is, as we do regular abi encoding
keysInsertions.push(
this.generateSimpleEncodingCode([paramType], 'keys', [`param${index}`]),
);
} else {
// If the parameter is a reference type, we hash the with special encoding
// function: more at:
// https://docs.soliditylang.org/en/v0.8.14/abi-spec.html#encoding-of-indexed-event-parameters
keysInsertions.push(
this.generateComplexEncodingCode([paramType], 'keys', [`param${index}`]),
);
}
} else {
// If the parameter is a reference type, we hash the with special encoding
// function: more at:
// https://docs.soliditylang.org/en/v0.8.14/abi-spec.html#encoding-of-indexed-event-parameters
keysInsertions.push(
this.generateComplexEncodingCode(paramType, 'keys', `param${index}`),
);
// A non-indexed parameter should go to the data array
dataParams.push(`param${index}`);
dataParamTypes.push(paramType);
}
} else {
// A non-indexed parameter should go to the data array
dataInsertions.push(this.generateSimpleEncodingCode(paramType, 'data', `param${index}`));
}

return [params, keysInsertions, dataInsertions];
},
[new Array<{ name: string; type: string }>(), new Array<string>(), new Array<string>()],
);

return [params, keysInsertions, dataParams, dataParamTypes];
},
[
new Array<{ name: string; type: string }>(),
new Array<string>(),
new Array<string>(),
new Array<TypeNode>(),
],
);

const dataInsertions = this.generateSimpleEncodingCode(dataParamTypes, 'data', dataParams);

const cairoParams = params.map((p) => `${p.name} : ${p.type}`).join(', ');

Expand All @@ -133,7 +142,7 @@ export class EventFunction extends StringIndexedFuncGen {
` // data arrays`,
` let data_len: felt = 0;`,
` let (data: felt*) = alloc();`,
...dataInsertions,
dataInsertions,
` // data: pack 31 bytes felts into a single 248 bits felt`,
` let (data_len: felt, data: felt*) = pack_bytes_felt(${BYTES_IN_FELT_PACKING}, ${BIG_ENDIAN}, data_len, data);`,
` emit_event(keys_len, keys, data_len, data);`,
Expand Down Expand Up @@ -167,29 +176,37 @@ export class EventFunction extends StringIndexedFuncGen {
].join('\n');
}

private generateSimpleEncodingCode(type: TypeNode, arrayName: string, argName: string): string {
const abiFunc = this.abiEncode.getOrCreate([type]);
private generateSimpleEncodingCode(
types: TypeNode[],
arrayName: string,
argNames: string[],
): string {
const abiFunc = this.abiEncode.getOrCreate(types);

this.requireImport('warplib.memory', 'wm_to_felt_array');
this.requireImport('warplib.keccak', 'felt_array_concat');

return [
` let (mem_encode: felt) = ${abiFunc}(${argName});`,
` let (mem_encode: felt) = ${abiFunc}(${argNames.join(',')});`,
` let (encode_bytes_len: felt, encode_bytes: felt*) = wm_to_felt_array(mem_encode);`,
` let (${arrayName}_len: felt) = felt_array_concat(encode_bytes_len, 0, encode_bytes, ${arrayName}_len, ${arrayName});`,
].join('\n');
}

private generateComplexEncodingCode(type: TypeNode, arrayName: string, argName: string): string {
const abiFunc = this.indexEncode.getOrCreate([type]);
private generateComplexEncodingCode(
types: TypeNode[],
arrayName: string,
argNames: string[],
): string {
const abiFunc = this.indexEncode.getOrCreate(types);

this.requireImport('starkware.cairo.common.uint256', 'Uint256');
this.requireImport(`warplib.maths.utils`, 'felt_to_uint256');
this.requireImport('warplib.keccak', 'warp_keccak');
this.requireImport('warplib.dynamic_arrays_util', 'fixed_bytes256_to_felt_dynamic_array_spl');

return [
` let (mem_encode: felt) = ${abiFunc}(${argName});`,
` let (mem_encode: felt) = ${abiFunc}(${argNames.join(',')});`,
` let (keccak_hash256: Uint256) = warp_keccak(mem_encode);`,
` let (${arrayName}_len: felt) = fixed_bytes256_to_felt_dynamic_array_spl(${arrayName}_len, ${arrayName}, 0, keccak_hash256);`,
].join('\n');
Expand Down
1 change: 1 addition & 0 deletions src/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export * from './transpiler';
export * from './utils/export';
export * from './transcode/encode';
export * from './transcode/decode';
export * from './transcode/interface';
33 changes: 32 additions & 1 deletion src/passes/rejectUnsupportedFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ContractKind,
DataLocation,
ErrorDefinition,
EventDefinition,
ExpressionStatement,
ExternalReferenceType,
FunctionCall,
Expand All @@ -15,9 +16,12 @@ import {
FunctionDefinition,
FunctionKind,
FunctionType,
generalizeType,
Identifier,
IndexAccess,
isReferenceType,
Literal,
MappingType,
MemberAccess,
PointerType,
RevertStatement,
Expand All @@ -33,7 +37,7 @@ import { AST } from '../ast/ast';
import { ASTMapper } from '../ast/mapper';
import { printNode } from '../utils/astPrinter';
import { getErrorMessage, WillNotSupportError } from '../utils/errors';
import { isDynamicArray, safeGetNodeType } from '../utils/nodeTypeProcessing';
import { isDynamicArray, safeGetNodeType, isValueType } from '../utils/nodeTypeProcessing';
import { isExternalCall, isExternallyVisible } from '../utils/utils';

const PATH_REGEX = /^[\w-@/\\]*$/;
Expand Down Expand Up @@ -93,6 +97,33 @@ export class RejectUnsupportedFeatures extends ASTMapper {
this.addUnsupported('User defined Errors are not supported', node);
}

visitEventDefinition(node: EventDefinition, ast: AST): void {
node.vParameters.vParameters.forEach((param) => {
if (param.indexed) {
rjnrohit marked this conversation as resolved.
Show resolved Hide resolved
const paramType = generalizeType(safeGetNodeType(param, ast.inference))[0];
if (isValueType(paramType)) {
this.commonVisit(node, ast);
return;
}
if (
paramType instanceof ArrayType ||
paramType instanceof MappingType ||
(paramType instanceof PointerType && isReferenceType(paramType.to))
rjnrohit marked this conversation as resolved.
Show resolved Hide resolved
)
this.addUnsupported(
`Indexed parameters of type: ${paramType.constructor.name} are not supported`,
node,
);

if (
paramType instanceof UserDefinedType &&
paramType.definition instanceof StructDefinition
)
this.addUnsupported(`Indexed parameters of type: Structs are not supported`, node);
}
});
}

visitFunctionCallOptions(node: FunctionCallOptions, ast: AST): void {
// Allow options only when passing salt values for contract creation
if (
Expand Down
2 changes: 1 addition & 1 deletion src/transcode/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export function encodeComplex(type: ParamType, inputs: IterableIterator<SolValue
throw new Error(`Can't encode complex type ${type}`);
}

export function makeIterator(value: SolValue) {
export function makeIterator<T>(value: T): IterableIterator<T> {
if (Array.isArray(value)) {
return value.values();
}
Expand Down
1 change: 1 addition & 0 deletions src/transcode/export.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './decode';
export * from './encode';
export * from './utils';
export * from './interface';
28 changes: 28 additions & 0 deletions src/transcode/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { EventFragment, Interface, Result } from 'ethers/lib/utils';
import { argType, EventItem, join248bitChunks, splitInto248BitChunks } from '../utils/event';

export class WarpInterface extends Interface {
decodeWarpEvent(fragment: EventFragment | string, warpEvent: EventItem): Result {
rjnrohit marked this conversation as resolved.
Show resolved Hide resolved
// reverse 248 bit packing
const data = join248bitChunks(warpEvent.data);
const keys = join248bitChunks(warpEvent.keys);

// Remove leading 0x from each element and join them
const chunkedData = `0x${data.map((x) => x.slice(2)).join('')}`;

return super.decodeEventLog(fragment, chunkedData, keys);
}

encodeWarpEvent(fragment: EventFragment, values: argType[], order = 0): EventItem {
const { data, topics }: { data: string; topics: string[] } = super.encodeEventLog(
fragment,
values,
);

const topicFlatHex = '0x' + topics.map((x) => x.slice(2).padStart(64, '0')).join('');
const topicItems248: string[] = splitInto248BitChunks(topicFlatHex);
rjnrohit marked this conversation as resolved.
Show resolved Hide resolved
const dataItems248: string[] = splitInto248BitChunks(data);

return { order, keys: topicItems248, data: dataItems248 };
}
}
53 changes: 27 additions & 26 deletions src/utils/event.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,49 @@
import createKeccakHash from 'keccak';
import { toUintOrFelt } from './export';

export type EventItem = { data: string[]; keys: string[]; order: number };
export type EventItem = { data: string[]; keys: string[]; order?: number };

export function decodeEventLog(eventsLog: EventItem[]): EventItem[] {
// flat number to hex string with rjust 62
const flatNumberToHexString = (num: number | bigint | string): string => {
export function splitInto248BitChunks(data: string): string[] {
if (data.startsWith('0x')) data = data.slice(2);
rjnrohit marked this conversation as resolved.
Show resolved Hide resolved
if (data === '') return [];
const paddedData = data.padEnd(data.length + (62 - (data.length % 62)), '0');

const result = [];
// get number from every 62 hex digits chunk
for (let i = 0; i < paddedData.length; i += 62) {
result.push(BigInt(`0x${paddedData.slice(i, i + 62)}`).toString());
}
return result;
}

export function join248bitChunks(data: string[]): string[] {
// numbers to hex in 248 bits
const numberToHex248 = (num: number | bigint | string): string => {
return `${BigInt(num).toString(16).padStart(62, '0')}`;
};

// decode number from raw hex input string
const byte32numbers = (hexArray: string[]): bigint[] => {
const raw_hex_input = hexArray.reduce((pv, cv) => {
return `${pv}${flatNumberToHexString(cv)}`;
}, '');
const decode248BitEncoding = (hexArray: string[]): bigint[] => {
const rawHex = hexArray.map(numberToHex248).join('');

// pad '0' to the end of the string to make it a multiple of 64
const padded_hex_input = raw_hex_input.padEnd(
raw_hex_input.length + (64 - (raw_hex_input.length % 64)),
'0',
);
const paddedHexVals = rawHex.padEnd(rawHex.length + (64 - (rawHex.length % 64)), '0');

// get number from every 64 hex digits chunk
const numbers: bigint[] = [];
for (let i = 0; i < padded_hex_input.length; i += 64) {
numbers.push(BigInt(`0x${padded_hex_input.slice(i, i + 64)}`));
const result: bigint[] = [];
for (let i = 0; i < paddedHexVals.length; i += 64) {
result.push(BigInt(`0x${paddedHexVals.slice(i, i + 64)}`));
}

//remove trailing zero
if (padded_hex_input.length !== raw_hex_input.length && numbers[numbers.length - 1] === 0n) {
numbers.pop();
if (paddedHexVals.length !== rawHex.length && result[result.length - 1] === 0n) {
result.pop();
}

return numbers;
return result;
};

const events: EventItem[] = eventsLog.map((event) => {
return {
order: event.order,
keys: byte32numbers(event.keys).map((num) => `0x${num.toString(16)}`),
data: byte32numbers(event.data).map((num) => `0x${num.toString(16)}`),
};
});
return events;
return decode248BitEncoding(data).map((num) => `0x${num.toString(16).padStart(64, '0')}`);
}

export type argType = string | argType[];
Expand Down
8 changes: 2 additions & 6 deletions tests/behaviour/behaviour.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { expectations } from './expectations';
import { AsyncTest, Expect, OUTPUT_DIR } from './expectations/types';
import { DeployResponse } from '../testnetInterface';
import { getDependencyGraph } from '../../src/utils/postCairoWrite';
import { decodeEventLog, EventItem } from '../../src/utils/event';
import { EventItem } from '../../src/utils/event';

const PRINT_STEPS = false;
const PARALLEL_COUNT = 8;
Expand Down Expand Up @@ -242,12 +242,8 @@ async function behaviourTest(
`${name} - Return data should match expectation`,
).to.deep.equal(replaced_expectedResult);
if (events !== undefined) {
events.forEach((e) => {
e.data = e.data.map((n) => `0x${BigInt(n).toString(16)}`);
e.keys = e.keys.map((n) => `0x${BigInt(n).toString(16)}`);
});
expect(
decodeEventLog(response.events as EventItem[]),
response.events as EventItem[],
`${name} - Events should match events expectation`,
).to.deep.equal(events);
}
Expand Down
14 changes: 7 additions & 7 deletions tests/behaviour/contracts/events/indexed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ contract WARP{

//Event Definitions
event uintEvent(uint indexed);
event arrayEvent(uint[] indexed);
event arrayStringEvent(string[] indexed);
event nestedArrayEvent(uint[][] indexed);
event structEvent(C indexed);
event arrayEvent(uint[]);
event arrayStringEvent(string[]);
event nestedArrayEvent(uint[][]);
event structEvent(C);

function add(uint256 a, uint256 b) public {
emit uintEvent(a+b);
Expand All @@ -25,9 +25,9 @@ contract WARP{

function arrayString() public {
string[] memory a = new string[](3);
a[0] = "a";
a[1] = "b";
a[2] = "c";
a[0] = "roh";
rjnrohit marked this conversation as resolved.
Show resolved Hide resolved
a[1] = "itra";
a[2] = "njan";
emit arrayStringEvent(a);
}

Expand Down
Loading