Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
## Unreleased
- TBD

## 10.2.1
- [When loading the ABI, sort custom types by their type dependencies](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/208)

## 10.2.0
- [Define Transaction Factory and Gas Estimator](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/203)
- [Fix type mapper / ABI registry (scenario: nested structs with ArrayN)](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/198)
Expand Down
64 changes: 34 additions & 30 deletions 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": "10.2.0",
"version": "10.2.1",
"description": "Smart Contracts interaction framework",
"main": "out/index.js",
"types": "out/index.d.js",
Expand Down
8 changes: 8 additions & 0 deletions src/smartcontracts/typesystem/abiRegistry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,12 @@ describe("test abi registry", () => {
assert.isTrue(dummyType == dummyTypeFromFooTypeFromBarType);
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");

assert.deepEqual(registry.getStruct("TypeA").getNamesOfDependencies(), ["TypeB", "TypeC", "u64"]);
assert.deepEqual(registry.getStruct("TypeB").getNamesOfDependencies(), ["TypeC", "u64"]);
assert.deepEqual(registry.getStruct("TypeC").getNamesOfDependencies(), ["u64"]);
});
});
29 changes: 29 additions & 0 deletions src/smartcontracts/typesystem/abiRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ export class AbiRegistry {

private extend(json: { name: string; endpoints: any[]; types: any[] }): AbiRegistry {
json.types = json.types || {};

// The "endpoints" collection is interpreted by "ContractInterface".
let iface = ContractInterface.fromJSON(json);
this.interfaces.push(iface);

for (const customTypeName in json.types) {
let itemJson = json.types[customTypeName];
let typeDiscriminant = itemJson.type;
Expand All @@ -30,8 +32,12 @@ export class AbiRegistry {
let customType = this.createCustomType(typeDiscriminant, itemJson);
this.customTypes.push(customType);
}

this.sortCustomTypesByDependencies();

return this;
}

private createCustomType(typeDiscriminant: string, json: any): CustomType {
if (typeDiscriminant == "struct") {
return StructType.fromJSON(json);
Expand All @@ -41,30 +47,50 @@ export class AbiRegistry {
}
throw new errors.ErrTypingSystem(`Unknown type discriminant: ${typeDiscriminant}`);
}

private sortCustomTypesByDependencies() {
this.customTypes.sort((a: CustomType, b: CustomType) => {
const bDependsonA = b.getNamesOfDependencies().indexOf(a.getName()) > -1;
if (bDependsonA) {
// Sort "a" before "b".
return -1;
}

// Keep original order.
return 0;
});
}

getInterface(name: string): ContractInterface {
let result = this.interfaces.find((e) => e.name == name);
guardValueIsSetWithMessage(`interface [${name}] not found`, result);
return result!;
}

getInterfaces(names: string[]): ContractInterface[] {
return names.map((name) => this.getInterface(name));
}

getStruct(name: string): StructType {
let result = this.customTypes.find((e) => e.getName() == name && e.hasExactClass(StructType.ClassName));
guardValueIsSetWithMessage(`struct [${name}] not found`, result);
return <StructType>result!;
}

getStructs(names: string[]): StructType[] {
return names.map((name) => this.getStruct(name));
}

getEnum(name: string): EnumType {
let result = this.customTypes.find((e) => e.getName() == name && e.hasExactClass(EnumType.ClassName));
guardValueIsSetWithMessage(`enum [${name}] not found`, result);
return <EnumType>result!;
}

getEnums(names: string[]): EnumType[] {
return names.map((name) => this.getEnum(name));
}

/**
* Right after loading ABI definitions into a registry, the endpoints and the custom types (structs, enums)
* use raw types for their I/O parameters (in the case of endpoints), or for their fields (in the case of structs).
Expand All @@ -79,11 +105,13 @@ export class AbiRegistry {
let mapper = new TypeMapper([]);
let newCustomTypes: CustomType[] = [];
let newInterfaces: ContractInterface[] = [];

// 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);
}

// 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);
Expand All @@ -95,6 +123,7 @@ export class AbiRegistry {
let newConstructor = iface.constructorDefinition ? mapEndpoint(iface.constructorDefinition, mapper) : null;
newInterfaces.push(new ContractInterface(iface.name, newConstructor, newEndpoints));
}

// Now return the new registry, with all types remapped to known types
let newRegistry = new AbiRegistry();
newRegistry.customTypes.push(...newCustomTypes);
Expand Down
14 changes: 14 additions & 0 deletions src/smartcontracts/typesystem/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ export class EnumType extends CustomType {
guardValueIsSet(`variant by name (${name})`, result);
return result!;
}

getNamesOfDependencies(): string[] {
const dependencies: string[] = [];

for (const variant of this.variants) {
dependencies.push(...variant.getNamesOfDependencies());
}

return [...new Set(dependencies)];
}
}

export class EnumVariantDefinition {
Expand Down Expand Up @@ -64,6 +74,10 @@ export class EnumVariantDefinition {
getFieldDefinition(name: string): FieldDefinition | undefined {
return this.fieldsDefinitions.find(item => item.name == name);
}

getNamesOfDependencies(): string[] {
return Fields.getNamesOfTypeDependencies(this.fieldsDefinitions);
}
}

export class EnumValue extends TypedValue {
Expand Down
11 changes: 11 additions & 0 deletions src/smartcontracts/typesystem/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,15 @@ export class Fields {

return true;
}

static getNamesOfTypeDependencies(definitions: FieldDefinition[]): string[] {
const dependencies: string[] = [];

for (const definition of definitions) {
dependencies.push(definition.type.getName());
dependencies.push(...definition.type.getNamesOfDependencies());
}

return [...new Set(dependencies)];
}
}
4 changes: 4 additions & 0 deletions src/smartcontracts/typesystem/struct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export class StructType extends CustomType {
getFieldDefinition(name: string): FieldDefinition | undefined {
return this.fieldsDefinitions.find(item => item.name == name);
}

getNamesOfDependencies(): string[] {
return Fields.getNamesOfTypeDependencies(this.fieldsDefinitions);
}
}

export class Struct extends TypedValue {
Expand Down
6 changes: 6 additions & 0 deletions src/smartcontracts/typesystem/types.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,10 @@ describe("test types", () => {
assert.deepEqual(new BytesType().getClassHierarchy(), ["Type", "PrimitiveType", "BytesType"]);
assert.deepEqual(new BytesValue(Buffer.from("foobar")).getClassHierarchy(), ["TypedValue", "PrimitiveValue", "BytesValue"]);
});

it("should report type dependencies", () => {
assert.deepEqual(parser.parse("MultiResultVec<u32>").getNamesOfDependencies(), ["u32"]);
assert.deepEqual(parser.parse("tuple2<Address,BigUint>").getNamesOfDependencies(), ["Address", "BigUint"]);
assert.deepEqual(parser.parse("Option<FooBar>").getNamesOfDependencies(), ["FooBar"]);
});
});
11 changes: 11 additions & 0 deletions src/smartcontracts/typesystem/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,17 @@ export class Type {
return fullyQualifiedNames;
}

getNamesOfDependencies(): string[] {
const dependencies: string[] = [];

for (const type of this.typeParameters) {
dependencies.push(type.getName());
dependencies.push(...type.getNamesOfDependencies());
}

return [...new Set(dependencies)];
}

/**
* Converts the account to a pretty, plain JavaScript object.
*/
Expand Down
Loading