Skip to content

Commit

Permalink
feat/fix(ts): split serializers for Cell, Slice and Builder (#562)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gusarich authored Jul 12, 2024
1 parent 99ce477 commit 30f7be2
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 64 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `[DEBUG]` prefix was removed from debug prints because a similar prefix was already present: PR [#506](https://github.com/tact-lang/tact/pull/506)
- File paths in debug prints always use POSIX file paths (even on Windows): PR [#523](https://github.com/tact-lang/tact/pull/523)
- The IPFS ABI and supported interfaces getters are not generated by default; to generate those, set to `true` the two newly introduced per-project options in `tact.config.json`: `ipfsAbiGetter` and `interfacesGetter`: PR [#534](https://github.com/tact-lang/tact/pull/534)
- Values of `Slice` and `Builder` types are not converted to `Cell` in Typescript bindings anymore: PR [#562](https://github.com/tact-lang/tact/pull/562)
- Debug prints now include line content for better debugging experience: PR [#563](https://github.com/tact-lang/tact/pull/563)

### Fixed
Expand Down
5 changes: 4 additions & 1 deletion examples/wallet.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ describe("wallet", () => {
{
$$type: "TransferMessage",
transfer,
signature: beginCell().storeBuffer(signature).endCell(),
signature: beginCell()
.storeBuffer(signature)
.endCell()
.asSlice(),
},
);
await system.run();
Expand Down
80 changes: 55 additions & 25 deletions src/bindings/typescript/serializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,49 +279,77 @@ const addressSerializer: Serializer<{ optional: boolean }> = {
},
};

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

function getCellLikeTsAsMethod(v: {
kind: "cell" | "slice" | "builder";
optional?: boolean;
}) {
if (v.optional) {
return `?.as${getCellLikeTsType(v)}() ?? null`;
} else {
return `.as${getCellLikeTsType(v)}()`;
}
}

const cellSerializer: Serializer<{
kind: "cell" | "slice" | "builder";
optional: boolean;
}> = {
tsType(v) {
if (v.optional) {
return "Cell | null";
return `${getCellLikeTsType(v)} | null`;
} else {
return "Cell";
return getCellLikeTsType(v);
}
},
tsLoad(v, slice, field, w) {
if (v.optional) {
w.append(
`let ${field} = ${slice}.loadBit() ? ${slice}.loadRef() : null;`,
`let ${field} = ${slice}.loadBit() ? ${slice}.loadRef()${v.kind !== "cell" ? getCellLikeTsAsMethod(v) : ""} : null;`,
);
} else {
w.append(`let ${field} = ${slice}.loadRef();`);
w.append(
`let ${field} = ${slice}.loadRef()${v.kind !== "cell" ? getCellLikeTsAsMethod(v) : ""};`,
);
}
},
tsLoadTuple(v, reader, field, w) {
if (v.optional) {
w.append(`let ${field} = ${reader}.readCellOpt();`);
w.append(
`let ${field} = ${reader}.readCellOpt()${v.kind !== "cell" ? getCellLikeTsAsMethod(v) : ""};`,
);
} else {
w.append(`let ${field} = ${reader}.readCell();`);
w.append(
`let ${field} = ${reader}.readCell()${v.kind !== "cell" ? getCellLikeTsAsMethod(v) : ""};`,
);
}
},
tsStore(v, builder, field, w) {
if (v.optional) {
w.append(
`if (${field} !== null && ${field} !== undefined) { ${builder}.storeBit(true).storeRef(${field}); } else { ${builder}.storeBit(false); }`,
`if (${field} !== null && ${field} !== undefined) { ${builder}.storeBit(true).storeRef(${field}${v.kind !== "cell" ? ".asCell()" : ""}); } else { ${builder}.storeBit(false); }`,
);
} else {
w.append(`${builder}.storeRef(${field});`);
w.append(
`${builder}.storeRef(${field}${v.kind !== "cell" ? ".asCell()" : ""});`,
);
}
},
tsStoreTuple(v, to, field, w) {
if (v.kind === "cell") {
w.append(`${to}.writeCell(${field});`);
} else if (v.kind === "slice") {
w.append(`${to}.writeSlice(${field});`);
if (v.optional) {
w.append(
`${to}.write${getCellLikeTsType(v)}(${field}${v.kind !== "cell" ? "?.asCell()" : ""});`,
);
} else {
w.append(`${to}.writeBuilder(${field});`);
w.append(
`${to}.write${getCellLikeTsType(v)}(${field}${v.kind !== "cell" ? ".asCell()" : ""});`,
);
}
},
abiMatcher(src) {
Expand Down Expand Up @@ -349,26 +377,28 @@ const cellSerializer: Serializer<{

const remainderSerializer: Serializer<{ kind: "cell" | "slice" | "builder" }> =
{
tsType(_v) {
return "Cell";
tsType(v) {
return getCellLikeTsType(v);
},
tsLoad(v, slice, field, w) {
w.append(`let ${field} = ${slice}.asCell();`);
w.append(
`let ${field} = ${slice}${v.kind !== "slice" ? getCellLikeTsAsMethod(v) : ""};`,
);
},
tsLoadTuple(v, reader, field, w) {
w.append(`let ${field} = ${reader}.readCell();`);
w.append(
`let ${field} = ${reader}.readCell()${v.kind !== "cell" ? getCellLikeTsAsMethod(v) : ""};`,
);
},
tsStore(v, builder, field, w) {
w.append(`${builder}.storeBuilder(${field}.asBuilder());`);
w.append(
`${builder}.storeBuilder(${field}${v.kind !== "builder" ? ".asBuilder()" : ""});`,
);
},
tsStoreTuple(v, to, field, w) {
if (v.kind === "cell") {
w.append(`${to}.writeCell(${field});`);
} else if (v.kind === "slice") {
w.append(`${to}.writeSlice(${field});`);
} else {
w.append(`${to}.writeBuilder(${field});`);
}
w.append(
`${to}.write${getCellLikeTsType(v)}(${field}${v.kind !== "cell" ? ".asCell()" : ""});`,
);
},
abiMatcher(src) {
if (src.kind === "simple") {
Expand Down
2 changes: 1 addition & 1 deletion src/test/e2e-emulated/contracts/serialization-3.tact
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ message Update {
f: String;
}

contract SerializationTester {
contract SerializationTester3 {

a: Int;
b: Bool;
Expand Down
30 changes: 16 additions & 14 deletions src/test/e2e-emulated/dns.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ describe("dns", () => {
const internalAddress = convertToInternal(invalidName);
expect(
await contract.getDnsInternalVerify(
beginCell().storeBuffer(internalAddress).endCell(),
beginCell()
.storeBuffer(internalAddress)
.endCell()
.asSlice(),
),
).toBe(false);
});
Expand All @@ -97,8 +100,7 @@ describe("dns", () => {
it(`should convert valid name: ${validName}`, async () => {
const data = (await contract.getStringToInternal(validName))!;
const received = data
.beginParse()
.loadBuffer(data.bits.length / 8)
.loadBuffer(data.remainingBits / 8)
.toString("hex");
expect(received).toBe(
convertToInternal(
Expand Down Expand Up @@ -126,16 +128,14 @@ describe("dns", () => {
))!;
data1 = await contract.getInternalNormalize(data1);
const received1 = data1
.beginParse()
.loadBuffer(data1.bits.length / 8)
.loadBuffer(data1.remainingBits / 8)
.toString("hex");
let data2 = (await contract.getStringToInternal(
equalNormalizedElem[1]!,
))!;
data2 = await contract.getInternalNormalize(data2);
const received2 = data2
.beginParse()
.loadBuffer(data2.bits.length / 8)
.loadBuffer(data2.remainingBits / 8)
.toString("hex");
expect(received1).toBe(received2);
expect(received1.length).toBe(received2.length);
Expand All @@ -148,16 +148,14 @@ describe("dns", () => {
))!;
data1 = await contract.getInternalNormalize(data1);
const received1 = data1
.beginParse()
.loadBuffer(data1.bits.length / 8)
.loadBuffer(data1.remainingBits / 8)
.toString("hex");
let data2 = (await contract.getStringToInternal(
notEqualNormalizedElem[1]!,
))!;
data2 = await contract.getInternalNormalize(data2);
const received2 = data2
.beginParse()
.loadBuffer(data2.bits.length / 8)
.loadBuffer(data2.remainingBits / 8)
.toString("hex");
expect(received1).not.toBe(received2);
expect(received1.length).toBe(received2.length);
Expand All @@ -168,7 +166,7 @@ describe("dns", () => {
it("should resolve name " + validName, async () => {
const internalAddress = convertToInternal(validName);
const resolved = (await contract.getDnsresolve(
beginCell().storeBuffer(internalAddress).endCell(),
beginCell().storeBuffer(internalAddress).endCell().asSlice(),
1n,
))!;
expect(resolved.prefix).toBe(BigInt(internalAddress.length * 8));
Expand Down Expand Up @@ -198,7 +196,10 @@ describe("dns", () => {
const internalAddress = convertToInternal(invalidName);
await expect(
contract.getDnsresolve(
beginCell().storeBuffer(internalAddress).endCell(),
beginCell()
.storeBuffer(internalAddress)
.endCell()
.asSlice(),
1n,
),
).rejects.toThrowError();
Expand All @@ -216,7 +217,8 @@ describe("dns", () => {
.storeBuffer(
Buffer.concat([Buffer.alloc(1, 0), internalAddress]),
)
.endCell(),
.endCell()
.asSlice(),
1n,
))!;
expect(resolved.prefix).toBe(
Expand Down
2 changes: 1 addition & 1 deletion src/test/e2e-emulated/intrinsics.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ describe("intrinsics", () => {
expect(await contract.getGetHash2()).toBe(sha256("hello world"));
expect(
await contract.getGetHash3(
beginCell().storeStringTail("sometest").endCell(),
beginCell().storeStringTail("sometest").endCell().asSlice(),
),
).toBe(sha256("sometest"));
expect(await contract.getGetHash4("wallet")).toBe(sha256("wallet"));
Expand Down
2 changes: 1 addition & 1 deletion src/test/e2e-emulated/local-type-inference.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe("local-type-inference", () => {
expect((await contract.getTest7()).toString()).toStrictEqual(
beginCell().storeUint(123, 64).endCell().toString(),
);
expect((await contract.getTest8()).toString()).toStrictEqual(
expect((await contract.getTest8()).asCell().toString()).toStrictEqual(
beginCell().storeUint(123, 64).endCell().toString(),
);
expect(await contract.getTest9()).toStrictEqual("hello");
Expand Down
10 changes: 6 additions & 4 deletions src/test/e2e-emulated/masterchain.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ describe("masterchain", () => {
expect(
(
await contract.getParseAddress(
beginCell().storeAddress(addr).endCell(),
beginCell().storeAddress(addr).endCell().asSlice(),
)
).equals(addr),
).toBe(true);
Expand All @@ -122,7 +122,9 @@ describe("masterchain", () => {
await system.run();
const addr = new Address(-1, Buffer.alloc(32, 0));
void expect(
contract.getParseAddress(beginCell().storeAddress(addr).endCell()),
contract.getParseAddress(
beginCell().storeAddress(addr).endCell().asSlice(),
),
).rejects.toThrowError(
"Masterchain support is not enabled for this contract",
);
Expand All @@ -138,7 +140,7 @@ describe("masterchain", () => {
expect(
(
await contract.getParseAddress(
beginCell().storeAddress(addr).endCell(),
beginCell().storeAddress(addr).endCell().asSlice(),
)
).equals(addr),
).toBe(true);
Expand All @@ -154,7 +156,7 @@ describe("masterchain", () => {
expect(
(
await contract.getParseAddress(
beginCell().storeAddress(addr).endCell(),
beginCell().storeAddress(addr).endCell().asSlice(),
)
).equals(addr),
).toBe(true);
Expand Down
6 changes: 4 additions & 2 deletions src/test/e2e-emulated/math.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ describe("math", () => {
const sliceA = beginCell()
.storeBit(0)
.storeRef(beginCell().storeBit(1).endCell())
.endCell();
.endCell()
.asSlice();
const sliceB = beginCell()
.storeBit(1)
.storeRef(beginCell().storeBit(1).endCell())
.endCell();
.endCell()
.asSlice();
const stringA = "foo";
const stringB = "bar";
const dictA = Dictionary.empty<bigint, bigint>().set(0n, 0n);
Expand Down
27 changes: 26 additions & 1 deletion src/test/e2e-emulated/serialization.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { toNano } from "@ton/core";
import { beginCell, Builder, Cell, Slice, toNano } from "@ton/core";
import { ContractSystem } from "@tact-lang/emulator";
import { __DANGER_resetNodeId } from "../../grammar/ast";
import { SerializationTester3 } from "./contracts/output/serialization-3_SerializationTester3";
import { SerializationTester2 } from "./contracts/output/serialization-2_SerializationTester2";
import { SerializationTester } from "./contracts/output/serialization_SerializationTester";

Expand Down Expand Up @@ -158,4 +159,28 @@ describe("serialization", () => {
});
}
}
it("serialization-3", async () => {
// Init contract
const system = await ContractSystem.create();
const treasure = system.treasure("treasure");
const contract = system.open(
await SerializationTester3.fromInit(
1n,
true,
beginCell().endCell(),
beginCell().endCell().asSlice(),
beginCell().endCell().asBuilder(),
"test",
),
);
await contract.send(treasure, { value: toNano("10") }, null);
await system.run();

expect(await contract.getGetA()).toBe(1n);
expect(await contract.getGetB()).toBe(true);
expect(await contract.getGetC()).toBeInstanceOf(Cell);
expect(await contract.getGetD()).toBeInstanceOf(Slice);
expect(await contract.getGetE()).toBeInstanceOf(Builder);
expect(await contract.getGetF()).toBe("test");
});
});
3 changes: 2 additions & 1 deletion src/test/e2e-emulated/stdlib.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ describe("stdlib", () => {
.storeBit(1)
.storeBit(1)
.storeRef(beginCell().storeBit(1).endCell())
.endCell();
.endCell()
.asSlice();
expect(await contract.getSliceBits(slice)).toBe(2n);
expect(await contract.getSliceRefs(slice)).toBe(1n);
expect(await contract.getSliceEmpty(slice)).toBe(false);
Expand Down
11 changes: 4 additions & 7 deletions src/test/e2e-emulated/strings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,9 @@ describe("strings", () => {
expect(await contract.getStringWithFloat()).toEqual("9.5");

const base = await contract.getBase64();
expect(
base
.beginParse()
.loadBuffer(base.bits.length / 8)
.toString(),
).toEqual("Many hands make light work.");
expect(base.loadBuffer(base.remainingBits / 8).toString()).toEqual(
"Many hands make light work.",
);

const b64cases = [
"SGVsbG8gV29ybGQ=",
Expand All @@ -79,7 +76,7 @@ describe("strings", () => {
for (const b of b64cases) {
const s = Buffer.from(b, "base64");
const r = await contract.getProcessBase64(b);
const d = r.beginParse().loadBuffer(r.bits.length / 8);
const d = r.loadBuffer(r.remainingBits / 8);
expect(d.toString("hex")).toEqual(s.toString("hex"));
}

Expand Down
Loading

0 comments on commit 30f7be2

Please sign in to comment.