Skip to content

Commit

Permalink
Merge pull request #245 from ElrondNetwork/fix-buffer-constraints
Browse files Browse the repository at this point in the history
Allow one to configure dependencies of: ResultsParser, ArgsSerializer.
  • Loading branch information
andreibancioiu authored Nov 23, 2022
2 parents 843e673 + 0292757 commit 496e500
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 815 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes will be documented in this file.

Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.

## 11.1.2
- [Allow one to configure dependencies of: ResultsParser, ArgsSerializer](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/245)

## 11.1.1
- [Fix `CompositeValue.valueOf()`](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/241)

Expand Down
1,007 changes: 219 additions & 788 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@elrondnetwork/erdjs",
"version": "11.1.1",
"version": "11.1.2",
"description": "Smart Contracts interaction framework",
"main": "out/index.js",
"types": "out/index.d.js",
Expand Down
35 changes: 27 additions & 8 deletions src/smartcontracts/argSerializer.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
import { BinaryCodec } from "./codec";
import { Type, EndpointParameterDefinition, TypedValue } from "./typesystem";
import { EndpointParameterDefinition, Type, TypedValue } from "./typesystem";
import { OptionalType, OptionalValue } from "./typesystem/algebraic";
import { CompositeType, CompositeValue } from "./typesystem/composite";
import { VariadicType, VariadicValue } from "./typesystem/variadic";
import { OptionalType, OptionalValue } from "./typesystem/algebraic";

export const ArgumentsSeparator = "@";

/**
* For the moment, this is the only codec used.
*/
const Codec = new BinaryCodec();
interface IArgSerializerOptions {
codec: ICodec;
}

interface ICodec {
decodeTopLevel(buffer: Buffer, type: Type): TypedValue;
encodeTopLevel(typedValue: TypedValue): Buffer;
}

// TODO: perhaps move default construction options to a factory (ArgSerializerFactory), instead of referencing them in the constructor
// (postpone as much as possible, breaking change)
const defaultArgSerializerrOptions: IArgSerializerOptions = {
codec: new BinaryCodec()
};

