Skip to content

Commit

Permalink
feat: verify
Browse files Browse the repository at this point in the history
  • Loading branch information
krigga committed Nov 23, 2023
1 parent c230d47 commit 1fd8605
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 21 deletions.
16 changes: 9 additions & 7 deletions src/cli/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@ import { UIProvider } from '../ui/UIProvider';
import arg from 'arg';
import { buildAll, buildOne } from '../build';

export const build: Runner = async (args: Args, ui: UIProvider) => {
require('ts-node/register');
export async function selectCompile(ui: UIProvider, args: Args) {
return await selectFile(await findCompiles(), {
ui,
hint: args._.length > 1 && args._[1].length > 0 ? args._[1] : undefined,
import: false,
});
}

export const build: Runner = async (args: Args, ui: UIProvider) => {
const localArgs = arg({
'--all': Boolean,
});

if (localArgs['--all']) {
await buildAll();
} else {
const sel = await selectFile(await findCompiles(), {
ui,
hint: args._.length > 1 && args._[1].length > 0 ? args._[1] : undefined,
import: false,
});
const sel = await selectCompile(ui, args);

await buildOne(sel.name, ui);
}
Expand Down
2 changes: 2 additions & 0 deletions src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { create } from './create';
import { run } from './run';
import { build } from './build';
import { test } from './test';
import { verify } from './verify';
import { additionalHelpMessages, help } from './help';
import { InquirerUIProvider } from '../ui/InquirerUIProvider';
import { argSpec, Runner } from './Runner';
Expand All @@ -19,6 +20,7 @@ const runners: Record<string, Runner> = {
build,
test,
help,
verify,
};

async function main() {
Expand Down
2 changes: 0 additions & 2 deletions src/cli/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { findScripts, selectFile } from '../utils';
import { UIProvider } from '../ui/UIProvider';

export const run: Runner = async (args: Args, ui: UIProvider) => {
require('ts-node/register');

const { module: mod } = await selectFile(await findScripts(), {
ui,
hint: args._.length > 1 && args._[1].length > 0 ? args._[1] : undefined,
Expand Down
2 changes: 0 additions & 2 deletions src/cli/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,5 @@ import { Runner } from './Runner';
import { execSync } from 'child_process';

export const test: Runner = async () => {
require('ts-node/register');

execSync('npm test', { stdio: 'inherit' });
};
248 changes: 248 additions & 0 deletions src/cli/verify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import { Address, Cell, Contract, ContractProvider, Dictionary, toNano } from '@ton/core';
import { doCompile } from '../compile/compile';
import { UIProvider } from '../ui/UIProvider';
import { Args, Runner } from './Runner';
import path from 'path';
import { createNetworkProvider } from '../network/createNetworkProvider';
import { selectCompile } from './build';

type FuncCompilerSettings = {
compiler: 'func';
compilerSettings: {
funcVersion: string;
commandLine: string;
};
};

type TactCompilerSettings = {
compiler: 'tact';
compilerSettings: {
tactVersion: string;
};
};

type CompilerSettings = FuncCompilerSettings | TactCompilerSettings;

type SourceObject = {
includeInCommand: boolean;
isEntrypoint: boolean;
isStdLib: boolean;
hasIncludeDirectives: boolean;
folder: string;
};

type SourcesObject = {
knownContractHash: string; // base64
knownContractAddress: string;
senderAddress: string;
sources: SourceObject[];
} & CompilerSettings;

const backends: Record<
'mainnet' | 'testnet',
{
verifierRegistry: Address;
backends: string[];
id: string;
}
> = {
mainnet: {
verifierRegistry: Address.parse('EQDS0AW7NV1w3nFwx-mmryfpH4OGQ3PXnoFGOJA_8PTHuLrw'),
backends: [
'https://ton-source-prod-1.herokuapp.com',
'https://ton-source-prod-2.herokuapp.com',
'https://ton-source-prod-3.herokuapp.com',
],
id: 'orbs.com',
},
testnet: {
verifierRegistry: Address.parse('EQB--CRXUbqYbqJKScEWeOrnk0TKJxB071M-WwvMpMEvq6Ed'),
backends: ['https://ton-source-prod-testnet-1.herokuapp.com'],
id: 'orbs-testnet',
},
};

function removeRandom<T>(els: T[]): T {
return els.splice(Math.floor(Math.random() * els.length), 1)[0];
}

class VerifierRegistry implements Contract {
constructor(readonly address: Address) {}

async getVerifiers(provider: ContractProvider) {
const res = await provider.get('get_verifiers', []);
const item = res.stack.readCell();
const c = item.beginParse();
const d = c.loadDict(Dictionary.Keys.BigUint(256), {
serialize: () => {
throw undefined;
},
parse: (s) => s,
});

return Array.from(d.values()).map((v) => {
const admin = v.loadAddress();
const quorom = v.loadUint(8);
const pubKeyEndpoints = v.loadDict(Dictionary.Keys.BigUint(256), Dictionary.Values.Uint(32));

return {
admin: admin,
quorum: quorom,
pubKeyEndpoints: new Map<bigint, number>(Array.from(pubKeyEndpoints).map(([k, v]) => [k, v])),
name: v.loadRef().beginParse().loadStringTail(),
url: v.loadRef().beginParse().loadStringTail(),
};
});
}
}

export const verify: Runner = async (args: Args, ui: UIProvider) => {
const sel = await selectCompile(ui, args);

const networkProvider = await createNetworkProvider(ui, false);

const sender = networkProvider.sender();

const senderAddress = sender.address;
if (senderAddress === undefined) {
throw new Error('Sender address needs to be known');
}

const network = networkProvider.network();
if (network === 'custom') {
throw new Error('Cannot use custom network');
}

const addr = await ui.input('Deployed contract address');

const result = await doCompile(sel.name);

let src: SourcesObject;
const fd = new FormData();

if (result.lang === 'func') {
for (const f of result.snapshot) {
fd.append(f.filename, new Blob([f.content]), path.basename(f.filename));
}

src = {
compiler: 'func',
compilerSettings: {
funcVersion: result.version,
commandLine: '-SPA ' + result.targets.join(' '),
},
knownContractAddress: addr,
knownContractHash: result.code.hash().toString('base64'),
sources: result.snapshot.map((s) => ({
includeInCommand: result.targets.includes(s.filename),
isEntrypoint: result.targets.includes(s.filename),
isStdLib: false,
hasIncludeDirectives: true,
folder: path.dirname(s.filename),
})),
senderAddress: senderAddress.toString(),
};
} else if (result.lang === 'tact') {
let pkg: { name: string; content: Buffer } | undefined = undefined;
for (const [k, v] of result.fs) {
if (k.endsWith('.pkg')) {
pkg = {
name: k,
content: v,
};
break;
}
}
if (pkg === undefined) {
throw new Error('Could not find .pkg in compilation results');
}

fd.append(path.basename(pkg.name), new Blob([pkg.content]), path.basename(pkg.name));

src = {
compiler: 'tact',
compilerSettings: {
tactVersion: '',
},
knownContractAddress: addr,
knownContractHash: result.code.hash().toString('base64'),
sources: [
{
includeInCommand: true,
isEntrypoint: false,
isStdLib: false,
hasIncludeDirectives: false,
folder: '',
},
],
senderAddress: senderAddress.toString(),
};
} else {
// future proofing

throw new Error('Unsupported language ' + (result as any).lang);
}

fd.append(
'json',
new Blob([JSON.stringify(src)], {
type: 'application/json',
}),
'blob',
);

const backend = backends[network];

const verifierRegistry = networkProvider.open(new VerifierRegistry(backend.verifierRegistry));

const verifier = (await verifierRegistry.getVerifiers()).find((v) => v.name === backend.id);
if (verifier === undefined) {
throw new Error('Could not find verifier');
}

const remainingBackends = [...backend.backends];

const sourceResponse = await fetch(removeRandom(remainingBackends) + '/source', {
method: 'POST',
body: fd,
});

if (sourceResponse.status !== 200) {
throw new Error('Could not compile on backend:\n' + (await sourceResponse.text()));
}

const sourceResult = await sourceResponse.json();

if (sourceResult.compileResult.result !== 'similar') {
throw new Error('The code is not similar');
}

let msgCell = sourceResult.msgCell;
let acquiredSigs = 1;

while (acquiredSigs < verifier.quorum) {
const signResponse = await fetch(removeRandom(remainingBackends) + '/sign', {
method: 'POST',
body: JSON.stringify({
messageCell: msgCell,
}),
});

if (signResponse.status !== 200) {
throw new Error('Could not compile on backend:\n' + (await signResponse.text()));
}

const signResult = await signResponse.json();

msgCell = signResult.msgCell;
acquiredSigs++;
}

const c = Cell.fromBoc(Buffer.from(msgCell.data))[0];

await networkProvider.sender().send({
to: backend.verifierRegistry,
value: toNano('0.5'),
body: c,
});
};
22 changes: 19 additions & 3 deletions src/compile/compile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { compileFunc, CompilerConfig as FuncCompilerConfig } from '@ton-community/func-js';
import {
compileFunc,
compilerVersion,
CompilerConfig as FuncCompilerConfig,
SourcesArray,
} from '@ton-community/func-js';
import { readFileSync } from 'fs';
import path from 'path';
import { Cell } from '@ton/core';
Expand All @@ -8,8 +13,6 @@ import { build } from '@tact-lang/compiler';
import { OverwritableVirtualFileSystem } from './OverwritableVirtualFileSystem';

async function getCompilerConfigForContract(name: string): Promise<CompilerConfig> {
require('ts-node/register');

const mod = await import(path.join(WRAPPERS_DIR, name + '.compile.ts'));

if (typeof mod.compile !== 'object') {
Expand All @@ -22,16 +25,29 @@ async function getCompilerConfigForContract(name: string): Promise<CompilerConfi
export type FuncCompileResult = {
lang: 'func';
code: Cell;
targets: string[];
snapshot: SourcesArray;
version: string;
};

async function doCompileFunc(config: FuncCompilerConfig): Promise<FuncCompileResult> {
const cr = await compileFunc(config);

if (cr.status === 'error') throw new Error(cr.message);

let targets: string[] = [];
if (config.targets) {
targets = config.targets;
} else if (Array.isArray(config.sources)) {
targets = config.sources.map((s) => s.filename);
}

return {
lang: 'func',
code: Cell.fromBase64(cr.codeBoc),
targets,
snapshot: cr.snapshot,
version: (await compilerVersion()).funcVersion,
};
}

Expand Down
Loading

0 comments on commit 1fd8605

Please sign in to comment.