Skip to content

Commit 8a213ed

Browse files
Merge pull request #208 from ElrondNetwork/fix-abi-order
[Regression] When loading the ABI, sort custom types by their type dependencies.
2 parents b9b6205 + e87cf03 commit 8a213ed

File tree

11 files changed

+187
-31
lines changed

11 files changed

+187
-31
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
77
## Unreleased
88
- TBD
99

10+
## 10.2.1
11+
- [When loading the ABI, sort custom types by their type dependencies](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/208)
12+
1013
## 10.2.0
1114
- [Define Transaction Factory and Gas Estimator](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/203)
1215
- [Fix type mapper / ABI registry (scenario: nested structs with ArrayN)](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/198)

package-lock.json

Lines changed: 34 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@elrondnetwork/erdjs",
3-
"version": "10.2.0",
3+
"version": "10.2.1",
44
"description": "Smart Contracts interaction framework",
55
"main": "out/index.js",
66
"types": "out/index.d.js",

src/smartcontracts/typesystem/abiRegistry.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,12 @@ describe("test abi registry", () => {
9797
assert.isTrue(dummyType == dummyTypeFromFooTypeFromBarType);
9898
assert.equal(dummyType.getFieldDefinition("raw")!.type.getClassName(), ArrayVecType.ClassName);
9999
});
100+
101+
it("should load ABI when custom types are out of order", async () => {
102+
const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order.abi.json");
103+
104+
assert.deepEqual(registry.getStruct("TypeA").getNamesOfDependencies(), ["TypeB", "TypeC", "u64"]);
105+
assert.deepEqual(registry.getStruct("TypeB").getNamesOfDependencies(), ["TypeC", "u64"]);
106+
assert.deepEqual(registry.getStruct("TypeC").getNamesOfDependencies(), ["u64"]);
107+
});
100108
});

