Skip to content

Commit 22a90b8

Browse files
authored
Merge pull request #43 from itsyogesh/fix/weight-autofill-encoding
Fix weight_limit auto-fill encoding error after gas estimation
2 parents ee246d9 + 8024ee1 commit 22a90b8

3 files changed

Lines changed: 118 additions & 26 deletions

File tree

__tests__/lib/codec.test.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,35 @@ import {
1111
decodeArgLegacy,
1212
decodeAllArgs,
1313
decomposeArgHex,
14+
normalizeFieldName,
1415
} from "../../lib/codec";
1516
import { createMockDedotClient } from "../helpers/mock-client";
1617

18+
// ---------------------------------------------------------------------------
19+
// normalizeFieldName
20+
// ---------------------------------------------------------------------------
21+
describe("normalizeFieldName", () => {
22+
it("converts snake_case to camelCase", () => {
23+
expect(normalizeFieldName("ref_time")).toBe("refTime");
24+
expect(normalizeFieldName("proof_size")).toBe("proofSize");
25+
expect(normalizeFieldName("storage_deposit_limit")).toBe("storageDepositLimit");
26+
});
27+
28+
it("preserves already camelCase names", () => {
29+
expect(normalizeFieldName("refTime")).toBe("refTime");
30+
expect(normalizeFieldName("proofSize")).toBe("proofSize");
31+
});
32+
33+
it("handles hash prefix replacement", () => {
34+
expect(normalizeFieldName("#field_name")).toBe("fieldName");
35+
});
36+
37+
it("handles single word names", () => {
38+
expect(normalizeFieldName("value")).toBe("value");
39+
expect(normalizeFieldName("dest")).toBe("dest");
40+
});
41+
});
42+
1743
// ---------------------------------------------------------------------------
1844
// encodeArg
1945
// ---------------------------------------------------------------------------
@@ -827,6 +853,82 @@ describe("decomposeArgHex", () => {
827853
}
828854
});
829855

