Skip to content

Commit

Permalink
Merge pull request #159 from ElrondNetwork/better-interactions
Browse files Browse the repository at this point in the history
Breaking changes (erdjs 10): improve contract interactions and interpretation of contract results
  • Loading branch information
andreibancioiu authored Mar 24, 2022
2 parents 8612618 + bbb2a56 commit f7f6182
Show file tree
Hide file tree
Showing 63 changed files with 1,379 additions and 838 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/erdjs-publish-alpha-beta.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Publish erdjs (alpha / beta)

on:
workflow_dispatch:
inputs:
channel:
type: choice
description: NPM channel
options:
- alpha
- beta

jobs:
publish-npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
registry-url: https://registry.npmjs.org/

- run: npm ci
- run: npm test

- name: Publish to npmjs
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
run: npm publish --access=public --tag=${{ github.event.inputs.channel }}
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,36 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
## Unreleased
- TBD

## [10.0.0]
- [Breaking changes: improve contract interactions and interpretation of contract results](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/159)

**Breaking changes**

- `ExecutionResultsBundle` and `QueryResponseBundle` have been removed, and replaced by `TypedOutcomeBundle` (and its untyped counterpart, `UntypedOutcomeBundle`).
- `SmartContractResults` has been changed to not use the concepts `immediate result` and `resulting calls` anymore. Instead, interpreting `SmartContractResults.items` is now the responsibility of the `ResultsParser` (on which the contract controllers depend).
- Redesigned `QueryResponse`, changed most of its public interface. Results interpretation is now the responsibility of the results parser, called by the smart contract controllers.
- `interpretQueryResponse()` and `interpretExecutionResults()` do not exist on the `Interaction` object anymore. Now, querying / executing an interaction against the controller will return the interpreted results.
- `TokenIdentifierValue` is constructed using a `string`, not a `buffer`. Its `valueOf()` is now a string, as well.
- The `Interaction` constructor does not receive the `interpretingFunction` parameter anymore.
- `Interaction.getInterpretingFunction()` and `Interaction.getExecutingFunction()` have been removed, replaced by `Interaction.getFunction()`.
- `DefaultInteractionRunner` has been removed, and replaced by **smart contract controllers**.
- `StrictChecker` has been renamed to `InteractionChecker`. It's public interface - the function `checkInteraction()` - has changed as well (it also requires the endpoint definition now, as a second parameter).
- The functions `getReceipt()`, `getSmartContractResults()` and `getLogs()` of `TransactionOnNetwork` have been removed. The underlying properties are now public.
- Renamed `OptionValue.newMissingType()` to `OptionValue.newMissingTyped()`
- Queries with a return code different than `Ok` do not automatically throw an exception anymore (`assertSuccess()` has to be called explicitly in order to throw).

**Other changes**

- `SmartContract`, in addition to `methods`, now also has a `methodAuto` object that allows one to create interactions without explicitly specifying the types of the arguments. Automatic type inference (within erdjs' typesystem) is leveraged. The type inference system was implemented in the past, in the `nativeSerializer` component - PR https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/9 by @claudiu725.
- Added utility function `getFieldValue()` on `Struct` and `EnumValue`.
- Refactoring in the `networkProvider` package (under development, in order to merge the provider interfaces under a single one)
- Added utility function `Interaction.useThenIncrementNonceOf()`
- Fixed `nativeSerializer` to not depend on `SmartContract`, `ContractWrapper` and `TestWallet` anymore (gathered under an interface).
- Replaced the old `lottery-egld` with the new `lottery-esdt` in integration tests.
- Added missing tests for some components: `nativeSerializer`, `struct`, `enum`.
- Added utility function `OptionalValue.newMissing()`. Added "fake" covariance wrt. "null type parameter" (when value is missing) on `OptionalType`.
- Added utility functions (typed value factories): `createListOfAddresses`, `createListOfTokenIdentifiers`.

## [9.2.3]
- [Fix log level in transaction watcher.](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/160)

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ Elrond SDK for JavaScript and TypeScript (written in TypeScript).

The most comprehensive usage examples are captured within the unit and the integration tests. Specifically, in the `*.spec.ts` files of the source code. For example:

- [transaction.dev.net.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/transaction.dev.net.spec.ts)
- [transaction.local.net.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/transaction.local.net.spec.ts)
- [address.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/address.spec.ts)
- [transactionPayloadBuilders.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/transactionPayloadBuilders.spec.ts)
- [smartContract.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/smartContract.spec.ts)
- [smartContract.dev.net.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/smartContract.dev.net.spec.ts)
- [smartContract.local.net.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/smartContract.local.net.spec.ts)
- [query.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/query.spec.ts)
- [query.main.net.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/query.main.net.spec.ts)

For advanced smart contract interaction, using ABIs, please see the following test files:

- [interaction.dev.net.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/interaction.dev.net.spec.ts)
- [interaction.local.net.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/interaction.local.net.spec.ts)
- [abiRegistry.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/typesystem/abiRegistry.spec.ts)
- [argSerializer.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/argSerializer.spec.ts)

Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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": "9.2.3",
"version": "10.0.0-beta.1",
"description": "Smart Contracts interaction framework",
"main": "out/index.js",
"types": "out/index.d.js",
Expand Down
27 changes: 27 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,33 @@ export class ErrTypingSystem extends Err {
}
}