export class ArgSerializer {
codec: ICodec;

constructor(options?: IArgSerializerOptions) {
options = { ...defaultArgSerializerrOptions, ...options };
this.codec = options.codec;
}

/**
* Reads typed values from an arguments string (e.g. aa@bb@@cc), given parameter definitions.
*/
Expand All @@ -34,6 +51,7 @@ export class ArgSerializer {
*/
buffersToValues(buffers: Buffer[], parameters: EndpointParameterDefinition[]): TypedValue[] {
// TODO: Refactor, split (function is quite complex).
const self = this;

buffers = buffers || [];

Expand Down Expand Up @@ -85,7 +103,7 @@ export class ArgSerializer {
}

let buffer = buffers[bufferIndex++];
let decodedValue = Codec.decodeTopLevel(buffer, type);
let decodedValue = self.codec.decodeTopLevel(buffer, type);
return decodedValue;
}

Expand Down Expand Up @@ -121,6 +139,7 @@ export class ArgSerializer {
*/
valuesToBuffers(values: TypedValue[]): Buffer[] {
// TODO: Refactor, split (function is quite complex).
const self = this;

let buffers: Buffer[] = [];

Expand Down Expand Up @@ -150,7 +169,7 @@ export class ArgSerializer {
} else {
// Non-composite (singular), non-variadic (fixed) type.
// The only branching without a recursive call.
let buffer: Buffer = Codec.encodeTopLevel(value);
let buffer: Buffer = self.codec.encodeTopLevel(value);
buffers.push(buffer);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/smartcontracts/codec/binary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ export class BinaryCodecConstraints {
maxListLength: number;

constructor(init?: Partial<BinaryCodecConstraints>) {
this.maxBufferLength = init?.maxBufferLength || 4096;
this.maxListLength = init?.maxListLength || 1024;
this.maxBufferLength = init?.maxBufferLength || 40960;
this.maxListLength = init?.maxListLength || 8192;
}

checkBufferLength(buffer: Buffer) {
Expand Down
56 changes: 48 additions & 8 deletions src/smartcontracts/resultsParser.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { ContractQueryResponse, ContractResultItem, ContractResults, TransactionEvent, TransactionEventTopic, TransactionLogs, TransactionOnNetwork } from "@elrondnetwork/erdjs-network-providers";
import { assert } from "chai";
import * as fs from "fs";
import path from "path";
import { assert } from "chai";
import { BigUIntType, BigUIntValue, EndpointDefinition, EndpointModifiers, EndpointParameterDefinition } from "./typesystem";
import { BytesType, BytesValue } from "./typesystem/bytes";
import { ReturnCode } from "./returnCode";
import { ResultsParser } from "./resultsParser";
import { Logger, LogLevel } from "../logger";
import { ITransactionOnNetwork } from "../interfaceOfNetwork";
import { ContractQueryResponse, ContractResultItem, ContractResults, TransactionEvent, TransactionEventTopic, TransactionLogs, TransactionOnNetwork } from "@elrondnetwork/erdjs-network-providers";
import { Address } from "../address";
import { ITransactionOnNetwork } from "../interfaceOfNetwork";
import { Logger, LogLevel } from "../logger";
import { ArgSerializer } from "./argSerializer";
import { ResultsParser } from "./resultsParser";
import { ReturnCode } from "./returnCode";
import { BigUIntType, BigUIntValue, EndpointDefinition, EndpointModifiers, EndpointParameterDefinition, TypedValue, U64Type, U64Value } from "./typesystem";
import { BytesType, BytesValue } from "./typesystem/bytes";

const KnownReturnCodes: string[] = [
ReturnCode.None.valueOf(),
Expand All @@ -32,6 +33,45 @@ const KnownReturnCodes: string[] = [
describe("test smart contract results parser", () => {
let parser = new ResultsParser();

it("should create parser with custom dependencies (1)", async () => {
const customParser = new ResultsParser({
argsSerializer: {
buffersToValues(_buffers, _parameters) {
return [new U64Value(42)];
},
stringToBuffers(_joinedString) {
return []
}
}
});

const endpoint = new EndpointDefinition("", [], [], new EndpointModifiers("", []));
const queryResponse = new ContractQueryResponse({});
const bundle = customParser.parseQueryResponse(queryResponse, endpoint);
assert.deepEqual(bundle.firstValue, new U64Value(42));
});

it("should create parser with custom dependencies (2)", async () => {
const customParser = new ResultsParser({
argsSerializer: new ArgSerializer({
codec: {
decodeTopLevel(_buffer, _type): TypedValue {
return new U64Value(42);
},
encodeTopLevel(_typedValue): Buffer {
return Buffer.from([])
},
}
})
});

const outputParameters = [new EndpointParameterDefinition("", "", new U64Type())];
const endpoint = new EndpointDefinition("", [], outputParameters, new EndpointModifiers("", []));
const queryResponse = new ContractQueryResponse({ returnData: [""] });
const bundle = customParser.parseQueryResponse(queryResponse, endpoint);
assert.deepEqual(bundle.firstValue, new U64Value(42));
});

it("should parse query response", async () => {
let endpointModifiers = new EndpointModifiers("", []);
let outputParameters = [
Expand Down
38 changes: 30 additions & 8 deletions src/smartcontracts/resultsParser.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { TransactionDecoder, TransactionMetadata } from "@elrondnetwork/transaction-decoder";
import { Address } from "../address";
import { ErrCannotParseContractResults } from "../errors";
import { Logger } from "../logger";
import { IAddress } from "../interface";
import { IContractQueryResponse, IContractResults, ITransactionLogs, ITransactionOnNetwork } from "../interfaceOfNetwork";
import { Logger } from "../logger";
import { ArgSerializer } from "./argSerializer";
import { TypedOutcomeBundle, UntypedOutcomeBundle } from "./interface";
import { ReturnCode } from "./returnCode";
import { EndpointDefinition } from "./typesystem";
import { IAddress } from "../interface";
import { EndpointDefinition, EndpointParameterDefinition, TypedValue } from "./typesystem";

enum WellKnownEvents {
OnTransactionCompleted = "completedTxEvent",
Expand All @@ -19,14 +19,36 @@ enum WellKnownTopics {
TooMuchGas = "@too much gas provided for processing"
}

interface IResultsParserOptions {
argsSerializer: IArgsSerializer;
}

interface IArgsSerializer {
buffersToValues(buffers: Buffer[], parameters: EndpointParameterDefinition[]): TypedValue[];
stringToBuffers(joinedString: string): Buffer[];
}

// TODO: perhaps move default construction options to a factory (ResultsParserFactory), instead of referencing them in the constructor
// (postpone as much as possible, breaking change)
const defaultResultsParserOptions: IResultsParserOptions = {
argsSerializer: new ArgSerializer()
};

/**
* Parses contract query responses and smart contract results.
* The parsing involves some heuristics, in order to handle slight inconsistencies (e.g. some SCRs are present on API, but missing on Gateway).
*/
export class ResultsParser {
private readonly argsSerializer: IArgsSerializer;

constructor(options?: IResultsParserOptions) {
options = { ...defaultResultsParserOptions, ...options };
this.argsSerializer = options.argsSerializer;
}

parseQueryResponse(queryResponse: IContractQueryResponse, endpoint: EndpointDefinition): TypedOutcomeBundle {
let parts = queryResponse.getReturnDataParts();
let values = new ArgSerializer().buffersToValues(parts, endpoint.output);
let values = this.argsSerializer.buffersToValues(parts, endpoint.output);
let returnCode = new ReturnCode(queryResponse.returnCode.toString());

return {
Expand All @@ -52,7 +74,7 @@ export class ResultsParser {

parseOutcome(transaction: ITransactionOnNetwork, endpoint: EndpointDefinition): TypedOutcomeBundle {
let untypedBundle = this.parseUntypedOutcome(transaction);
let values = new ArgSerializer().buffersToValues(untypedBundle.values, endpoint.output);
let values = this.argsSerializer.buffersToValues(untypedBundle.values, endpoint.output);

return {
returnCode: untypedBundle.returnCode,
Expand Down Expand Up @@ -218,7 +240,7 @@ export class ResultsParser {

private createBundleOnWriteLogWhereFirstTopicEqualsAddress(logs: ITransactionLogs, address: IAddress): UntypedOutcomeBundle | null {
let hexAddress = new Address(address.bech32()).hex();

let eventWriteLogWhereTopicIsSender = logs.findSingleOrNoneEvent(
WellKnownEvents.OnWriteLog,
event => event.findFirstOrNoneTopic(topic => topic.hex() == hexAddress) != undefined
Expand Down Expand Up @@ -283,10 +305,10 @@ export class ResultsParser {
// TODO: Upon gathering more transaction samples, fix for other kinds of transfers, as well (future PR, as needed).
}

let parts = new ArgSerializer().stringToBuffers(data);
let parts = this.argsSerializer.stringToBuffers(data);
let returnCodePart = parts[startingIndex] || Buffer.from([]);
let returnDataParts = parts.slice(startingIndex + 1);

if (returnCodePart.length == 0) {
throw new ErrCannotParseContractResults("no return code");
}
Expand Down

0 comments on commit 496e500

Please sign in to comment.