856+
it("normalizes snake_case metadata field names to camelCase for value lookup", () => {
857+
// This is the exact scenario from gas estimation auto-fill:
858+
// Metadata has snake_case fields (ref_time, proof_size) but the auto-filled
859+
// value object uses camelCase (refTime, proofSize) matching Dedot's convention.
860+
const tryEncode = jest.fn().mockReturnValue(new Uint8Array([0x01]));
861+
const client = createMockDedotClient({
862+
registry: {
863+
findCodec: jest.fn().mockReturnValue({
864+
tryEncode,
865+
tryDecode: jest.fn(),
866+
}),
867+
findType: jest.fn().mockReturnValue({
868+
typeDef: {
869+
type: "Struct",
870+
value: {
871+
fields: [
872+
{ name: "ref_time", typeId: 10 },
873+
{ name: "proof_size", typeId: 20 },
874+
],
875+
},
876+
},
877+
}),
878+
},
879+
});
880+
// Value uses camelCase keys (as produced by gas estimation auto-fill)
881+
const result = decomposeArgHex(client, 1, {
882+
refTime: "117234441",
883+
proofSize: "135850",
884+
});
885+
expect(result.kind).toBe("compound");
886+
if (result.kind === "compound") {
887+
expect(result.compoundType).toBe("Struct");
888+
expect(result.children).toHaveLength(2);
889+
// Labels should be normalized to camelCase
890+
expect(result.children[0].label).toBe("refTime");
891+
expect(result.children[1].label).toBe("proofSize");
892+
// Encoder should have been called with coerced values (BigInt), not undefined
893+
expect(tryEncode).toHaveBeenCalledWith(BigInt("117234441"));
894+
expect(tryEncode).toHaveBeenCalledWith(BigInt("135850"));
895+
}
896+
});
897+
898+
it("handles already-camelCase metadata field names without double-normalizing", () => {
899+
const tryEncode = jest.fn().mockReturnValue(new Uint8Array([0x01]));
900+
const client = createMockDedotClient({
901+
registry: {
902+
findCodec: jest.fn().mockReturnValue({
903+
tryEncode,
904+
tryDecode: jest.fn(),
905+
}),
906+
findType: jest.fn().mockReturnValue({
907+
typeDef: {
908+
type: "Struct",
909+
value: {
910+
fields: [
911+
{ name: "refTime", typeId: 10 },
912+
{ name: "proofSize", typeId: 20 },
913+
],
914+
},
915+
},
916+
}),
917+
},
918+
});
919+
const result = decomposeArgHex(client, 1, {
920+
refTime: "100",
921+
proofSize: "200",
922+
});
923+
expect(result.kind).toBe("compound");
924+
if (result.kind === "compound") {
925+
expect(result.children[0].label).toBe("refTime");
926+
expect(result.children[1].label).toBe("proofSize");
927+
expect(tryEncode).toHaveBeenCalledWith(BigInt("100"));
928+
expect(tryEncode).toHaveBeenCalledWith(BigInt("200"));
929+
}
930+
});
931+
830932
it("decomposes Enum single-field variant", () => {
831933
const tryEncode = jest.fn().mockReturnValue(new Uint8Array([0x01]));
832934
const client = createMockDedotClient({

components/builder/extrinsic-builder.tsx

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -110,29 +110,11 @@ const ExtrinsicBuilder: React.FC<ExtrinsicBuilderProps> = ({
110110
useEffect(() => {
111111
if (!isReviveInstantiate || !gasEstimation.weightRequired || !tx) return;
112112

113-
// Resolve weight field names from metadata
114-
const weightField = tx.meta?.fields?.find((f) => f.name === "weight_limit");
115-
if (weightField) {
116-
try {
117-
const weightType = client.registry.findType(weightField.typeId);
118-
const { typeDef } = weightType;
119-
if (typeDef.type === "Struct" && typeDef.value.fields.length >= 2) {
120-
const [field0, field1] = typeDef.value.fields;
121-
const name0 = String(field0.name);
122-
const name1 = String(field1.name);
123-
builderForm.setValue("weight_limit", {
124-
[name0]: String(gasEstimation.weightRequired.refTime),
125-
[name1]: String(gasEstimation.weightRequired.proofSize),
126-
});
127-
}
128-
} catch {
129-
// Fallback: use camelCase names
130-
builderForm.setValue("weight_limit", {
131-
refTime: String(gasEstimation.weightRequired.refTime),
132-
proofSize: String(gasEstimation.weightRequired.proofSize),
133-
});
134-
}
135-
}
113+
// Auto-fill weight_limit with camelCase keys (Dedot codec convention)
114+
builderForm.setValue("weight_limit", {
115+
refTime: String(gasEstimation.weightRequired.refTime),
116+
proofSize: String(gasEstimation.weightRequired.proofSize),
117+
});
136118

137119
// Auto-fill storage deposit only for Charge
138120
if (gasEstimation.storageDeposit?.type === "Charge") {

lib/codec.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import { DedotClient } from "dedot";
2-
import { u8aToHex, hexToU8a, hexStripPrefix, hexAddPrefix, decodeAddress } from "dedot/utils";
2+
import { u8aToHex, hexToU8a, hexStripPrefix, hexAddPrefix, decodeAddress, stringCamelCase } from "dedot/utils";
3+
4+
/**
5+
* Normalize a metadata field name to camelCase, matching Dedot's internal convention.
6+
* Metadata uses snake_case (ref_time, proof_size), but Dedot codecs expect camelCase.
7+
*/
8+
export function normalizeFieldName(name: string): string {
9+
return stringCamelCase(name.replace("#", "_"));
10+
}
311

412
/**
513
* Result type for encoding operations
@@ -541,7 +549,7 @@ export function decomposeArgHex(
541549
if (fields.length > 0 && typeof value === "object" && value !== null && !Array.isArray(value)) {
542550
const obj = value as Record<string, unknown>;
543551
const children: HexChildItem[] = fields.map((field) => {
544-
const fieldName = field.name || "";
552+
const fieldName = normalizeFieldName(field.name || "");
545553
const fieldValue = obj[fieldName];
546554
const result = encodeArg(client, field.typeId, fieldValue);
547555
return {
@@ -577,7 +585,7 @@ export function decomposeArgHex(
577585
if (typeof enumObj.value === "object" && enumObj.value !== null) {
578586
const obj = enumObj.value as Record<string, unknown>;
579587
const children: HexChildItem[] = variant.fields.map((field) => {
580-
const fieldName = field.name || "";
588+
const fieldName = normalizeFieldName(field.name || "");
581589
const fieldValue = obj[fieldName];
582590
const result = encodeArg(client, field.typeId, fieldValue);
583591
return {

0 commit comments

Comments
 (0)