/**
* Signals a missing field on a struct.
*/
export class ErrMissingFieldOnStruct extends Err {
public constructor(fieldName: string, structName: string) {
super(`field ${fieldName} does not exist on struct ${structName}`);
}
}

/**
* Signals a missing field on an enum.
*/
export class ErrMissingFieldOnEnum extends Err {
public constructor(fieldName: string, enumName: string) {
super(`field ${fieldName} does not exist on enum ${enumName}`);
}
}

/**
* Signals an error when parsing the contract results.
*/
export class ErrCannotParseContractResults extends Err {
public constructor(details: string) {
super(`cannot parse contract results: ${details}`);
}
}

/**
* Signals a generic codec (encode / decode) error.
*/
Expand Down
23 changes: 3 additions & 20 deletions src/networkProvider/contractResults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Hash } from "../hash";
import { IContractQueryResponse, IContractResultItem, IContractResults } from "./interface";
import { GasLimit, GasPrice } from "../networkParams";
import { Nonce } from "../nonce";
import { ArgSerializer, EndpointDefinition, MaxUint64, ReturnCode, TypedValue } from "../smartcontracts";
import { MaxUint64, ReturnCode } from "../smartcontracts";
import { TransactionHash } from "../transaction";

export class ContractResults implements IContractResults {
Expand Down Expand Up @@ -80,16 +80,6 @@ export class ContractResultItem implements IContractResultItem {

return item;
}

getOutputUntyped(): Buffer[] {
// TODO: Decide how to parse "data" (immediate results vs. other results).
throw new Error("Method not implemented.");
}

getOutputTyped(_endpointDefinition: EndpointDefinition): TypedValue[] {
// TODO: Decide how to parse "data" (immediate results vs. other results).
throw new Error("Method not implemented.");
}
}

