From 6bd585c64ca3924da31326f9d34d7e35e5bb4cb8 Mon Sep 17 00:00:00 2001 From: Daniil Sedov <42098239+Gusarich@users.noreply.github.com> Date: Thu, 14 Mar 2024 17:05:43 +0300 Subject: [PATCH] feat: supports addresses for dump() (#175) --- CHANGELOG.md | 1 + src/abi/global.ts | 3 + src/generator/Writer.ts | 2 +- .../writeSerialization.spec.ts.snap | 381 ++++++++++++++++++ src/generator/writers/writeStdlib.ts | 102 +++++ src/test/feature-debug.spec.ts | 29 +- src/test/features/debug.tact | 4 + 7 files changed, 513 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fb250b6c..507940a64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed +- Update the `dump` function to handle addresses: PR [#175](https://github.com/tact-lang/tact/pull/175) ### Fixed diff --git a/src/abi/global.ts b/src/abi/global.ts index b5cd7d3d5..7265dc589 100644 --- a/src/abi/global.ts +++ b/src/abi/global.ts @@ -187,6 +187,9 @@ export const GlobalFunctions: { [key: string]: AbiFunction } = { } else if (arg.name === 'String') { const exp = writeExpression(resolved[0], ctx); return `${ctx.used(`__tact_debug_str`)}(${exp})`; + } else if (arg.name === 'Address') { + const exp = writeExpression(resolved[0], ctx); + return `${ctx.used(`__tact_debug_address`)}(${exp})`; } throwError('dump() not supported for type: ' + arg.name, ref); } else { diff --git a/src/generator/Writer.ts b/src/generator/Writer.ts index 13f145693..fa5559955 100644 --- a/src/generator/Writer.ts +++ b/src/generator/Writer.ts @@ -3,7 +3,7 @@ import { trimIndent } from "../utils/text"; import { topologicalSort } from "../utils/utils"; import { Writer } from "../utils/Writer"; -type Flag = 'inline' | 'impure'; +type Flag = 'inline' | 'impure' | 'inline_ref'; type Body = { kind: 'generic', diff --git a/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap b/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap index 22d73653d..88ed442a3 100644 --- a/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap +++ b/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap @@ -319,6 +319,133 @@ return __tact_create_address(chain, hash);", "name": "__tact_debug_bool", "signature": "() __tact_debug_bool(int value)", }, + { + "code": { + "code": "asm "SDSUBSTR"", + "kind": "asm", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_preload_offset", + "signature": "(slice) __tact_preload_offset(slice s, int offset, int bits)", + }, + { + "code": { + "code": "slice new_data = begin_cell() + .store_slice(data) + .store_slice("0000"s) +.end_cell().begin_parse(); +int reg = 0; +while (~ new_data.slice_data_empty?()) { + int byte = new_data~load_uint(8); + int mask = 0x80; + while (mask > 0) { + reg <<= 1; + if (byte & mask) { + reg += 1; + } + mask >>= 1; + if (reg > 0xffff) { + reg &= 0xffff; + reg ^= 0x1021; + } + } +} +(int q, int r) = divmod(reg, 256); +return begin_cell() + .store_uint(q, 8) + .store_uint(r, 8) +.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline_ref", + }, + "name": "__tact_crc16", + "signature": "(slice) __tact_crc16(slice data)", + }, + { + "code": { + "code": "slice chars = "4142434445464748494A4B4C4D4E4F505152535455565758595A6162636465666768696A6B6C6D6E6F707172737475767778797A303132333435363738392D5F"s; +builder res = begin_cell(); + +while (data.slice_bits() >= 24) { + (int bs1, int bs2, int bs3) = (data~load_uint(8), data~load_uint(8), data~load_uint(8)); + + int n = (bs1 << 16) | (bs2 << 8) | bs3; + + res = res + .store_slice(__tact_preload_offset(chars, ((n >> 18) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n >> 12) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n >> 6) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n ) & 63) * 8, 8)); +} + +return res.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_preload_offset", + }, + "flags": Set {}, + "name": "__tact_base64_encode", + "signature": "(slice) __tact_base64_encode(slice data)", + }, + { + "code": { + "code": "(int wc, int hash) = address.parse_std_addr(); + +slice user_friendly_address = begin_cell() + .store_slice("11"s) + .store_uint((wc + 0x100) % 0x100, 8) + .store_uint(hash, 256) +.end_cell().begin_parse(); + +slice checksum = __tact_crc16(user_friendly_address); +slice user_friendly_address_with_checksum = begin_cell() + .store_slice(user_friendly_address) + .store_slice(checksum) +.end_cell().begin_parse(); + +return __tact_base64_encode(user_friendly_address_with_checksum);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_crc16", + "__tact_base64_encode", + }, + "flags": Set {}, + "name": "__tact_address_to_userfriendly", + "signature": "(slice) __tact_address_to_userfriendly(slice address)", + }, + { + "code": { + "code": "__tact_debug_str(__tact_address_to_userfriendly(address));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_debug_str", + "__tact_address_to_userfriendly", + }, + "flags": Set { + "impure", + }, + "name": "__tact_debug_address", + "signature": "() __tact_debug_address(slice address)", + }, { "code": { "code": "return __tact_context;", @@ -4142,6 +4269,133 @@ return __tact_create_address(chain, hash);", "name": "__tact_debug_bool", "signature": "() __tact_debug_bool(int value)", }, + { + "code": { + "code": "asm "SDSUBSTR"", + "kind": "asm", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_preload_offset", + "signature": "(slice) __tact_preload_offset(slice s, int offset, int bits)", + }, + { + "code": { + "code": "slice new_data = begin_cell() + .store_slice(data) + .store_slice("0000"s) +.end_cell().begin_parse(); +int reg = 0; +while (~ new_data.slice_data_empty?()) { + int byte = new_data~load_uint(8); + int mask = 0x80; + while (mask > 0) { + reg <<= 1; + if (byte & mask) { + reg += 1; + } + mask >>= 1; + if (reg > 0xffff) { + reg &= 0xffff; + reg ^= 0x1021; + } + } +} +(int q, int r) = divmod(reg, 256); +return begin_cell() + .store_uint(q, 8) + .store_uint(r, 8) +.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline_ref", + }, + "name": "__tact_crc16", + "signature": "(slice) __tact_crc16(slice data)", + }, + { + "code": { + "code": "slice chars = "4142434445464748494A4B4C4D4E4F505152535455565758595A6162636465666768696A6B6C6D6E6F707172737475767778797A303132333435363738392D5F"s; +builder res = begin_cell(); + +while (data.slice_bits() >= 24) { + (int bs1, int bs2, int bs3) = (data~load_uint(8), data~load_uint(8), data~load_uint(8)); + + int n = (bs1 << 16) | (bs2 << 8) | bs3; + + res = res + .store_slice(__tact_preload_offset(chars, ((n >> 18) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n >> 12) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n >> 6) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n ) & 63) * 8, 8)); +} + +return res.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_preload_offset", + }, + "flags": Set {}, + "name": "__tact_base64_encode", + "signature": "(slice) __tact_base64_encode(slice data)", + }, + { + "code": { + "code": "(int wc, int hash) = address.parse_std_addr(); + +slice user_friendly_address = begin_cell() + .store_slice("11"s) + .store_uint((wc + 0x100) % 0x100, 8) + .store_uint(hash, 256) +.end_cell().begin_parse(); + +slice checksum = __tact_crc16(user_friendly_address); +slice user_friendly_address_with_checksum = begin_cell() + .store_slice(user_friendly_address) + .store_slice(checksum) +.end_cell().begin_parse(); + +return __tact_base64_encode(user_friendly_address_with_checksum);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_crc16", + "__tact_base64_encode", + }, + "flags": Set {}, + "name": "__tact_address_to_userfriendly", + "signature": "(slice) __tact_address_to_userfriendly(slice address)", + }, + { + "code": { + "code": "__tact_debug_str(__tact_address_to_userfriendly(address));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_debug_str", + "__tact_address_to_userfriendly", + }, + "flags": Set { + "impure", + }, + "name": "__tact_debug_address", + "signature": "() __tact_debug_address(slice address)", + }, { "code": { "code": "return __tact_context;", @@ -7965,6 +8219,133 @@ return __tact_create_address(chain, hash);", "name": "__tact_debug_bool", "signature": "() __tact_debug_bool(int value)", }, + { + "code": { + "code": "asm "SDSUBSTR"", + "kind": "asm", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_preload_offset", + "signature": "(slice) __tact_preload_offset(slice s, int offset, int bits)", + }, + { + "code": { + "code": "slice new_data = begin_cell() + .store_slice(data) + .store_slice("0000"s) +.end_cell().begin_parse(); +int reg = 0; +while (~ new_data.slice_data_empty?()) { + int byte = new_data~load_uint(8); + int mask = 0x80; + while (mask > 0) { + reg <<= 1; + if (byte & mask) { + reg += 1; + } + mask >>= 1; + if (reg > 0xffff) { + reg &= 0xffff; + reg ^= 0x1021; + } + } +} +(int q, int r) = divmod(reg, 256); +return begin_cell() + .store_uint(q, 8) + .store_uint(r, 8) +.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline_ref", + }, + "name": "__tact_crc16", + "signature": "(slice) __tact_crc16(slice data)", + }, + { + "code": { + "code": "slice chars = "4142434445464748494A4B4C4D4E4F505152535455565758595A6162636465666768696A6B6C6D6E6F707172737475767778797A303132333435363738392D5F"s; +builder res = begin_cell(); + +while (data.slice_bits() >= 24) { + (int bs1, int bs2, int bs3) = (data~load_uint(8), data~load_uint(8), data~load_uint(8)); + + int n = (bs1 << 16) | (bs2 << 8) | bs3; + + res = res + .store_slice(__tact_preload_offset(chars, ((n >> 18) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n >> 12) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n >> 6) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n ) & 63) * 8, 8)); +} + +return res.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_preload_offset", + }, + "flags": Set {}, + "name": "__tact_base64_encode", + "signature": "(slice) __tact_base64_encode(slice data)", + }, + { + "code": { + "code": "(int wc, int hash) = address.parse_std_addr(); + +slice user_friendly_address = begin_cell() + .store_slice("11"s) + .store_uint((wc + 0x100) % 0x100, 8) + .store_uint(hash, 256) +.end_cell().begin_parse(); + +slice checksum = __tact_crc16(user_friendly_address); +slice user_friendly_address_with_checksum = begin_cell() + .store_slice(user_friendly_address) + .store_slice(checksum) +.end_cell().begin_parse(); + +return __tact_base64_encode(user_friendly_address_with_checksum);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_crc16", + "__tact_base64_encode", + }, + "flags": Set {}, + "name": "__tact_address_to_userfriendly", + "signature": "(slice) __tact_address_to_userfriendly(slice address)", + }, + { + "code": { + "code": "__tact_debug_str(__tact_address_to_userfriendly(address));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_debug_str", + "__tact_address_to_userfriendly", + }, + "flags": Set { + "impure", + }, + "name": "__tact_debug_address", + "signature": "() __tact_debug_address(slice address)", + }, { "code": { "code": "return __tact_context;", diff --git a/src/generator/writers/writeStdlib.ts b/src/generator/writers/writeStdlib.ts index c1b2c6096..932ded8e9 100644 --- a/src/generator/writers/writeStdlib.ts +++ b/src/generator/writers/writeStdlib.ts @@ -208,6 +208,108 @@ export function writeStdlib(ctx: WriterContext) { }); }); + ctx.fun('__tact_preload_offset', () => { + ctx.signature(`(slice) __tact_preload_offset(slice s, int offset, int bits)`); + ctx.flag('inline'); + ctx.context('stdlib'); + ctx.asm(`asm "SDSUBSTR"`); + }); + + ctx.fun('__tact_crc16', () => { + ctx.signature(`(slice) __tact_crc16(slice data)`); + ctx.flag('inline_ref'); + ctx.context('stdlib'); + ctx.body(() => { + ctx.write(` + slice new_data = begin_cell() + .store_slice(data) + .store_slice("0000"s) + .end_cell().begin_parse(); + int reg = 0; + while (~ new_data.slice_data_empty?()) { + int byte = new_data~load_uint(8); + int mask = 0x80; + while (mask > 0) { + reg <<= 1; + if (byte & mask) { + reg += 1; + } + mask >>= 1; + if (reg > 0xffff) { + reg &= 0xffff; + reg ^= 0x1021; + } + } + } + (int q, int r) = divmod(reg, 256); + return begin_cell() + .store_uint(q, 8) + .store_uint(r, 8) + .end_cell().begin_parse(); + `); + }); + }); + + ctx.fun('__tact_base64_encode', () => { + ctx.signature(`(slice) __tact_base64_encode(slice data)`); + ctx.context('stdlib'); + ctx.body(() => { + ctx.write(` + slice chars = "4142434445464748494A4B4C4D4E4F505152535455565758595A6162636465666768696A6B6C6D6E6F707172737475767778797A303132333435363738392D5F"s; + builder res = begin_cell(); + + while (data.slice_bits() >= 24) { + (int bs1, int bs2, int bs3) = (data~load_uint(8), data~load_uint(8), data~load_uint(8)); + + int n = (bs1 << 16) | (bs2 << 8) | bs3; + + res = res + .store_slice(${ctx.used('__tact_preload_offset')}(chars, ((n >> 18) & 63) * 8, 8)) + .store_slice(${ctx.used('__tact_preload_offset')}(chars, ((n >> 12) & 63) * 8, 8)) + .store_slice(${ctx.used('__tact_preload_offset')}(chars, ((n >> 6) & 63) * 8, 8)) + .store_slice(${ctx.used('__tact_preload_offset')}(chars, ((n ) & 63) * 8, 8)); + } + + return res.end_cell().begin_parse(); + `); + }); + }); + + ctx.fun('__tact_address_to_userfriendly', () => { + ctx.signature(`(slice) __tact_address_to_userfriendly(slice address)`); + ctx.context('stdlib'); + ctx.body(() => { + ctx.write(` + (int wc, int hash) = address.parse_std_addr(); + + slice user_friendly_address = begin_cell() + .store_slice("11"s) + .store_uint((wc + 0x100) % 0x100, 8) + .store_uint(hash, 256) + .end_cell().begin_parse(); + + slice checksum = ${ctx.used('__tact_crc16')}(user_friendly_address); + slice user_friendly_address_with_checksum = begin_cell() + .store_slice(user_friendly_address) + .store_slice(checksum) + .end_cell().begin_parse(); + + return ${ctx.used('__tact_base64_encode')}(user_friendly_address_with_checksum); + `); + }); + }); + + ctx.fun('__tact_debug_address', () => { + ctx.signature(`() __tact_debug_address(slice address)`); + ctx.flag('impure'); + ctx.context('stdlib'); + ctx.body(() => { + ctx.write(` + ${ctx.used('__tact_debug_str')}(${ctx.used('__tact_address_to_userfriendly')}(address)); + `); + }); + }); + ctx.fun('__tact_context_get', () => { ctx.signature(`(int, slice, int, slice) __tact_context_get()`); ctx.flag('inline'); diff --git a/src/test/feature-debug.spec.ts b/src/test/feature-debug.spec.ts index ddd6699d3..202250367 100644 --- a/src/test/feature-debug.spec.ts +++ b/src/test/feature-debug.spec.ts @@ -1,4 +1,4 @@ -import { toNano } from '@ton/core'; +import { Address, toNano } from '@ton/core'; import { ContractSystem } from '@tact-lang/emulator'; import { __DANGER_resetNodeId } from '../grammar/ast'; import { Debug } from './features/output/debug_Debug'; @@ -8,13 +8,16 @@ describe('feature-debug', () => { __DANGER_resetNodeId(); }); it('should dump values correctly', async () => { - // Init const system = await ContractSystem.create(); const treasure = system.treasure('treasure'); const contract = system.open(await Debug.fromInit()); const logger = system.log(contract.address); - await contract.send(treasure, { value: toNano('10') }, { $$type: 'Deploy', queryId: 0n }); + await contract.send( + treasure, + { value: toNano('10') }, + { $$type: 'Deploy', queryId: 0n } + ); await system.run(); logger.reset(); @@ -22,9 +25,19 @@ describe('feature-debug', () => { await system.run(); const res = logger.collect(); - expect(res.indexOf('=== DEBUG LOGS ===')).toBeGreaterThan(-1); - expect(res.indexOf('Hello world!')).toBeGreaterThan(-1); - expect(res.indexOf('true')).toBeGreaterThan(-1); - expect(res.indexOf('false')).toBeGreaterThan(-1); + const debugLogs = res.slice( + res.indexOf('=== DEBUG LOGS ===') + 19, + res.indexOf('=== VM LOGS ===') - 2 + ); + expect(debugLogs).toStrictEqual(`stack(2 values) : 10000000000 () +Hello world! +123 +true +false +null +${contract.address.toString({ bounceable: true })} +${Address.parseRaw( + '0:83dfd552e63729b472fcbcc8c45ebcc6691702558b68ec7527e1ba403a0f31a8' +)}`); }); -}); \ No newline at end of file +}); diff --git a/src/test/features/debug.tact b/src/test/features/debug.tact index e62595437..e643b6401 100644 --- a/src/test/features/debug.tact +++ b/src/test/features/debug.tact @@ -13,6 +13,8 @@ contract Debug with Deployable { dump(true); dump(false); dump(null); + dump(myAddress()); + dump(newAddress(0, 0x83dfd552e63729b472fcbcc8c45ebcc6691702558b68ec7527e1ba403a0f31a8)); } get fun debug() { @@ -22,5 +24,7 @@ contract Debug with Deployable { dump(true); dump(false); dump(null); + dump(myAddress()); + dump(newAddress(0, 0x83dfd552e63729b472fcbcc8c45ebcc6691702558b68ec7527e1ba403a0f31a8)); } } \ No newline at end of file