src/smartcontracts/typesystem/abiRegistry.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ export class AbiRegistry {
1919

2020
private extend(json: { name: string; endpoints: any[]; types: any[] }): AbiRegistry {
2121
json.types = json.types || {};
22+
2223
// The "endpoints" collection is interpreted by "ContractInterface".
2324
let iface = ContractInterface.fromJSON(json);
2425
this.interfaces.push(iface);
26+
2527
for (const customTypeName in json.types) {
2628
let itemJson = json.types[customTypeName];
2729
let typeDiscriminant = itemJson.type;
@@ -30,8 +32,12 @@ export class AbiRegistry {
3032
let customType = this.createCustomType(typeDiscriminant, itemJson);
3133
this.customTypes.push(customType);
3234
}
35+
36+
this.sortCustomTypesByDependencies();
37+
3338
return this;
3439
}
40+
3541
private createCustomType(typeDiscriminant: string, json: any): CustomType {
3642
if (typeDiscriminant == "struct") {
3743
return StructType.fromJSON(json);
@@ -41,30 +47,50 @@ export class AbiRegistry {
4147
}
4248
throw new errors.ErrTypingSystem(`Unknown type discriminant: ${typeDiscriminant}`);
4349
}
50+
51+
private sortCustomTypesByDependencies() {
52+
this.customTypes.sort((a: CustomType, b: CustomType) => {
53+
const bDependsonA = b.getNamesOfDependencies().indexOf(a.getName()) > -1;
54+
if (bDependsonA) {
55+
// Sort "a" before "b".
56+
return -1;
57+
}
58+
59+
// Keep original order.
60+
return 0;
61+
});
62+
}
63+
4464
getInterface(name: string): ContractInterface {
4565
let result = this.interfaces.find((e) => e.name == name);
4666
guardValueIsSetWithMessage(`interface [${name}] not found`, result);
4767
return result!;
4868
}
69+
4970
getInterfaces(names: string[]): ContractInterface[] {
5071
return names.map((name) => this.getInterface(name));
5172
}
73+
5274
getStruct(name: string): StructType {
5375
let result = this.customTypes.find((e) => e.getName() == name && e.hasExactClass(StructType.ClassName));
5476
guardValueIsSetWithMessage(`struct [${name}] not found`, result);
5577
return <StructType>result!;
5678
}
79+
5780
getStructs(names: string[]): StructType[] {
5881
return names.map((name) => this.getStruct(name));
5982
}
83+
6084
getEnum(name: string): EnumType {
6185
let result = this.customTypes.find((e) => e.getName() == name && e.hasExactClass(EnumType.ClassName));
6286
guardValueIsSetWithMessage(`enum [${name}] not found`, result);
6387
return <EnumType>result!;
6488
}
89+
6590
getEnums(names: string[]): EnumType[] {
6691
return names.map((name) => this.getEnum(name));
6792
}
93+
6894
/**
6995
* Right after loading ABI definitions into a registry, the endpoints and the custom types (structs, enums)
7096
* use raw types for their I/O parameters (in the case of endpoints), or for their fields (in the case of structs).
@@ -79,11 +105,13 @@ export class AbiRegistry {
79105
let mapper = new TypeMapper([]);
80106
let newCustomTypes: CustomType[] = [];
81107
let newInterfaces: ContractInterface[] = [];
108+
82109
// First, remap custom types (actually, under the hood, this will remap types of struct fields)
83110
for (const type of this.customTypes) {
84111
const mappedTyped = mapper.mapType(type);
85112
newCustomTypes.push(mappedTyped);
86113
}
114+
87115
// Then, remap types of all endpoint parameters.
88116
// But we'll use an enhanced mapper, that takes into account the results from the previous step.
89117
mapper = new TypeMapper(newCustomTypes);
@@ -95,6 +123,7 @@ export class AbiRegistry {
95123
let newConstructor = iface.constructorDefinition ? mapEndpoint(iface.constructorDefinition, mapper) : null;
96124
newInterfaces.push(new ContractInterface(iface.name, newConstructor, newEndpoints));
97125
}
126+
98127
// Now return the new registry, with all types remapped to known types
99128
let newRegistry = new AbiRegistry();
100129
newRegistry.customTypes.push(...newCustomTypes);

src/smartcontracts/typesystem/enum.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ export class EnumType extends CustomType {
3434
guardValueIsSet(`variant by name (${name})`, result);
3535
return result!;
3636
}
37+
38+
getNamesOfDependencies(): string[] {
39+
const dependencies: string[] = [];
40+
41+
for (const variant of this.variants) {
42+
dependencies.push(...variant.getNamesOfDependencies());
43+
}
44+
45+
return [...new Set(dependencies)];
46+
}
3747
}
3848

3949
export class EnumVariantDefinition {
@@ -64,6 +74,10 @@ export class EnumVariantDefinition {
6474
getFieldDefinition(name: string): FieldDefinition | undefined {
6575
return this.fieldsDefinitions.find(item => item.name == name);
6676
}
77+
78+
getNamesOfDependencies(): string[] {
79+
return Fields.getNamesOfTypeDependencies(this.fieldsDefinitions);
80+
}
6781
}
6882

6983
export class EnumValue extends TypedValue {

src/smartcontracts/typesystem/fields.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,15 @@ export class Fields {
7474

7575
return true;
7676
}
77+
78+
static getNamesOfTypeDependencies(definitions: FieldDefinition[]): string[] {
79+
const dependencies: string[] = [];
80+
81+
for (const definition of definitions) {
82+
dependencies.push(definition.type.getName());
83+
dependencies.push(...definition.type.getNamesOfDependencies());
84+
}
85+
86+
return [...new Set(dependencies)];
87+
}
7788
}

src/smartcontracts/typesystem/struct.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export class StructType extends CustomType {
2727
getFieldDefinition(name: string): FieldDefinition | undefined {
2828
return this.fieldsDefinitions.find(item => item.name == name);
2929
}
30+
31+
getNamesOfDependencies(): string[] {
32+
return Fields.getNamesOfTypeDependencies(this.fieldsDefinitions);
33+
}
3034
}
3135

3236
export class Struct extends TypedValue {

src/smartcontracts/typesystem/types.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,10 @@ describe("test types", () => {
6666
assert.deepEqual(new BytesType().getClassHierarchy(), ["Type", "PrimitiveType", "BytesType"]);
6767
assert.deepEqual(new BytesValue(Buffer.from("foobar")).getClassHierarchy(), ["TypedValue", "PrimitiveValue", "BytesValue"]);
6868
});
69+
70+
it("should report type dependencies", () => {
71+
assert.deepEqual(parser.parse("MultiResultVec<u32>").getNamesOfDependencies(), ["u32"]);
72+
assert.deepEqual(parser.parse("tuple2<Address,BigUint>").getNamesOfDependencies(), ["Address", "BigUint"]);
73+
assert.deepEqual(parser.parse("Option<FooBar>").getNamesOfDependencies(), ["FooBar"]);
74+
});
6975
});

src/smartcontracts/typesystem/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,17 @@ export class Type {
133133
return fullyQualifiedNames;
134134
}
135135

136+
getNamesOfDependencies(): string[] {
137+
const dependencies: string[] = [];
138+
139+
for (const type of this.typeParameters) {
140+
dependencies.push(type.getName());
141+
dependencies.push(...type.getNamesOfDependencies());
142+
}
143+
144+
return [...new Set(dependencies)];
145+
}
146+
136147
/**
137148
* Converts the account to a pretty, plain JavaScript object.
138149
*/

0 commit comments

Comments
 (0)