Skip to content

Commit

Permalink
Merge pull request #262 from multiversx/main-into-12
Browse files Browse the repository at this point in the history
Merge main into feat/v12
  • Loading branch information
andreibancioiu authored Feb 20, 2023
2 parents 5a44a88 + 8aeeebe commit 2cb3cd0
Show file tree
Hide file tree
Showing 11 changed files with 1,358 additions and 69 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/package-lock-checks.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: Check package-lock.json

on:
pull_request:
branches: [ main ]
workflow_dispatch:

jobs:
Expand Down
15 changes: 7 additions & 8 deletions src/proto/serializer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { assert } from "chai";
import { ProtoSerializer } from "./serializer";
import { Transaction } from "../transaction";
import { loadTestWallets, TestWallet } from "../testutils";
import { Signature } from "../signature";
import { TransactionVersion } from "../networkParams";
import { TransactionPayload } from "../transactionPayload";
import BigNumber from "bignumber.js";
import { Signature } from "../signature";
import { loadTestWallets, TestWallet } from "../testutils";
import { TokenPayment } from "../tokenPayment";
import { Transaction } from "../transaction";
import { TransactionPayload } from "../transactionPayload";
import { ProtoSerializer } from "./serializer";

describe("serialize transactions", () => {
let wallets: Record<string, TestWallet>;
Expand Down Expand Up @@ -69,7 +68,7 @@ describe("serialize transactions", () => {
it("with data, with large value", async () => {
let transaction = new Transaction({
nonce: 92,
value: new BigNumber("123456789000000000000000000000"),
value: "123456789000000000000000000000",
sender: wallets.alice.address,
receiver: wallets.bob.address,
gasLimit: 100000,
Expand All @@ -86,7 +85,7 @@ describe("serialize transactions", () => {
it("with nonce = 0", async () => {
let transaction = new Transaction({
nonce: 0,
value: new BigNumber("0"),
value: "0",
sender: wallets.alice.address,
receiver: wallets.bob.address,
gasLimit: 80000,
Expand Down
31 changes: 28 additions & 3 deletions src/smartcontracts/typesystem/abiRegistry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { BytesType } from "./bytes";
import { EnumType } from "./enum";
import { ListType, OptionType } from "./generic";
import { ArrayVecType } from "./genericArray";
import { BigUIntType, I64Type, U32Type, U64Type, U8Type } from "./numerical";
import { BigUIntType, I64Type, U32Type, U64Type } from "./numerical";
import { StructType } from "./struct";
import { TokenIdentifierType } from "./tokenIdentifier";

Expand Down Expand Up @@ -98,13 +98,38 @@ describe("test abi registry", () => {
assert.equal(dummyType.getFieldDefinition("raw")!.type.getClassName(), ArrayVecType.ClassName);
});

it("should load ABI when custom types are out of order", async () => {
const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order.abi.json");
it("should load ABI when custom types are out of order (a)", async () => {
const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-a.abi.json");

assert.deepEqual(registry.getStruct("EsdtTokenPayment").getNamesOfDependencies(), ["EsdtTokenType", "TokenIdentifier", "u64", "BigUint"]);
assert.deepEqual(registry.getEnum("EsdtTokenType").getNamesOfDependencies(), []);
assert.deepEqual(registry.getStruct("TypeA").getNamesOfDependencies(), ["TypeB", "TypeC", "u64"]);
assert.deepEqual(registry.getStruct("TypeB").getNamesOfDependencies(), ["TypeC", "u64"]);
assert.deepEqual(registry.getStruct("TypeC").getNamesOfDependencies(), ["u64"]);
});

it("should load ABI when custom types are out of order (b)", async () => {
const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-b.abi.json");

assert.deepEqual(registry.getStruct("EsdtTokenPayment").getNamesOfDependencies(), ["EsdtTokenType", "TokenIdentifier", "u64", "BigUint"]);
assert.deepEqual(registry.getEnum("EsdtTokenType").getNamesOfDependencies(), []);
assert.deepEqual(registry.getStruct("TypeA").getNamesOfDependencies(), ["TypeB", "TypeC", "u64"]);
assert.deepEqual(registry.getStruct("TypeB").getNamesOfDependencies(), ["TypeC", "u64"]);
assert.deepEqual(registry.getStruct("TypeC").getNamesOfDependencies(), ["u64"]);
});

it("should load ABI when custom types are out of order (community example: c)", async () => {
const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-c.abi.json");

assert.lengthOf(registry.customTypes, 5);
assert.deepEqual(registry.getStruct("LoanCreateOptions").getNamesOfDependencies(), ["BigUint", "Address", "TokenIdentifier", "Status", "bytes"]);

});

it("should load ABI when custom types are out of order (community example: d)", async () => {
const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-d.abi.json");

assert.lengthOf(registry.customTypes, 12);
assert.deepEqual(registry.getStruct("AuctionItem").getNamesOfDependencies(), ["u64", "Address", "BigUint", "Option", "NftData", "bytes", "TokenIdentifier", "List"]);
});
});
74 changes: 30 additions & 44 deletions src/smartcontracts/typesystem/abiRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import * as errors from "../../errors";
import { guardValueIsSetWithMessage } from "../../utils";
import { StructType } from "./struct";
import { ContractInterface } from "./contractInterface";
import { CustomType } from "./types";
import { EndpointDefinition, EndpointParameterDefinition } from "./endpoint";
import { EnumType } from "./enum";
import { StructType } from "./struct";
import { TypeMapper } from "./typeMapper";
import { EndpointDefinition, EndpointParameterDefinition } from "./endpoint";
import { CustomType } from "./types";

export class AbiRegistry {
readonly interfaces: ContractInterface[] = [];
private customTypes: CustomType[] = [];
readonly customTypes: CustomType[] = [];

static create(json: { name: string; endpoints: any[]; types: Record<string, any> }): AbiRegistry {
let registry = new AbiRegistry().extend(json);
Expand All @@ -33,8 +33,6 @@ export class AbiRegistry {
this.customTypes.push(customType);
}

this.sortCustomTypesByDependencies();

return this;
}

Expand All @@ -48,40 +46,6 @@ export class AbiRegistry {
throw new errors.ErrTypingSystem(`Unknown type discriminant: ${typeDiscriminant}`);
}

private sortCustomTypesByDependencies() {
// Use of topological sort algorithm to sort custom types by dependencies.
let dependencies: { [key: string]: string[] } = {};
let visited: { [key: string]: boolean } = {};
this.customTypes.forEach((type: CustomType) => {
dependencies[type.getName()] = type.getNamesOfDependencies();
visited[type.getName()] = false;
});
let sortedArray = new Array<CustomType>();

const topologicalSortUtil = (name: string, visited: { [key: string]: boolean }, sortedArray: CustomType[]) => {
visited[name] = true;

for (const dependency of dependencies[name]) {
if (!this.customTypes.find((e) => e.getName() == dependency)) continue;
if (!visited[dependency]) {
topologicalSortUtil(dependency, visited, sortedArray);
}
}
const type = this.customTypes.find((e) => e.getName() == name);
if (type) {
sortedArray.push(type);
}
};

for (const type of this.customTypes) {
if (!visited[type.getName()]) {
topologicalSortUtil(type.getName(), visited, sortedArray);
}
}

this.customTypes = sortedArray;
}

getInterface(name: string): ContractInterface {
let result = this.interfaces.find((e) => e.name == name);
guardValueIsSetWithMessage(`interface [${name}] not found`, result);
Expand Down Expand Up @@ -129,13 +93,15 @@ export class AbiRegistry {

// First, remap custom types (actually, under the hood, this will remap types of struct fields)
for (const type of this.customTypes) {
const mappedTyped = mapper.mapType(type);
newCustomTypes.push(mappedTyped);
this.mapCustomTypeDepthFirst(type, this.customTypes, mapper, newCustomTypes);
}

if (this.customTypes.length != newCustomTypes.length) {
throw new errors.ErrTypingSystem("Did not re-map all custom types");
}

// Then, remap types of all endpoint parameters.
// But we'll use an enhanced mapper, that takes into account the results from the previous step.
mapper = new TypeMapper(newCustomTypes);
// The mapper learned all necessary types in the previous step.
for (const iface of this.interfaces) {
let newEndpoints: EndpointDefinition[] = [];
for (const endpoint of iface.endpoints) {
Expand All @@ -152,6 +118,26 @@ export class AbiRegistry {

return newRegistry;
}

private mapCustomTypeDepthFirst(typeToMap: CustomType, allTypesToMap: CustomType[], mapper: TypeMapper, mappedTypes: CustomType[]) {
const hasBeenMapped = mappedTypes.findIndex(type => type.getName() == typeToMap.getName()) >= 0;
if (hasBeenMapped) {
return;
}

for (const typeName of typeToMap.getNamesOfDependencies()) {
const dependencyType = allTypesToMap.find(type => type.getName() == typeName);
if (!dependencyType) {
// It's a type that we don't have to map (e.g. could be a primitive type).
continue;
}

this.mapCustomTypeDepthFirst(dependencyType, allTypesToMap, mapper, mappedTypes)
}

const mappedType = mapper.mapType(typeToMap);
mappedTypes.push(mappedType);
}
}

function mapEndpoint(endpoint: EndpointDefinition, mapper: TypeMapper): EndpointDefinition {
Expand Down
14 changes: 11 additions & 3 deletions src/smartcontracts/typesystem/typeMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,18 @@ export class TypeMapper {
}
}

/**
* Maps a "raw type" object to a "known (specific) type" object.
* In the process, it also learns the new type.
* Can only map types if their dependencies were previously learned (through mapping).
*/
mapType(type: Type): Type {
let mappedType = this.mapRecursiveType(type);
let mappedType = this.mapTypeRecursively(type);
if (mappedType) {
// We do not learn generic types (that also have type parameters)
// We do not learn generic types (that also have type parameters),
// we only learn closed, non-generic types.
// Reason: in the ABI, generic types are unnamed.
// E.g.: two occurrences of List<Foobar> aren't recognized as a single type (simplification).
if (!mappedType.isGenericType()) {
this.learnType(mappedType);
}
Expand All @@ -117,7 +125,7 @@ export class TypeMapper {
throw new errors.ErrTypingSystem(`Cannot map the type "${type.getName()}" to a known type`);
}

mapRecursiveType(type: Type): Type | null {
private mapTypeRecursively(type: Type): Type | null {
let isGeneric = type.isGenericType();

let previouslyLearnedType = this.learnedTypesMap.get(type.getName());
Expand Down
66 changes: 66 additions & 0 deletions src/testdata/custom-types-out-of-order-b.abi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "Sample",
"types": {
"EsdtTokenPayment": {
"type": "struct",
"fields": [
{
"name": "token_type",
"type": "EsdtTokenType"
},
{
"name": "token_identifier",
"type": "TokenIdentifier"
},
{
"name": "token_nonce",
"type": "u64"
},
{
"name": "amount",
"type": "BigUint"
}
]
},
"TypeC": {
"type": "struct",
"fields": [
{
"name": "foobar",
"type": "u64"
}
]
},
"EsdtTokenType": {
"type": "enum",
"variants": [
{
"name": "Fungible",
"discriminant": 0
},
{
"name": "NonFungible",
"discriminant": 1
}
]
},
"TypeB": {
"type": "struct",
"fields": [
{
"name": "c",
"type": "TypeC"
}
]
},
"TypeA": {
"type": "struct",
"fields": [
{
"name": "b",
"type": "TypeB"
}
]
}
}
}
Loading

0 comments on commit 2cb3cd0

Please sign in to comment.