export class ContractQueryResponse implements IContractQueryResponse {
Expand All @@ -110,14 +100,7 @@ export class ContractQueryResponse implements IContractQueryResponse {
return response;
}

getOutputUntyped(): Buffer[] {
let buffers = this.returnData.map((item) => Buffer.from(item || "", "base64"));
return buffers;
}

getOutputTyped(endpointDefinition: EndpointDefinition): TypedValue[] {
let buffers = this.getOutputUntyped();
let values = new ArgSerializer().buffersToValues(buffers, endpointDefinition!.output);
return values;
getReturnDataParts(): Buffer[] {
return this.returnData.map((item) => Buffer.from(item || ""));
}
}
10 changes: 3 additions & 7 deletions src/networkProvider/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { NetworkStake } from "../networkStake";
import { NetworkStatus } from "../networkStatus";
import { Nonce } from "../nonce";
import { Signature } from "../signature";
import { EndpointDefinition, Query, ReturnCode, TypedValue } from "../smartcontracts";
import { Query, ReturnCode } from "../smartcontracts";
import { Stats } from "../stats";
import { Transaction, TransactionHash, TransactionStatus } from "../transaction";
import { TransactionLogs } from "../transactionLogs";
Expand Down Expand Up @@ -204,19 +204,15 @@ export interface IContractResultItem {
gasPrice: GasPrice;
callType: number;
returnMessage: string;

getOutputUntyped(): Buffer[];
getOutputTyped(endpointDefinition: EndpointDefinition): TypedValue[];
}

export interface IContractQueryResponse {
returnData: string[];
returnCode: ReturnCode;
returnMessage: string;
gasUsed: GasLimit;

getOutputUntyped(): Buffer[];
getOutputTyped(endpointDefinition: EndpointDefinition): TypedValue[];

getReturnDataParts(): Buffer[];
}

export interface IContractSimulation {
Expand Down
6 changes: 3 additions & 3 deletions src/networkProvider/providers.dev.net.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ describe("test network providers on devnet: Proxy and API", function () {
let proxyResponse = await proxyProvider.queryContract(query);

assert.deepEqual(apiResponse, proxyResponse);
assert.deepEqual(apiResponse.getOutputUntyped(), proxyResponse.getOutputUntyped());
assert.deepEqual(apiResponse.getReturnDataParts(), proxyResponse.getReturnDataParts());

// Query: increment counter
query = new Query({
Expand All @@ -240,7 +240,7 @@ describe("test network providers on devnet: Proxy and API", function () {
proxyResponse = await proxyProvider.queryContract(query);

assert.deepEqual(apiResponse, proxyResponse);
assert.deepEqual(apiResponse.getOutputUntyped(), proxyResponse.getOutputUntyped());
assert.deepEqual(apiResponse.getReturnDataParts(), proxyResponse.getReturnDataParts());

// Query: issue ESDT
query = new Query({
Expand All @@ -260,6 +260,6 @@ describe("test network providers on devnet: Proxy and API", function () {
proxyResponse = await proxyProvider.queryContract(query);

assert.deepEqual(apiResponse, proxyResponse);
assert.deepEqual(apiResponse.getOutputUntyped(), proxyResponse.getOutputUntyped());
assert.deepEqual(apiResponse.getReturnDataParts(), proxyResponse.getReturnDataParts());
});
});
2 changes: 1 addition & 1 deletion src/proxyProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class ProxyProvider implements IProvider {
return this.doPostGeneric("vm-values/query", data, (response) =>
QueryResponse.fromHttpResponse(response.data || response.vmOutput)
);
} catch (err) {
} catch (err: any) {
throw errors.ErrContractQuery.increaseSpecificity(err);
}
}
Expand Down
20 changes: 8 additions & 12 deletions src/smartcontracts/codec/binary.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { BinaryCodec, BinaryCodecConstraints } from "./binary";
import { AddressType, AddressValue, BigIntType, BigUIntType, BigUIntValue, BooleanType, BooleanValue, I16Type, I32Type, I64Type, I8Type, NumericalType, NumericalValue, Struct, Field, StructType, TypedValue, U16Type, U32Type, U32Value, U64Type, U64Value, U8Type, U8Value, List, ListType, EnumType, EnumVariantDefinition, EnumValue, ArrayVec, ArrayVecType, U16Value, TokenIdentifierType, TokenIdentifierValue, StringValue, StringType } from "../typesystem";
import { isMsbOne } from "./utils";
import { Address } from "../../address";
import { Balance } from "../../balance";
import { BytesType, BytesValue } from "../typesystem/bytes";
import BigNumber from "bignumber.js";
import { FieldDefinition } from "../typesystem/fields";
Expand Down Expand Up @@ -189,29 +188,27 @@ describe("test binary codec (advanced)", () => {
let fooType = new StructType(
"Foo",
[
new FieldDefinition("token_identifier", "", new TokenIdentifierType()),
new FieldDefinition("ticket_price", "", new BigUIntType()),
new FieldDefinition("tickets_left", "", new U32Type()),
new FieldDefinition("deadline", "", new U64Type()),
new FieldDefinition("max_entries_per_user", "", new U32Type()),
new FieldDefinition("prize_distribution", "", new BytesType()),
new FieldDefinition("whitelist", "", new ListType(new AddressType())),
new FieldDefinition("current_ticket_number", "", new U32Type()),
new FieldDefinition("prize_pool", "", new BigUIntType())
]
);

let fooStruct = new Struct(fooType, [
new Field(new BigUIntValue(Balance.egld(10).valueOf()), "ticket_price"),
new Field(new TokenIdentifierValue("lucky-token"), "token_identifier"),
new Field(new BigUIntValue(1), "ticket_price"),
new Field(new U32Value(0), "tickets_left"),
new Field(new U64Value(new BigNumber("0x000000005fc2b9db")), "deadline"),
new Field(new U32Value(0xffffffff), "max_entries_per_user"),
new Field(new BytesValue(Buffer.from([0x64])), "prize_distribution"),
new Field(new List(new ListType(new AddressType()), []), "whitelist"),
new Field(new U32Value(9472), "current_ticket_number"),
new Field(new BigUIntValue(new BigNumber("94720000000000000000000")), "prize_pool")
]);

let encodedExpected = serialized("[00000008|8ac7230489e80000] [00000000] [000000005fc2b9db] [ffffffff] [00000001|64] [00000000] [00002500] [0000000a|140ec80fa7ee88000000]");
let encodedExpected = serialized("[0000000b|6c75636b792d746f6b656e] [00000001|01] [00000000] [000000005fc2b9db] [ffffffff] [00000001|64] [0000000a|140ec80fa7ee88000000]");
let encoded = codec.encodeNested(fooStruct);
assert.deepEqual(encoded, encodedExpected);

Expand All @@ -221,13 +218,12 @@ describe("test binary codec (advanced)", () => {

let plainFoo = decoded.valueOf();
assert.deepEqual(plainFoo, {
ticket_price: new BigNumber("10000000000000000000"),
token_identifier: "lucky-token",
ticket_price: new BigNumber("1"),
tickets_left: new BigNumber(0),
deadline: new BigNumber("0x000000005fc2b9db", 16),
max_entries_per_user: new BigNumber(0xffffffff),
prize_distribution: Buffer.from([0x64]),
whitelist: [],
current_ticket_number: new BigNumber(9472),
prize_pool: new BigNumber("94720000000000000000000")
});
});
Expand All @@ -244,7 +240,7 @@ describe("test binary codec (advanced)", () => {
);

let paymentStruct = new Struct(paymentType, [
new Field(new TokenIdentifierValue(Buffer.from("TEST-1234")), "token_identifier"),
new Field(new TokenIdentifierValue("TEST-1234"), "token_identifier"),
new Field(new U64Value(new BigNumber(42)), "nonce"),
new Field(new BigUIntValue(new BigNumber("123450000000000000000")), "amount")
]);
Expand All @@ -259,7 +255,7 @@ describe("test binary codec (advanced)", () => {

let decodedPayment = decoded.valueOf();
assert.deepEqual(decodedPayment, {
token_identifier: Buffer.from("TEST-1234"),
token_identifier: "TEST-1234",
nonce: new BigNumber(42),
amount: new BigNumber("123450000000000000000"),
});
Expand Down
2 changes: 1 addition & 1 deletion src/smartcontracts/codec/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class OptionValueBinaryCodec {

decodeNested(buffer: Buffer, type: Type): [OptionValue, number] {
if (buffer[0] == 0x00) {
return [OptionValue.newMissingType(type), 1];
return [OptionValue.newMissingTyped(type), 1];
}

if (buffer[0] != 0x01) {
Expand Down
8 changes: 4 additions & 4 deletions src/smartcontracts/codec/tokenIdentifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ export class TokenIdentifierCodec {

decodeNested(buffer: Buffer): [TokenIdentifierValue, number] {
let [bytesValue, length] = this.bytesCodec.decodeNested(buffer);
return [new TokenIdentifierValue(bytesValue.valueOf()), length];
return [new TokenIdentifierValue(bytesValue.toString()), length];
}

decodeTopLevel(buffer: Buffer): TokenIdentifierValue {
let bytesValue = this.bytesCodec.decodeTopLevel(buffer);
return new TokenIdentifierValue(bytesValue.valueOf());
return new TokenIdentifierValue(bytesValue.toString());
}

encodeNested(tokenIdentifier: TokenIdentifierValue): Buffer {
let bytesValue = new BytesValue(tokenIdentifier.valueOf());
let bytesValue = BytesValue.fromUTF8(tokenIdentifier.valueOf());
return this.bytesCodec.encodeNested(bytesValue);
}

encodeTopLevel(tokenIdentifier: TokenIdentifierValue): Buffer {
return tokenIdentifier.valueOf();
return Buffer.from(tokenIdentifier.valueOf());
}
}
Loading

0 comments on commit f7f6182

Please sign in to comment.