From 31beaeea51c9123b9157be4ab004422cd8a9c637 Mon Sep 17 00:00:00 2001 From: Shahar Yakir Date: Tue, 14 Jun 2022 12:06:24 +0300 Subject: [PATCH] Support func 0.2.0 (consts, pragma version, includes) (#4) * tests pass * update gitignore * removed console.log * consts * restore prettier + compiled file name --- build/.gitignore | 3 +- build/_build.ts | 83 ++++++++++++++++++++++------------ contracts/imports/constants.fc | 16 +++---- contracts/main.fc | 24 ++++++---- package-lock.json | 67 +++++++++++++++++++++++++++ package.json | 4 ++ test/counter.spec.ts | 5 +- test/deposit.spec.ts | 5 +- test/ownership.spec.ts | 5 +- tsconfig.json | 5 +- 10 files changed, 161 insertions(+), 56 deletions(-) diff --git a/build/.gitignore b/build/.gitignore index 22e613e..9083908 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1,3 +1,4 @@ *.fif *.fc -*.cell \ No newline at end of file +*.cell +*-bitcode.json \ No newline at end of file diff --git a/build/_build.ts b/build/_build.ts index 9da0d66..f379f09 100644 --- a/build/_build.ts +++ b/build/_build.ts @@ -11,10 +11,12 @@ import path from "path"; import process from "process"; import child_process from "child_process"; import glob from "fast-glob"; +import { Cell } from "ton"; +import semver from "semver"; async function main() { - console.log(`=================================================================`); - console.log(`Build script running, let's find some FunC contracts to compile..`); + console.log("================================================================="); + console.log("Build script running, let's find some FunC contracts to compile.."); // if we have an explicit bin directory, use the executables there (needed for glitch.com) if (fs.existsSync("bin")) { @@ -23,12 +25,15 @@ async function main() { } // make sure func compiler is available - let funcVersion = ""; + const minSupportFunc = "0.2.0"; try { - funcVersion = child_process.execSync("func -V").toString(); - } catch (e) {} - if (!funcVersion.includes(`Func build information`)) { - console.log(`\nFATAL ERROR: 'func' executable is not found, is it installed and in path?`); + const funcVersion = child_process + .execSync("func -V") + .toString() + .match(/semantic version: v([0-9.]+)/)?.[1]; + if (!semver.gte(semver.coerce(funcVersion) ?? "", minSupportFunc)) throw new Error("Nonexistent version or outdated"); + } catch (e) { + console.log(`\nFATAL ERROR: 'func' with version >= ${minSupportFunc} executable is not found, is it installed and in path?`); process.exit(1); } @@ -37,8 +42,8 @@ async function main() { try { fiftVersion = child_process.execSync("fift -V").toString(); } catch (e) {} - if (!fiftVersion.includes(`Fift build information`)) { - console.log(`\nFATAL ERROR: 'fift' executable is not found, is it installed and in path?`); + if (!fiftVersion.includes("Fift build information")) { + console.log("\nFATAL ERROR: 'fift' executable is not found, is it installed and in path?"); process.exit(1); } @@ -70,6 +75,11 @@ async function main() { console.log(` - Deleting old build artifact '${cellArtifact}'`); fs.unlinkSync(cellArtifact); } + const hexArtifact = `build/${contractName}.compiled.json`; + if (fs.existsSync(hexArtifact)) { + console.log(` - Deleting old build artifact '${hexArtifact}'`); + fs.unlinkSync(hexArtifact); + } // check if we have a tlb file const tlbFile = `contracts/${contractName}.tlb`; @@ -87,27 +97,20 @@ async function main() { console.log(` - Warning: TL-B file for contract '${tlbFile}' not found, are your op consts according to standard?`); } - // create a merged fc file with source code from all dependencies - let sourceToCompile = ""; - const importFiles = glob.sync([`contracts/imports/*.fc`, `contracts/imports/*.func`, `contracts/imports/${contractName}/*.fc`, `contracts/imports/${contractName}/*.func`]); - for (const importFile of importFiles) { - console.log(` - Adding import '${importFile}'`); - sourceToCompile += `${fs.readFileSync(importFile).toString()}\n`; - } - console.log(` - Adding the contract itself '${rootContract}'`); - sourceToCompile += `${fs.readFileSync(rootContract).toString()}\n`; - fs.writeFileSync(mergedFuncArtifact, sourceToCompile); - console.log(` - Build artifact created '${mergedFuncArtifact}'`); - // run the func compiler to create a fif file - console.log(` - Trying to compile '${mergedFuncArtifact}' with 'func' compiler..`); - const buildErrors = child_process.execSync(`func -APS -o build/${contractName}.fif ${mergedFuncArtifact} 2>&1 1>node_modules/.tmpfunc`).toString(); + console.log(` - Trying to compile '${rootContract}' with 'func' compiler..`); + let buildErrors: string; + try { + buildErrors = child_process.execSync(`func -APS -o build/${contractName}.fif ${rootContract} 2>&1 1>node_modules/.tmpfunc`).toString(); + } catch (e) { + buildErrors = e.stdout.toString(); + } if (buildErrors.length > 0) { - console.log(` - OH NO! Compilation Errors! The compiler output was:`); + console.log(" - OH NO! Compilation Errors! The compiler output was:"); console.log(`\n${buildErrors}`); process.exit(1); } else { - console.log(` - Compilation successful!`); + console.log(" - Compilation successful!"); } // make sure fif build artifact was created @@ -119,7 +122,7 @@ async function main() { } // create a temp cell.fif that will generate the cell - let fiftCellSource = `"Asm.fif" include\n`; + let fiftCellSource = '"Asm.fif" include\n'; fiftCellSource += `${fs.readFileSync(fiftArtifact).toString()}\n`; fiftCellSource += `boc>B "${cellArtifact}" B>file`; fs.writeFileSync(fiftCellArtifact, fiftCellSource); @@ -128,21 +131,41 @@ async function main() { try { child_process.execSync(`fift ${fiftCellArtifact}`); } catch (e) { - console.log(`FATAL ERROR: 'fift' executable failed, is FIFTPATH env variable defined?`); + console.log("FATAL ERROR: 'fift' executable failed, is FIFTPATH env variable defined?"); process.exit(1); } + // Remove intermediary + fs.unlinkSync(fiftCellArtifact); + // make sure cell build artifact was created if (!fs.existsSync(cellArtifact)) { console.log(` - For some reason '${cellArtifact}' was not created!`); process.exit(1); } else { console.log(` - Build artifact created '${cellArtifact}'`); - fs.unlinkSync(fiftCellArtifact); + } + + fs.writeFileSync( + hexArtifact, + JSON.stringify({ + hex: Cell.fromBoc(fs.readFileSync(cellArtifact))[0].toBoc().toString("hex"), + }) + ); + + // Remove intermediary + fs.unlinkSync(cellArtifact); + + // make sure hex artifact was created + if (!fs.existsSync(hexArtifact)) { + console.log(` - For some reason '${hexArtifact}' was not created!`); + process.exit(1); + } else { + console.log(` - Build artifact created '${hexArtifact}'`); } } - console.log(``); + console.log(""); } main(); @@ -152,7 +175,7 @@ main(); function crc32(r: string) { for (var a, o = [], c = 0; c < 256; c++) { a = c; - for (var f = 0; f < 8; f++) a = 1 & a ? 3988292384 ^ (a >>> 1) : a >>> 1; + for (let f = 0; f < 8; f++) a = 1 & a ? 3988292384 ^ (a >>> 1) : a >>> 1; o[c] = a; } for (var n = -1, t = 0; t < r.length; t++) n = (n >>> 8) ^ o[255 & (n ^ r.charCodeAt(t))]; diff --git a/contracts/imports/constants.fc b/contracts/imports/constants.fc index c260f95..ce7ebf6 100644 --- a/contracts/imports/constants.fc +++ b/contracts/imports/constants.fc @@ -1,13 +1,13 @@ ;; operations (constant values taken from crc32 on op message in the companion .tlb files and appear during build) -int op::increment() asm "0x37491f2f PUSHINT"; -int op::deposit() asm "0x47d54391 PUSHINT"; -int op::withdraw() asm "0x41836980 PUSHINT"; -int op::transfer_ownership() asm "0x2da38aaf PUSHINT"; +const op::increment = 0x37491f2f; +const op::deposit = 0x47d54391; +const op::withdraw = 0x41836980; +const op::transfer_ownership = 0x2da38aaf; ;; errors -int error::unknown_op() asm "101 PUSHINT"; -int error::access_denied() asm "102 PUSHINT"; -int error::insufficient_balance() asm "103 PUSHINT"; +const error::unknown_op = 101; +const error::access_denied = 102; +const error::insufficient_balance = 103; ;; other -int const::min_tons_for_storage() asm "10000000 PUSHINT"; ;; 0.01 TON \ No newline at end of file +const const::min_tons_for_storage = 10000000; ;; 0.01 TON \ No newline at end of file diff --git a/contracts/main.fc b/contracts/main.fc index 40ceb53..deb1697 100644 --- a/contracts/main.fc +++ b/contracts/main.fc @@ -2,6 +2,12 @@ ;; storage binary format is defined as TL-B in companion .tlb file +#pragma version >=0.2.0; + +#include "imports/stdlib.fc"; +#include "imports/constants.fc"; +#include "imports/utils.fc"; + (slice, int) load_data() inline { var ds = get_data().begin_parse(); return ( @@ -41,38 +47,38 @@ int op = in_msg_body~load_uint(32); int query_id = in_msg_body~load_uint(64); - if (op == op::increment()) { + if (op == op::increment) { save_data(owner_address, counter + 1); return (); } - if (op == op::deposit()) { + if (op == op::deposit) { ;; empty since ton received (msg_value) is added automatically to contract balance ;; ~dump msg_value; ;; an example of debug output, requires running contract in debug mode return (); } - if (op == op::withdraw()) { - throw_unless(error::access_denied(), equal_slices(sender_address, owner_address)); + if (op == op::withdraw) { + throw_unless(error::access_denied, equal_slices(sender_address, owner_address)); int withdraw_amount = in_msg_body~load_coins(); op_withdraw(withdraw_amount, owner_address); return (); } - if (op == op::transfer_ownership()) { - throw_unless(error::access_denied(), equal_slices(sender_address, owner_address)); + if (op == op::transfer_ownership) { + throw_unless(error::access_denied, equal_slices(sender_address, owner_address)); slice new_owner_address = in_msg_body~load_msg_addr(); save_data(new_owner_address, counter); return (); } - throw(error::unknown_op()); + throw(error::unknown_op); } () op_withdraw(int withdraw_amount, slice owner_address) impure { var [balance, _] = get_balance(); - throw_unless(error::insufficient_balance(), balance >= withdraw_amount); - int return_value = min(withdraw_amount, balance - const::min_tons_for_storage()); + throw_unless(error::insufficient_balance, balance >= withdraw_amount); + int return_value = min(withdraw_amount, balance - const::min_tons_for_storage); send_grams(owner_address, return_value); } diff --git a/package-lock.json b/package-lock.json index 3d8b17a..ef52e5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,15 @@ "version": "0.0.0", "hasInstallScript": true, "license": "MIT", + "dependencies": { + "semver": "^7.3.7" + }, "devDependencies": { "@swc/core": "^1.2.177", "@types/bn.js": "^5.1.0", "@types/chai": "^4.3.0", "@types/mocha": "^9.0.0", + "@types/semver": "^7.3.9", "axios-request-throttle": "^1.0.0", "chai": "^4.3.4", "chai-bn": "^0.3.1", @@ -403,6 +407,12 @@ "integrity": "sha512-tx5jMmMFwx7wBwq/V7OohKDVb/JwJU5qCVkeLMh1//xycAJ/ESuw9aJ9SEtlCZDYi2pBfe4JkisSoAtbOsBNAA==", "dev": true }, + "node_modules/@types/semver": { + "version": "7.3.9", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", + "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", + "dev": true + }, "node_modules/@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", @@ -1215,6 +1225,17 @@ "get-func-name": "^2.0.0" } }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -1568,6 +1589,20 @@ } ] }, + "node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -1900,6 +1935,11 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -2192,6 +2232,12 @@ "integrity": "sha512-tx5jMmMFwx7wBwq/V7OohKDVb/JwJU5qCVkeLMh1//xycAJ/ESuw9aJ9SEtlCZDYi2pBfe4JkisSoAtbOsBNAA==", "dev": true }, + "@types/semver": { + "version": "7.3.9", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", + "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", + "dev": true + }, "@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", @@ -2787,6 +2833,14 @@ "get-func-name": "^2.0.0" } }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -3015,6 +3069,14 @@ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "requires": { + "lru-cache": "^6.0.0" + } + }, "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -3270,6 +3332,11 @@ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", diff --git a/package.json b/package.json index b560536..8b5f080 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@types/bn.js": "^5.1.0", "@types/chai": "^4.3.0", "@types/mocha": "^9.0.0", + "@types/semver": "^7.3.9", "axios-request-throttle": "^1.0.0", "chai": "^4.3.4", "chai-bn": "^0.3.1", @@ -43,5 +44,8 @@ }, "engines": { "node": ">=16.0.0" + }, + "dependencies": { + "semver": "^7.3.7" } } diff --git a/test/counter.spec.ts b/test/counter.spec.ts index bb0055c..9477ef6 100644 --- a/test/counter.spec.ts +++ b/test/counter.spec.ts @@ -3,18 +3,19 @@ import chaiBN from "chai-bn"; import BN from "bn.js"; chai.use(chaiBN(BN)); -import * as fs from "fs"; import { Cell } from "ton"; import { SmartContract } from "ton-contract-executor"; import * as main from "../contracts/main"; import { internalMessage, randomAddress } from "./helpers"; +import { hex } from "../build/main-bitcode.json"; + describe("Counter tests", () => { let contract: SmartContract; beforeEach(async () => { contract = await SmartContract.fromCell( - Cell.fromBoc(fs.readFileSync("build/main.cell"))[0], // code cell from build output + Cell.fromBoc(hex)[0], // code cell from build output main.data({ ownerAddress: randomAddress("owner"), counter: 17, diff --git a/test/deposit.spec.ts b/test/deposit.spec.ts index b682c08..f3daca1 100644 --- a/test/deposit.spec.ts +++ b/test/deposit.spec.ts @@ -3,18 +3,19 @@ import chaiBN from "chai-bn"; import BN from "bn.js"; chai.use(chaiBN(BN)); -import * as fs from "fs"; import { Cell, toNano } from "ton"; import { SmartContract } from "ton-contract-executor"; import * as main from "../contracts/main"; import { internalMessage, randomAddress, setBalance } from "./helpers"; +import { hex } from "../build/main-bitcode.json"; + describe("Deposit and withdraw tests", () => { let contract: SmartContract; beforeEach(async () => { contract = await SmartContract.fromCell( - Cell.fromBoc(fs.readFileSync("build/main.cell"))[0], // code cell from build output + Cell.fromBoc(hex)[0], // code cell from build output main.data({ ownerAddress: randomAddress("owner"), counter: 17, diff --git a/test/ownership.spec.ts b/test/ownership.spec.ts index 7feac58..21adca3 100644 --- a/test/ownership.spec.ts +++ b/test/ownership.spec.ts @@ -3,18 +3,19 @@ import chaiBN from "chai-bn"; import BN from "bn.js"; chai.use(chaiBN(BN)); -import * as fs from "fs"; import { Cell, Slice } from "ton"; import { SmartContract } from "ton-contract-executor"; import * as main from "../contracts/main"; import { internalMessage, randomAddress } from "./helpers"; +import { hex } from "../build/main-bitcode.json"; + describe("Transfer ownership tests", () => { let contract: SmartContract; beforeEach(async () => { contract = await SmartContract.fromCell( - Cell.fromBoc(fs.readFileSync("build/main.cell"))[0], // code cell from build output + Cell.fromBoc(hex)[0], // code cell from build output main.data({ ownerAddress: randomAddress("owner"), counter: 17, diff --git a/tsconfig.json b/tsconfig.json index 1113141..3bbcdda 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,10 +5,11 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, - "skipLibCheck": true + "skipLibCheck": true, + "resolveJsonModule": true }, "ts-node": { "transpileOnly": true, "transpiler": "ts-node/transpilers/swc" } -} \ No newline at end of file +}