Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add native support of exotic cells #343

Closed
wants to merge 6 commits into from
Closed
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
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"lvalues",
"masterchain",
"maxint",
"merkle",
"minmax",
"mintable",
"mktemp",
Expand Down
8 changes: 8 additions & 0 deletions src/abi/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,12 @@ export const contractErrors = {
id: 137,
message: "Masterchain support is not enabled for this contract",
},
expectedExoticCell: {
id: 138,
message: "Expected exotic cell, got regular cell",
},
invalidExoticCellType: {
id: 139,
message: "Invalid exotic cell type",
},
};
42 changes: 26 additions & 16 deletions src/bindings/typescript/serializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,15 +280,23 @@ const addressSerializer: Serializer<{ optional: boolean }> = {
},
};

function isCellType(vKind: string) {
return vKind === "cell" || vKind === "merkleProof";
}

function getCellLikeTsType(v: {
kind: "cell" | "slice" | "builder";
kind: "cell" | "slice" | "builder" | "merkleProof";
optional?: boolean;
}) {
return v.kind == "cell" ? "Cell" : v.kind == "slice" ? "Slice" : "Builder";
return isCellType(v.kind)
? "Cell"
: v.kind === "slice"
? "Slice"
: "Builder";
}

function getCellLikeTsAsMethod(v: {
kind: "cell" | "slice" | "builder";
kind: "cell" | "slice" | "builder" | "merkleProof";
optional?: boolean;
}) {
if (v.optional) {
Expand All @@ -299,7 +307,7 @@ function getCellLikeTsAsMethod(v: {
}

const cellSerializer: Serializer<{
kind: "cell" | "slice" | "builder";
kind: "cell" | "slice" | "builder" | "merkleProof";
optional: boolean;
}> = {
tsType(v) {
Expand All @@ -312,44 +320,44 @@ const cellSerializer: Serializer<{
tsLoad(v, slice, field, w) {
if (v.optional) {
w.append(
`let ${field} = ${slice}.loadBit() ? ${slice}.loadRef()${v.kind !== "cell" ? getCellLikeTsAsMethod(v) : ""} : null;`,
`let ${field} = ${slice}.loadBit() ? ${slice}.loadRef()${!isCellType(v.kind) ? getCellLikeTsAsMethod(v) : ""} : null;`,
);
} else {
w.append(
`let ${field} = ${slice}.loadRef()${v.kind !== "cell" ? getCellLikeTsAsMethod(v) : ""};`,
`let ${field} = ${slice}.loadRef()${!isCellType(v.kind) ? getCellLikeTsAsMethod(v) : ""};`,
);
}
},
tsLoadTuple(v, reader, field, w) {
if (v.optional) {
w.append(
`let ${field} = ${reader}.readCellOpt()${v.kind !== "cell" ? getCellLikeTsAsMethod(v) : ""};`,
`let ${field} = ${reader}.readCellOpt()${!isCellType(v.kind) ? getCellLikeTsAsMethod(v) : ""};`,
);
} else {
w.append(
`let ${field} = ${reader}.readCell()${v.kind !== "cell" ? getCellLikeTsAsMethod(v) : ""};`,
`let ${field} = ${reader}.readCell()${!isCellType(v.kind) ? getCellLikeTsAsMethod(v) : ""};`,
);
}
},
tsStore(v, builder, field, w) {
if (v.optional) {
w.append(
`if (${field} !== null && ${field} !== undefined) { ${builder}.storeBit(true).storeRef(${field}${v.kind !== "cell" ? ".asCell()" : ""}); } else { ${builder}.storeBit(false); }`,
`if (${field} !== null && ${field} !== undefined) { ${builder}.storeBit(true).storeRef(${field}${!isCellType(v.kind) ? ".asCell()" : ""}); } else { ${builder}.storeBit(false); }`,
);
} else {
w.append(
`${builder}.storeRef(${field}${v.kind !== "cell" ? ".asCell()" : ""});`,
`${builder}.storeRef(${field}${!isCellType(v.kind) ? ".asCell()" : ""});`,
);
}
},
tsStoreTuple(v, to, field, w) {
if (v.optional) {
w.append(
`${to}.write${getCellLikeTsType(v)}(${field}${v.kind !== "cell" ? "?.asCell()" : ""});`,
`${to}.write${getCellLikeTsType(v)}(${field}${!isCellType(v.kind) ? "?.asCell()" : ""});`,
);
} else {
w.append(
`${to}.write${getCellLikeTsType(v)}(${field}${v.kind !== "cell" ? ".asCell()" : ""});`,
`${to}.write${getCellLikeTsType(v)}(${field}${!isCellType(v.kind) ? ".asCell()" : ""});`,
);
}
},
Expand All @@ -358,12 +366,14 @@ const cellSerializer: Serializer<{
if (
src.type === "cell" ||
src.type === "slice" ||
src.type === "builder"
src.type === "builder" ||
src.type === "merkleProof"
) {
if (
src.format === null ||
src.format === undefined ||
src.format === "ref"
src.format === "ref" ||
src.type === "merkleProof"
) {
return {
optional: src.optional ? src.optional : false,
Expand All @@ -388,7 +398,7 @@ const remainderSerializer: Serializer<{ kind: "cell" | "slice" | "builder" }> =
},
tsLoadTuple(v, reader, field, w) {
w.append(
`let ${field} = ${reader}.readCell()${v.kind !== "cell" ? getCellLikeTsAsMethod(v) : ""};`,
`let ${field} = ${reader}.readCell()${!isCellType(v.kind) ? getCellLikeTsAsMethod(v) : ""};`,
);
},
tsStore(v, builder, field, w) {
Expand All @@ -398,7 +408,7 @@ const remainderSerializer: Serializer<{ kind: "cell" | "slice" | "builder" }> =
},
tsStoreTuple(v, to, field, w) {
w.append(
`${to}.write${getCellLikeTsType(v)}(${field}${v.kind !== "cell" ? ".asCell()" : ""});`,
`${to}.write${getCellLikeTsType(v)}(${field}${!isCellType(v.kind) ? ".asCell()" : ""});`,
);
},
abiMatcher(src) {
Expand Down
2 changes: 2 additions & 0 deletions src/generator/writers/resolveFuncFlatPack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export function resolveFuncFlatPack(
resolveFuncFlatPack(v.type, name + `'` + v.name, ctx),
);
}
} else if (descriptor.kind === "exotic") {
return ["int", "int", "cell"];
}

// Unreachable
Expand Down
2 changes: 2 additions & 0 deletions src/generator/writers/resolveFuncFlatTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export function resolveFuncFlatTypes(
resolveFuncFlatTypes(v.type, ctx),
);
}
} else if (descriptor.kind === "exotic") {
return ["int", "int", "cell"];
}

// Unreachable
Expand Down
11 changes: 11 additions & 0 deletions src/generator/writers/resolveFuncType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ export function resolveFuncType(
")"
);
}
} else if (descriptor.kind === "exotic") {
const t = getType(ctx.ctx, descriptor.struct);
return (
"int, int, (" +
t.fields
.map((v) =>
resolveFuncType(v.type, ctx, false, usePartialFields),
)
.join(", ") +
")"
);
}

// Unreachable
Expand Down
12 changes: 12 additions & 0 deletions src/generator/writers/resolveFuncTypeFromAbi.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ABITypeRef } from "@ton/core";
import { getType } from "../../types/resolveDescriptors";
import { WriterContext } from "../Writer";
import { throwInternalCompilerError } from "../../errors";

export function resolveFuncTypeFromAbi(
fields: ABITypeRef[],
Expand Down Expand Up @@ -36,6 +37,17 @@ export function resolveFuncTypeFromAbi(
res.push("slice");
} else if (f.type === "string") {
res.push("slice");
} else if (f.type === "merkleProof") {
if (typeof f.format !== "string") {
throwInternalCompilerError(
"Expected string format for merkleProof",
); // should never happen
}
const t = getType(ctx.ctx, f.format);
res.push("int");
res.push("int");
const loaded = t.fields.map((v) => v.abi.type);
res.push(resolveFuncTypeFromAbi(loaded, ctx));
} else {
const t = getType(ctx.ctx, f.type);
if (t.kind !== "struct") {
Expand Down
2 changes: 2 additions & 0 deletions src/generator/writers/resolveFuncTypeFromAbiUnpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export function resolveFuncTypeFromAbiUnpack(
res.push(`${name}'${f.name}`);
} else if (f.type.type === "string") {
res.push(`${name}'${f.name}`);
} else if (f.type.type === "merkleProof") {
res.push(`${name}'${f.name}`);
} else {
const t = getType(ctx.ctx, f.type.type);
if (f.type.optional ?? t.fields.length === 0) {
Expand Down
17 changes: 17 additions & 0 deletions src/generator/writers/resolveFuncTypeUnpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,23 @@ export function resolveFuncTypeUnpack(
")"
);
}
} else if (descriptor.kind === "exotic") {
const t = getType(ctx.ctx, descriptor.struct);
return (
`${name}'rootHash, ${name}'depth, (` +
t.fields
.map((v) => {
return resolveFuncTypeUnpack(
v.type,
name + `'data'` + v.name,
ctx,
false,
usePartialFields,
);
})
.join(", ") +
")"
);
}

// Unreachable
Expand Down
36 changes: 35 additions & 1 deletion src/generator/writers/writeExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,13 +432,47 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string {
const src = getExpType(wCtx.ctx, f.aggregate);
if (
(src.kind !== "ref" || src.optional) &&
src.kind !== "ref_bounced"
src.kind !== "ref_bounced" &&
src.kind !== "exotic"
) {
throwCompilationError(
`Cannot access field of non-struct type: "${printTypeRef(src)}"`,
f.loc,
);
}

if (src.kind === "exotic") {
// exotics have fields `rootHash` (int), `depth` (int) and `data` (arbitrary struct)
// they are accessed exactly the same as struct fields
const path = tryExtractPath(f);
if (path) {
// if we are accessing `data` field, we need to unpack the struct
if (path[path.length - 1]?.text === "data") {
const idd = writePathExpression(path);
const t = getType(wCtx.ctx, src.struct);
return (
"(" +
t.fields
.map((v) =>
resolveFuncTypeUnpack(
v.type,
`${idd}'${v.name}`,
wCtx,
false,
),
)
.join(", ") +
")"
);
}
return writePathExpression(path);
}
throwCompilationError(
`Cannot access field of exotic type: "${printTypeRef(src)}"`,
f.loc,
);
}

const srcT = getType(wCtx.ctx, src.name);

// Resolve field
Expand Down
38 changes: 33 additions & 5 deletions src/generator/writers/writeSerialization.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { contractErrors } from "../../abi/errors";
import { throwInternalCompilerError } from "../../errors";
import { dummySrcInfo, ItemOrigin } from "../../grammar/grammar";
import { ItemOrigin } from "../../grammar/grammar";
import { AllocationCell, AllocationOperation } from "../../storage/operation";
import { StorageAllocation } from "../../storage/StorageAllocation";
import { getType } from "../../types/resolveDescriptors";
Expand Down Expand Up @@ -305,9 +304,11 @@ function writeSerializerField(
}
return;
}
case "merkle-proof": {
ctx.append(`build_${gen} = build_${gen}.store_ref(${fieldName});`);
return;
}
}

throwInternalCompilerError(`Unsupported field kind`, dummySrcInfo);
}

//
Expand Down Expand Up @@ -350,7 +351,15 @@ export function writeParser(
ctx.append(`return (sc_0, null());`);
} else {
ctx.append(
`return (sc_0, (${allocation.ops.map((v) => `v'${v.name}`).join(", ")}));`,
`return (sc_0, (${allocation.ops
.map((v) => {
if (v.op.kind == "merkle-proof") {
return `v'${v.name}'rootHash, v'${v.name}'depth, v'${v.name}'data`;
} else {
return `v'${v.name}`;
}
})
.join(", ")}));`,
);
}
});
Expand Down Expand Up @@ -643,5 +652,24 @@ function writeFieldParser(
}
return;
}
case "merkle-proof": {
const name = `v'${f.name}`;
ctx.append(
`var (${name}, exotic?) = sc_${gen}~load_ref().begin_parse_exotic();`,
);
ctx.append(
`throw_unless(${contractErrors.expectedExoticCell.id}, exotic?);`,
);
ctx.append(
`throw_unless(${contractErrors.invalidExoticCellType.id}, ${name}~load_uint(8) == 3);`,
);
ctx.append(`var ${name}'rootHash = ${name}~load_uint(256);`);
ctx.append(`var ${name}'depth = ${name}~load_uint(16);`);
ctx.append(`var sc = ${name}~load_ref().begin_parse();`);
ctx.append(
`var ${name}'data = sc~${ops.reader(op.struct, ctx)}();`,
);
return;
}
}
}
Loading
Loading