From b5e6c08764c79d3db56eefd66d0d6f13e13e5f77 Mon Sep 17 00:00:00 2001 From: GiFTED! Date: Tue, 7 Jul 2026 12:18:49 +0100 Subject: [PATCH] fix(a12): decode real blockchain-config param values --- app/src/panels/A12-config/ConfigPanel.tsx | 38 +++++++---- app/src/rpc/contract.ts | 16 ++++- packages/bridge/handlers.ts | 83 +++++++++++++++++++++-- packages/mock-bridge/src/server.ts | 25 ++++++- 4 files changed, 142 insertions(+), 20 deletions(-) diff --git a/app/src/panels/A12-config/ConfigPanel.tsx b/app/src/panels/A12-config/ConfigPanel.tsx index 0e47bb8..c6fc033 100644 --- a/app/src/panels/A12-config/ConfigPanel.tsx +++ b/app/src/panels/A12-config/ConfigPanel.tsx @@ -3,6 +3,7 @@ import { Search, SlidersHorizontal, Upload } from 'lucide-react'; import { Panel } from '../../components/Panel'; import { useConfig, useSetConfig } from '../../hooks/useConfig'; import { usePontoonStore } from '../../state/store'; +import type { ConfigParam } from '../../rpc/contract'; import { Badge, Button, @@ -19,13 +20,6 @@ import { TableRow } from '../../components/ui'; -type ConfigParam = { - id: number; - label?: string; - kind: 'labeled' | 'raw'; - value: string | { boc: string }; -}; - export function ConfigPanel() { const workspaceId = usePontoonStore((s) => s.activeWorkspaceId); const query = useConfig(workspaceId); @@ -70,7 +64,10 @@ function ConfigBody({ params, onSet, setPending }: BodyProps) { (p) => String(p.id).includes(q) || (p.label ?? '').toLowerCase().includes(q) || - valueString(p.value).toLowerCase().includes(q) + valueString(p.value).toLowerCase().includes(q) || + (p.fields ?? []).some( + (f) => f.label.toLowerCase().includes(q) || f.value.toLowerCase().includes(q) + ) ); }, [params, filter]); @@ -116,7 +113,9 @@ function ConfigBody({ params, onSet, setPending }: BodyProps) { {filtered.map((param) => { const value = valueString(param.value); - const isBoc = typeof param.value !== 'string'; + const copyValue = param.fields + ? param.fields.map((f) => `${f.label}: ${f.value}`).join('\n') + : value; return ( @@ -127,14 +126,27 @@ function ConfigBody({ params, onSet, setPending }: BodyProps) { - {isBoc ? 'boc' : param.kind} + {param.kind} - - + + {param.fields ? ( +
+ {param.fields.map((f) => ( +
+
{f.label}
+
+ +
+
+ ))} +
+ ) : ( + + )}
- +
); diff --git a/app/src/rpc/contract.ts b/app/src/rpc/contract.ts index 71745d1..e1315f9 100644 --- a/app/src/rpc/contract.ts +++ b/app/src/rpc/contract.ts @@ -262,6 +262,20 @@ export interface SubscriptionResponse { subscriptionId: string; } +export interface DecodedConfigField { + label: string; + value: string; +} + +export interface ConfigParam { + id: number; + label?: string; + kind: 'labeled' | 'raw'; + value: string | { boc: string }; + /** Decoded fields for labeled params (fee/gas/validator values); absent for raw params. */ + fields?: DecodedConfigField[]; +} + export type MethodMap = { 'handshake': { req: HandshakeRequest; res: HandshakeResponse }; 'workspace.open': { req: { path: string }; res: WorkspaceRef }; @@ -294,7 +308,7 @@ export type MethodMap = { 'snapshot.export': { req: { workspaceId: string; id: string }; res: { boc: string } }; 'snapshot.import': { req: { workspaceId: string; name: string; boc: string }; res: SnapshotRef }; 'fork.status': { req: { workspaceId: string }; res: { enabled: boolean; mode: 'pinned-readonly'; network: 'mainnet' | 'testnet'; blockSeqno: number; accounts: { address: string; loaded: 'lazy' | 'cached'; overridden: boolean }[] } }; - 'config.get': { req: { workspaceId: string }; res: { params: { id: number; label?: string; kind: 'labeled' | 'raw'; value: string | { boc: string } }[] } }; + 'config.get': { req: { workspaceId: string }; res: { params: ConfigParam[] } }; 'config.set': { req: { workspaceId: string; boc: string }; res: { ok: boolean } }; 'library.list': { req: { workspaceId: string }; res: { autoDeployLibs: boolean; libs: { hash: string; cellBoc: string }[] } }; 'library.add': { req: { workspaceId: string; cellBoc: string }; res: { hash: string; cellBoc: string } }; diff --git a/packages/bridge/handlers.ts b/packages/bridge/handlers.ts index 2c12f32..03240e2 100644 --- a/packages/bridge/handlers.ts +++ b/packages/bridge/handlers.ts @@ -7,6 +7,12 @@ import { toNano, type Message } from '@ton/core'; +import { + configParse18, + configParseGasLimitsPrices, + configParseMsgPrices, + parseValidatorSet +} from '@ton/ton'; import { createShardAccount, snapshotFromSerializable, snapshotToSerializable } from '@ton/sandbox'; import { getWorkspace, listWorkspaces, recordTransactions, type Workspace } from './state.js'; import { addrToWire, big, cellToWire, tupleItemToWire, wireToCell, wireToTupleItem } from './encode.js'; @@ -128,6 +134,61 @@ async function runSend(ws: Workspace, msg: any) { return ws.blockchain.sendMessage(built.msg, { randomSeed } as any); } +type DecodedConfigField = { label: string; value: string }; + +function decodeConfigParam(id: number, cell: Cell): DecodedConfigField[] | null { + const s = cell.beginParse(); + switch (id) { + case 18: { + const prices = configParse18(s); + const latest = prices[prices.length - 1]; + if (!latest) return null; + return [ + { label: 'utime since', value: String(latest.utime_since) }, + { label: 'bit price ps', value: latest.bit_price_ps.toString() }, + { label: 'cell price ps', value: latest.cell_price_ps.toString() }, + { label: 'mc bit price ps', value: latest.mc_bit_price_ps.toString() }, + { label: 'mc cell price ps', value: latest.mc_cell_price_ps.toString() } + ]; + } + case 20: + case 21: { + const g = configParseGasLimitsPrices(s); + return [ + { label: 'gas price', value: g.other.gasPrice.toString() }, + { label: 'gas limit', value: g.other.gasLimit.toString() }, + { label: 'flat gas limit', value: g.flatLimit.toString() }, + { label: 'flat gas price', value: g.flatGasPrice.toString() }, + { label: 'block gas limit', value: g.other.blockGasLimit.toString() } + ]; + } + case 24: + case 25: { + const m = configParseMsgPrices(s); + return [ + { label: 'lump price', value: m.lumpPrice.toString() }, + { label: 'bit price', value: m.bitPrice.toString() }, + { label: 'cell price', value: m.cellPrice.toString() }, + { label: 'first frac', value: String(m.firstFrac) }, + { label: 'next frac', value: String(m.nextFrac) } + ]; + } + case 34: { + const v = parseValidatorSet(s); + if (!v) return null; + return [ + { label: 'utime since', value: String(v.timeSince) }, + { label: 'utime until', value: String(v.timeUntil) }, + { label: 'total', value: String(v.total) }, + { label: 'main', value: String(v.main) }, + { label: 'validators', value: String(v.list.size) } + ]; + } + default: + return null; + } +} + export const HANDLERS: Record = { // ── workspace ── 'workspace.open': async (p) => { @@ -418,11 +479,23 @@ export const HANDLERS: Record = { ); for (const id of dict.keys().sort((a, b) => a - b)) { const cell = dict.get(id)!; - params.push( - labels[id] - ? { id, label: labels[id], kind: 'labeled', value: big(0n) } - : { id, kind: 'raw', value: { boc: cellToWire(cell) } } - ); + if (!labels[id]) { + params.push({ id, kind: 'raw', value: { boc: cellToWire(cell) } }); + continue; + } + let fields: DecodedConfigField[] | null = null; + try { + fields = decodeConfigParam(id, cell); + } catch { + /* unexpected TL-B tag — fall back to the raw cell below */ + } + params.push({ + id, + label: labels[id], + kind: 'labeled', + value: { boc: cellToWire(cell) }, + ...(fields ? { fields } : {}) + }); } } catch { params.push({ id: 0, kind: 'raw', value: { boc: cellToWire(ws.blockchain.config) } }); diff --git a/packages/mock-bridge/src/server.ts b/packages/mock-bridge/src/server.ts index 8dfeb9d..36f7a62 100644 --- a/packages/mock-bridge/src/server.ts +++ b/packages/mock-bridge/src/server.ts @@ -115,7 +115,30 @@ wss.on('connection', (ws) => { break; } case 'fork.status': ok({ enabled: true, mode: 'pinned-readonly', network: 'testnet', blockSeqno: 123456, accounts: [{ address: '0:pool', loaded: 'lazy', overridden: false }] }); break; - case 'config.get': ok({ params: [{ id: 18, label: 'storage prices', kind: 'labeled', value: '1' }, { id: 20, label: 'gas prices masterchain', kind: 'labeled', value: '1' }, { id: 99, kind: 'raw', value: { boc: 'te6ccgEBAQEAAgAAAA==' } }] }); break; + case 'config.get': ok({ params: [ + { id: 18, label: 'storage prices', kind: 'labeled', value: { boc: 'te6ccgEBAQEAAgAAAA==' }, fields: [ + { label: 'utime since', value: '0' }, + { label: 'bit price ps', value: '1' }, + { label: 'cell price ps', value: '500' }, + { label: 'mc bit price ps', value: '1000' }, + { label: 'mc cell price ps', value: '500000' } + ] }, + { id: 20, label: 'gas prices (masterchain)', kind: 'labeled', value: { boc: 'te6ccgEBAQEAAgAAAA==' }, fields: [ + { label: 'gas price', value: '655360000' }, + { label: 'gas limit', value: '1000000' }, + { label: 'flat gas limit', value: '100' }, + { label: 'flat gas price', value: '1000000' }, + { label: 'block gas limit', value: '10000000' } + ] }, + { id: 24, label: 'msg fwd prices (masterchain)', kind: 'labeled', value: { boc: 'te6ccgEBAQEAAgAAAA==' }, fields: [ + { label: 'lump price', value: '10000000' }, + { label: 'bit price', value: '655360000' }, + { label: 'cell price', value: '65536000000' }, + { label: 'first frac', value: '21845' }, + { label: 'next frac', value: '21845' } + ] }, + { id: 99, kind: 'raw', value: { boc: 'te6ccgEBAQEAAgAAAA==' } } + ] }); break; case 'library.list': ok({ autoDeployLibs: true, libs: [{ hash: 'lib-hash', cellBoc: 'te6ccgEBAQEAAgAAAA==' }] }); break; case 'library.add': ok({ hash: 'lib-new', cellBoc: req.params.cellBoc }); break; case 'logs.subscribe': {