From bf048bfa9c1be32b427a2bab3f55d4873a671edc Mon Sep 17 00:00:00 2001 From: Gusarich Date: Sun, 29 Dec 2024 03:35:03 +0300 Subject: [PATCH 1/3] feat: implement `map.fromCell` --- src/abi/map.ts | 64 +++++++ src/test/e2e-emulated/contracts/maps1.tact | 156 +++++++++++++++++ src/test/e2e-emulated/contracts/maps2.tact | 156 +++++++++++++++++ src/test/e2e-emulated/map1.spec.ts | 189 ++++++++++++++++++++- src/test/e2e-emulated/map2.spec.ts | 157 ++++++++++++++++- 5 files changed, 719 insertions(+), 3 deletions(-) diff --git a/src/abi/map.ts b/src/abi/map.ts index 2c0332b1f..122f8123c 100644 --- a/src/abi/map.ts +++ b/src/abi/map.ts @@ -368,6 +368,70 @@ export const MapFunctions: Map = new Map([ }, }, ], + [ + "fromCell", + { + name: "fromCell", + resolve( + ctx: CompilerContext, + args: (TypeRef | undefined)[], + ref: SrcInfo, + ) { + checkArgumentsLength( + args, + 2, + "fromCell expects one argument", + ref, + ); + + const [self, newCell] = args; + checkMapType(self, ref); + + if ( + !newCell || + newCell.kind !== "ref" || + newCell.name !== "Cell" + ) { + throwCompilationError( + "fromCell expects a Cell as second argument", + ref, + ); + } + + return { kind: "void" }; + }, + generate( + ctx: WriterContext, + args: (TypeRef | undefined)[], + exprs: AstExpression[], + ref: SrcInfo, + ) { + checkArgumentsLength( + args, + 2, + "fromCell expects one argument", + ref, + ); + + const [self, newCell] = args; + checkMapType(self, ref); + + if ( + !newCell || + newCell.kind !== "ref" || + newCell.name !== "Cell" + ) { + throwCompilationError( + "fromCell expects a Cell as second argument", + ref, + ); + } + + const resolved = exprs.map((v) => writeExpression(v, ctx)); + return `${resolved[0]} = ${resolved[1]}`; + }, + }, + ], [ "isEmpty", { diff --git a/src/test/e2e-emulated/contracts/maps1.tact b/src/test/e2e-emulated/contracts/maps1.tact index 69b2b3aee..6e3ced595 100644 --- a/src/test/e2e-emulated/contracts/maps1.tact +++ b/src/test/e2e-emulated/contracts/maps1.tact @@ -549,6 +549,88 @@ message ReplaceGetAllMaps { valueStruct: SomeStruct?; } +message FromCellAllMaps { + // Integer (`Int`) Key Maps + int_varint16: Cell; + int_varint32: Cell; + int_varuint16: Cell; + int_varuint32: Cell; + int_bool: Cell; + int_cell: Cell; + int_address: Cell; + int_struct: Cell; + + // Integer (`Int as int8`) Key Maps + int8_varint16: Cell; + int8_varint32: Cell; + int8_varuint16: Cell; + int8_varuint32: Cell; + int8_bool: Cell; + int8_cell: Cell; + int8_address: Cell; + int8_struct: Cell; + + // Integer (`Int as int42`) Key Maps + int42_varint16: Cell; + int42_varint32: Cell; + int42_varuint16: Cell; + int42_varuint32: Cell; + int42_bool: Cell; + int42_cell: Cell; + int42_address: Cell; + int42_struct: Cell; + + // Integer (`Int as int256`) Key Maps + int256_varint16: Cell; + int256_varint32: Cell; + int256_varuint16: Cell; + int256_varuint32: Cell; + int256_bool: Cell; + int256_cell: Cell; + int256_address: Cell; + int256_struct: Cell; + + // Integer (`Int as uint8`) Key Maps + uint8_varint16: Cell; + uint8_varint32: Cell; + uint8_varuint16: Cell; + uint8_varuint32: Cell; + uint8_bool: Cell; + uint8_cell: Cell; + uint8_address: Cell; + uint8_struct: Cell; + + // Integer (`Int as uint42`) Key Maps + uint42_varint16: Cell; + uint42_varint32: Cell; + uint42_varuint16: Cell; + uint42_varuint32: Cell; + uint42_bool: Cell; + uint42_cell: Cell; + uint42_address: Cell; + uint42_struct: Cell; + + // Integer (`Int as uint256`) Key Maps + uint256_varint16: Cell; + uint256_varint32: Cell; + uint256_varuint16: Cell; + uint256_varuint32: Cell; + uint256_bool: Cell; + uint256_cell: Cell; + uint256_address: Cell; + uint256_struct: Cell; + + // Address Key Maps + address_varint16: Cell; + address_varint32: Cell; + address_varuint16: Cell; + address_varuint32: Cell; + address_bool: Cell; + address_cell: Cell; + address_address: Cell; + address_struct: Cell; +} + message CheckNullReference { } @@ -972,6 +1054,80 @@ contract MapTestContract { self.address_struct.replaceGet(msg.keyAddress, msg.valueStruct); } + receive(msg: FromCellAllMaps) { + self.int_varint16.fromCell(msg.int_varint16); + self.int_varint32.fromCell(msg.int_varint32); + self.int_varuint16.fromCell(msg.int_varuint16); + self.int_varuint32.fromCell(msg.int_varuint32); + self.int_bool.fromCell(msg.int_bool); + self.int_cell.fromCell(msg.int_cell); + self.int_address.fromCell(msg.int_address); + self.int_struct.fromCell(msg.int_struct); + + self.int8_varint16.fromCell(msg.int8_varint16); + self.int8_varint32.fromCell(msg.int8_varint32); + self.int8_varuint16.fromCell(msg.int8_varuint16); + self.int8_varuint32.fromCell(msg.int8_varuint32); + self.int8_bool.fromCell(msg.int8_bool); + self.int8_cell.fromCell(msg.int8_cell); + self.int8_address.fromCell(msg.int8_address); + self.int8_struct.fromCell(msg.int8_struct); + + self.int42_varint16.fromCell(msg.int42_varint16); + self.int42_varint32.fromCell(msg.int42_varint32); + self.int42_varuint16.fromCell(msg.int42_varuint16); + self.int42_varuint32.fromCell(msg.int42_varuint32); + self.int42_bool.fromCell(msg.int42_bool); + self.int42_cell.fromCell(msg.int42_cell); + self.int42_address.fromCell(msg.int42_address); + self.int42_struct.fromCell(msg.int42_struct); + + self.int256_varint16.fromCell(msg.int256_varint16); + self.int256_varint32.fromCell(msg.int256_varint32); + self.int256_varuint16.fromCell(msg.int256_varuint16); + self.int256_varuint32.fromCell(msg.int256_varuint32); + self.int256_bool.fromCell(msg.int256_bool); + self.int256_cell.fromCell(msg.int256_cell); + self.int256_address.fromCell(msg.int256_address); + self.int256_struct.fromCell(msg.int256_struct); + + self.uint8_varint16.fromCell(msg.uint8_varint16); + self.uint8_varint32.fromCell(msg.uint8_varint32); + self.uint8_varuint16.fromCell(msg.uint8_varuint16); + self.uint8_varuint32.fromCell(msg.uint8_varuint32); + self.uint8_bool.fromCell(msg.uint8_bool); + self.uint8_cell.fromCell(msg.uint8_cell); + self.uint8_address.fromCell(msg.uint8_address); + self.uint8_struct.fromCell(msg.uint8_struct); + + self.uint42_varint16.fromCell(msg.uint42_varint16); + self.uint42_varint32.fromCell(msg.uint42_varint32); + self.uint42_varuint16.fromCell(msg.uint42_varuint16); + self.uint42_varuint32.fromCell(msg.uint42_varuint32); + self.uint42_bool.fromCell(msg.uint42_bool); + self.uint42_cell.fromCell(msg.uint42_cell); + self.uint42_address.fromCell(msg.uint42_address); + self.uint42_struct.fromCell(msg.uint42_struct); + + self.uint256_varint16.fromCell(msg.uint256_varint16); + self.uint256_varint32.fromCell(msg.uint256_varint32); + self.uint256_varuint16.fromCell(msg.uint256_varuint16); + self.uint256_varuint32.fromCell(msg.uint256_varuint32); + self.uint256_bool.fromCell(msg.uint256_bool); + self.uint256_cell.fromCell(msg.uint256_cell); + self.uint256_address.fromCell(msg.uint256_address); + self.uint256_struct.fromCell(msg.uint256_struct); + + self.address_varint16.fromCell(msg.address_varint16); + self.address_varint32.fromCell(msg.address_varint32); + self.address_varuint16.fromCell(msg.address_varuint16); + self.address_varuint32.fromCell(msg.address_varuint32); + self.address_bool.fromCell(msg.address_bool); + self.address_cell.fromCell(msg.address_cell); + self.address_address.fromCell(msg.address_address); + self.address_struct.fromCell(msg.address_struct); + } + // =============================== // Getters // =============================== diff --git a/src/test/e2e-emulated/contracts/maps2.tact b/src/test/e2e-emulated/contracts/maps2.tact index e14b918e9..e4ea3c8ae 100644 --- a/src/test/e2e-emulated/contracts/maps2.tact +++ b/src/test/e2e-emulated/contracts/maps2.tact @@ -549,6 +549,88 @@ message ReplaceGetAllMaps { valueCoins: Int?; } +message FromCellAllMaps { + // Integer (`Int`) Key Maps + int_int: Cell; + int_int8: Cell; + int_int42: Cell; + int_int256: Cell; + int_uint8: Cell; + int_uint42: Cell; + int_uint256: Cell; + int_coins: Cell; + + // Integer (`Int as int8`) Key Maps + int8_int: Cell; + int8_int8: Cell; + int8_int42: Cell; + int8_int256: Cell; + int8_uint8: Cell; + int8_uint42: Cell; + int8_uint256: Cell; + int8_coins: Cell; + + // Integer (`Int as int42`) Key Maps + int42_int: Cell; + int42_int8: Cell; + int42_int42: Cell; + int42_int256: Cell; + int42_uint8: Cell; + int42_uint42: Cell; + int42_uint256: Cell; + int42_coins: Cell; + + // Integer (`Int as int256`) Key Maps + int256_int: Cell; + int256_int8: Cell; + int256_int42: Cell; + int256_int256: Cell; + int256_uint8: Cell; + int256_uint42: Cell; + int256_uint256: Cell; + int256_coins: Cell; + + // Unsigned Integer (`Int as uint8`) Key Maps + uint8_int: Cell; + uint8_int8: Cell; + uint8_int42: Cell; + uint8_int256: Cell; + uint8_uint8: Cell; + uint8_uint42: Cell; + uint8_uint256: Cell; + uint8_coins: Cell; + + // Unsigned Integer (`Int as uint42`) Key Maps + uint42_int: Cell; + uint42_int8: Cell; + uint42_int42: Cell; + uint42_int256: Cell; + uint42_uint8: Cell; + uint42_uint42: Cell; + uint42_uint256: Cell; + uint42_coins: Cell; + + // Unsigned Integer (`Int as uint256`) Key Maps + uint256_int: Cell; + uint256_int8: Cell; + uint256_int42: Cell; + uint256_int256: Cell; + uint256_uint8: Cell; + uint256_uint42: Cell; + uint256_uint256: Cell; + uint256_coins: Cell; + + // Address Key Maps + address_int: Cell; + address_int8: Cell; + address_int42: Cell; + address_int256: Cell; + address_uint8: Cell; + address_uint42: Cell; + address_uint256: Cell; + address_coins: Cell; +} + message CheckNullReference { } @@ -972,6 +1054,80 @@ contract MapTestContract { self.address_coins.replaceGet(msg.keyAddress, msg.valueCoins); } + receive(msg: FromCellAllMaps) { + self.int_int.fromCell(msg.int_int); + self.int_int8.fromCell(msg.int_int8); + self.int_int42.fromCell(msg.int_int42); + self.int_int256.fromCell(msg.int_int256); + self.int_uint8.fromCell(msg.int_uint8); + self.int_uint42.fromCell(msg.int_uint42); + self.int_uint256.fromCell(msg.int_uint256); + self.int_coins.fromCell(msg.int_coins); + + self.int8_int.fromCell(msg.int8_int); + self.int8_int8.fromCell(msg.int8_int8); + self.int8_int42.fromCell(msg.int8_int42); + self.int8_int256.fromCell(msg.int8_int256); + self.int8_uint8.fromCell(msg.int8_uint8); + self.int8_uint42.fromCell(msg.int8_uint42); + self.int8_uint256.fromCell(msg.int8_uint256); + self.int8_coins.fromCell(msg.int8_coins); + + self.int42_int.fromCell(msg.int42_int); + self.int42_int8.fromCell(msg.int42_int8); + self.int42_int42.fromCell(msg.int42_int42); + self.int42_int256.fromCell(msg.int42_int256); + self.int42_uint8.fromCell(msg.int42_uint8); + self.int42_uint42.fromCell(msg.int42_uint42); + self.int42_uint256.fromCell(msg.int42_uint256); + self.int42_coins.fromCell(msg.int42_coins); + + self.int256_int.fromCell(msg.int256_int); + self.int256_int8.fromCell(msg.int256_int8); + self.int256_int42.fromCell(msg.int256_int42); + self.int256_int256.fromCell(msg.int256_int256); + self.int256_uint8.fromCell(msg.int256_uint8); + self.int256_uint42.fromCell(msg.int256_uint42); + self.int256_uint256.fromCell(msg.int256_uint256); + self.int256_coins.fromCell(msg.int256_coins); + + self.uint8_int.fromCell(msg.uint8_int); + self.uint8_int8.fromCell(msg.uint8_int8); + self.uint8_int42.fromCell(msg.uint8_int42); + self.uint8_int256.fromCell(msg.uint8_int256); + self.uint8_uint8.fromCell(msg.uint8_uint8); + self.uint8_uint42.fromCell(msg.uint8_uint42); + self.uint8_uint256.fromCell(msg.uint8_uint256); + self.uint8_coins.fromCell(msg.uint8_coins); + + self.uint42_int.fromCell(msg.uint42_int); + self.uint42_int8.fromCell(msg.uint42_int8); + self.uint42_int42.fromCell(msg.uint42_int42); + self.uint42_int256.fromCell(msg.uint42_int256); + self.uint42_uint8.fromCell(msg.uint42_uint8); + self.uint42_uint42.fromCell(msg.uint42_uint42); + self.uint42_uint256.fromCell(msg.uint42_uint256); + self.uint42_coins.fromCell(msg.uint42_coins); + + self.uint256_int.fromCell(msg.uint256_int); + self.uint256_int8.fromCell(msg.uint256_int8); + self.uint256_int42.fromCell(msg.uint256_int42); + self.uint256_int256.fromCell(msg.uint256_int256); + self.uint256_uint8.fromCell(msg.uint256_uint8); + self.uint256_uint42.fromCell(msg.uint256_uint42); + self.uint256_uint256.fromCell(msg.uint256_uint256); + self.uint256_coins.fromCell(msg.uint256_coins); + + self.address_int.fromCell(msg.address_int); + self.address_int8.fromCell(msg.address_int8); + self.address_int42.fromCell(msg.address_int42); + self.address_int256.fromCell(msg.address_int256); + self.address_uint8.fromCell(msg.address_uint8); + self.address_uint42.fromCell(msg.address_uint42); + self.address_uint256.fromCell(msg.address_uint256); + self.address_coins.fromCell(msg.address_coins); + } + // =============================== // Getters // =============================== diff --git a/src/test/e2e-emulated/map1.spec.ts b/src/test/e2e-emulated/map1.spec.ts index d7a0d600e..f58984127 100644 --- a/src/test/e2e-emulated/map1.spec.ts +++ b/src/test/e2e-emulated/map1.spec.ts @@ -9,9 +9,17 @@ import { SomeStruct, ReplaceAllMaps, ReplaceGetAllMaps, + storeSomeStruct, } from "./contracts/output/maps1_MapTestContract"; import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox"; -import { Address, beginCell, Cell, Dictionary, toNano } from "@ton/core"; +import { + Address, + beginCell, + Cell, + Dictionary, + DictionaryKey, + toNano, +} from "@ton/core"; import "@ton/test-utils"; // Type Guard for SomeStruct @@ -1465,7 +1473,7 @@ describe("MapTestContract", () => { } }); - it.only("asCell: should correctly serialize and deserialize maps", async () => { + it("asCell: should correctly serialize and deserialize maps", async () => { for (const { keys, values } of testCases) { // Set values for the current test case const setMessage: SetAllMaps = { @@ -1973,4 +1981,181 @@ describe("MapTestContract", () => { exitCode: 128, }); }); + + it("fromCell: should correctly set maps from cells", async () => { + function getTestKey(mapName: string): bigint | Address { + if (mapName.startsWith("address_")) { + return Address.parse( + "UQBKgXCNLPexWhs2L79kiARR1phGH1LwXxRbNsCFF9doczSI", + ); + } + return 1n; + } + + function buildDictionaryCell(mapName: string): Cell { + let keyType: DictionaryKey; + if (mapName.startsWith("int8_")) { + keyType = Dictionary.Keys.BigInt(8); + } else if (mapName.startsWith("int42_")) { + keyType = Dictionary.Keys.BigInt(42); + } else if (mapName.startsWith("int256_")) { + keyType = Dictionary.Keys.BigInt(256); + } else if (mapName.startsWith("int_")) { + keyType = Dictionary.Keys.BigInt(257); + } else if (mapName.startsWith("uint8_")) { + keyType = Dictionary.Keys.BigUint(8); + } else if (mapName.startsWith("uint42_")) { + keyType = Dictionary.Keys.BigUint(42); + } else if (mapName.startsWith("uint256_")) { + keyType = Dictionary.Keys.BigUint(256); + } else if (mapName.startsWith("address_")) { + keyType = Dictionary.Keys.Address(); + } else { + keyType = Dictionary.Keys.BigInt(257); + } + + const [, valuePart] = mapName.split("_", 2) as [string, string]; + const testKey = getTestKey(mapName); + + let dict: Dictionary; + + switch (valuePart) { + case "varint16": + dict = Dictionary.empty( + keyType, + Dictionary.Values.BigVarInt(4), + ).set(testKey, 999n); + break; + case "varint32": + dict = Dictionary.empty( + keyType, + Dictionary.Values.BigVarInt(5), + ).set(testKey, -123_456n); + break; + case "varuint16": + dict = Dictionary.empty( + keyType, + Dictionary.Values.BigVarUint(4), + ).set(testKey, 500n); + break; + case "varuint32": + dict = Dictionary.empty( + keyType, + Dictionary.Values.BigVarUint(5), + ).set(testKey, 999_999n); + break; + case "bool": + dict = Dictionary.empty( + keyType, + Dictionary.Values.Bool(), + ).set(testKey, true); + break; + case "cell": + dict = Dictionary.empty( + keyType, + Dictionary.Values.Cell(), + ).set(testKey, beginCell().storeUint(777, 32).endCell()); + break; + case "address": + dict = Dictionary.empty( + keyType, + Dictionary.Values.Address(), + ).set( + testKey, + Address.parse( + "UQBKgXCNLPexWhs2L79kiARR1phGH1LwXxRbNsCFF9doczSI", + ), + ); + break; + case "struct": + dict = Dictionary.empty(keyType, Dictionary.Values.Cell()); + dict.set( + testKey, + beginCell() + .store( + storeSomeStruct({ + $$type: "SomeStruct", + int: 123n, + bool: false, + address: randomAddress(0, "struct_addr"), + a: 10n, + b: -42n, + }), + ) + .endCell(), + ); + break; + default: + throw new Error(`Unknown value part: ${valuePart}`); // should never happen + } + + return beginCell().storeDictDirect(dict).endCell(); + } + + // Build the message with one dictionary-cell per map in mapConfigs + const fromCellMessage: any = { $$type: "FromCellAllMaps" }; + for (const { mapName } of mapConfigs) { + fromCellMessage[mapName] = buildDictionaryCell(mapName); + } + + // Send message + const result = await contract.send( + treasury.getSender(), + { value: toNano("1") }, + fromCellMessage, + ); + expect(result.transactions).toHaveLength(2); + expect(result.transactions).toHaveTransaction({ + on: contract.address, + success: true, + }); + + // Read maps from contract + const allMaps = await contract.getAllMaps(); + + // Verify each dictionary + for (const { mapName } of mapConfigs) { + const map = allMaps[mapName] as Dictionary; + expect(map.size).toBe(1); + + let testKey: bigint | Address | number = getTestKey(mapName); + testKey = + testKey instanceof Address || + (!mapName.startsWith("int8_") && !mapName.startsWith("uint8_")) + ? testKey + : Number(testKey); + const val = map.get(testKey); + + // Check the stored value + if (mapName.endsWith("_bool")) { + expect(val).toBe(true); + } else if (mapName.endsWith("_cell")) { + expect(val).toEqualCell( + beginCell().storeUint(777, 32).endCell(), + ); + } else if (mapName.endsWith("_address")) { + expect(val).toEqualAddress( + Address.parse( + "UQBKgXCNLPexWhs2L79kiARR1phGH1LwXxRbNsCFF9doczSI", + ), + ); + } else if (mapName.endsWith("_struct")) { + expect(val).toBeTruthy(); + if (val && typeof val === "object") { + expect(val.int).toBe(123n); + expect(val.bool).toBe(false); + expect(val.a).toBe(10n); + expect(val.b).toBe(-42n); + } + } else if (mapName.endsWith("_varint16")) { + expect(val).toBe(999n); + } else if (mapName.endsWith("_varint32")) { + expect(val).toBe(-123_456n); + } else if (mapName.endsWith("_varuint16")) { + expect(val).toBe(500n); + } else if (mapName.endsWith("_varuint32")) { + expect(val).toBe(999_999n); + } + } + }); }); diff --git a/src/test/e2e-emulated/map2.spec.ts b/src/test/e2e-emulated/map2.spec.ts index c5375a3d6..244446ff2 100644 --- a/src/test/e2e-emulated/map2.spec.ts +++ b/src/test/e2e-emulated/map2.spec.ts @@ -10,7 +10,14 @@ import { ReplaceGetAllMaps, } from "./contracts/output/maps2_MapTestContract"; import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox"; -import { Address, beginCell, Dictionary, toNano } from "@ton/core"; +import { + Address, + beginCell, + Cell, + Dictionary, + DictionaryKey, + toNano, +} from "@ton/core"; import "@ton/test-utils"; // Type definitions for keys and values to make them type-safe @@ -1815,4 +1822,152 @@ describe("MapTestContract", () => { exitCode: 128, }); }); + + it("fromCell: should correctly set maps from cells", async () => { + function getTestKey(mapName: string): bigint | Address { + if (mapName.startsWith("address_")) { + return Address.parse( + "UQBKgXCNLPexWhs2L79kiARR1phGH1LwXxRbNsCFF9doczSI", + ); + } + return 1n; + } + + function buildDictionaryCell(mapName: string): Cell { + let keyType: DictionaryKey; + if (mapName.startsWith("int8_")) { + keyType = Dictionary.Keys.BigInt(8); + } else if (mapName.startsWith("int42_")) { + keyType = Dictionary.Keys.BigInt(42); + } else if (mapName.startsWith("int256_")) { + keyType = Dictionary.Keys.BigInt(256); + } else if (mapName.startsWith("int_")) { + keyType = Dictionary.Keys.BigInt(257); + } else if (mapName.startsWith("uint8_")) { + keyType = Dictionary.Keys.BigUint(8); + } else if (mapName.startsWith("uint42_")) { + keyType = Dictionary.Keys.BigUint(42); + } else if (mapName.startsWith("uint256_")) { + keyType = Dictionary.Keys.BigUint(256); + } else if (mapName.startsWith("address_")) { + keyType = Dictionary.Keys.Address(); + } else { + keyType = Dictionary.Keys.BigInt(257); + } + + const [, valuePart] = mapName.split("_", 2) as [string, string]; + const testKey = getTestKey(mapName); + + let dict: Dictionary; + + switch (valuePart) { + case "int": + dict = Dictionary.empty( + keyType, + Dictionary.Values.BigInt(257), + ).set(testKey, 111n); + break; + case "int8": + dict = Dictionary.empty( + keyType, + Dictionary.Values.BigInt(8), + ).set(testKey, -10n); + break; + case "int42": + dict = Dictionary.empty( + keyType, + Dictionary.Values.BigInt(42), + ).set(testKey, 4242n); + break; + case "int256": + dict = Dictionary.empty( + keyType, + Dictionary.Values.BigInt(256), + ).set(testKey, -99999n); + break; + case "uint8": + dict = Dictionary.empty( + keyType, + Dictionary.Values.BigUint(8), + ).set(testKey, 200n); + break; + case "uint42": + dict = Dictionary.empty( + keyType, + Dictionary.Values.BigUint(42), + ).set(testKey, 424242n); + break; + case "uint256": + dict = Dictionary.empty( + keyType, + Dictionary.Values.BigUint(256), + ).set(testKey, 999999n); + break; + case "coins": + dict = Dictionary.empty( + keyType, + Dictionary.Values.BigVarUint(4), + ).set(testKey, toNano("3")); + break; + default: + throw new Error(`Unknown value part: ${valuePart}`); // should never happen + } + + return beginCell().storeDictDirect(dict).endCell(); + } + + // Build the message with one dictionary-cell per map in mapConfigs + const fromCellMessage: any = { $$type: "FromCellAllMaps" }; + for (const { mapName } of mapConfigs) { + fromCellMessage[mapName] = buildDictionaryCell(mapName); + } + + // Send message + const result = await contract.send( + treasury.getSender(), + { value: toNano("1") }, + fromCellMessage, + ); + expect(result.transactions).toHaveLength(2); + expect(result.transactions).toHaveTransaction({ + on: contract.address, + success: true, + }); + + // Read maps from contract + const allMaps = await contract.getAllMaps(); + + // Verify each dictionary + for (const { mapName } of mapConfigs) { + const map = allMaps[mapName] as Dictionary; + expect(map.size).toBe(1); + + let testKey: bigint | Address | number = getTestKey(mapName); + testKey = + testKey instanceof Address || + (!mapName.startsWith("int8_") && !mapName.startsWith("uint8_")) + ? testKey + : Number(testKey); + const val = map.get(testKey); + + // Check the stored value + if (mapName.endsWith("_int")) { + expect(val).toBe(111n); + } else if (mapName.endsWith("_int8")) { + expect(val).toBe(-10); + } else if (mapName.endsWith("_int42")) { + expect(val).toBe(4242n); + } else if (mapName.endsWith("_int256")) { + expect(val).toBe(-99999n); + } else if (mapName.endsWith("_uint8")) { + expect(val).toBe(200); + } else if (mapName.endsWith("_uint42")) { + expect(val).toBe(424242n); + } else if (mapName.endsWith("_uint256")) { + expect(val).toBe(999999n); + } else if (mapName.endsWith("_coins")) { + expect(val).toBe(toNano("3")); + } + } + }); }); From 63bd32cf03c97adf0e3c871521de0007991ed7b6 Mon Sep 17 00:00:00 2001 From: Gusarich Date: Sun, 29 Dec 2024 03:37:32 +0300 Subject: [PATCH 2/3] feat: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a9378fa4..fb28a44a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `VarInt16`, `VarInt32`, `VarUint16`, `VarUint32` integer serialization types: PR [#1186](https://github.com/tact-lang/tact/pull/1186) - `unboc`: a standalone CLI utility to expose Tact's TVM disassembler: PR [#1259](https://github.com/tact-lang/tact/pull/1259) - Added alternative parser: PR [#1258](https://github.com/tact-lang/tact/pull/1258) +- The `fromCell` method for the `Map` type: PR [#1271](https://github.com/tact-lang/tact/pull/1271) ### Changed From 495e6117a5616fd692e2db5a33da26ffad4b57c2 Mon Sep 17 00:00:00 2001 From: Gusarich Date: Sun, 29 Dec 2024 03:43:29 +0300 Subject: [PATCH 3/3] feat(docs): cover `fromCell()` --- docs/src/content/docs/book/maps.mdx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/src/content/docs/book/maps.mdx b/docs/src/content/docs/book/maps.mdx index 4183465de..6823b034d 100644 --- a/docs/src/content/docs/book/maps.mdx +++ b/docs/src/content/docs/book/maps.mdx @@ -360,6 +360,21 @@ contract Example { } ``` +### Convert from a `Cell`, `.fromCell()` {#fromcell} + +

+ +To convert a [`Cell{:tact}`][cell] type back to a map, use the `.fromCell(){:tact}` [method](/book/functions#extension-function). + +```tact +// Suppose we have a Cell +let cell: Cell = ...; + +// And we want to initialize a map variable from it +let fizz: map = emptyMap(); +fizz.fromCell(cell); +``` + ### Traverse over entries {#traverse} To iterate over map entries there is a [`foreach{:tact}`](/book/statements#foreach-loop) loop statement: