Browse Source

Added deploy script

master
Tal Kol 3 years ago
parent
commit
f6ca83c40d
  1. 10
      README.md
  2. 1
      build/.gitignore
  3. 41
      build/build.ts
  4. 110
      build/deploy.ts
  5. 10
      build/main.deploy.ts
  6. 37
      package-lock.json
  7. 4
      package.json

10
README.md

@ -14,7 +14,6 @@ This project is part of a set of 3 typical repositories needed for a blockchain
* `contracts/*.fc` - Smart contracts for TON blockchain written in [FunC](https://ton.org/docs/#/func) language * `contracts/*.fc` - Smart contracts for TON blockchain written in [FunC](https://ton.org/docs/#/func) language
* `build/build.ts` - Build script to compile the FunC code to [Fift](https://ton-blockchain.github.io/docs/fiftbase.pdf) * `build/build.ts` - Build script to compile the FunC code to [Fift](https://ton-blockchain.github.io/docs/fiftbase.pdf)
* `build/*.fif` - Output Fift files for every contract that was compiled, not uploaded to git
* `build/deploy.ts` - Deploy script to deploy the compiled code to TON mainnet * `build/deploy.ts` - Deploy script to deploy the compiled code to TON mainnet
* `test/*.spec.ts` - Test suite for the contracts running on [Mocha](https://mochajs.org/) test runner * `test/*.spec.ts` - Test suite for the contracts running on [Mocha](https://mochajs.org/) test runner
@ -65,11 +64,18 @@ To setup your machine for development, please make sure you have the following:
* Build * Build
* In the root repo dir, run in terminal `npm run build` * In the root repo dir, run in terminal `npm run build`
* Compilation errors will appear on screen * Compilation errors will appear on screen
* Resulting build artifacts include:
* `mycontract.merged.fc` - merged and flattened FunC source code with all imports
* `mycontract.fif` - Fift file result of compilation (not very useful by itself)
* `mycontract.cell` - the binary code cell of the compiled contract (for deployment)
* Test * Test
* In the root repo dir, run in terminal `npm run test` * In the root repo dir, run in terminal `npm run test`
* Make sure to build before running tests * Don't forget to build (or rebuild) before running tests
* Tests are running inside Node.js by running TVM in web-assembly using `ton-contract-executor`
* Deploy * Deploy
* In the root repo dir, run in terminal `npm run deploy` * In the root repo dir, run in terminal `npm run deploy`
* Follow the on-screen instructions to deploy to mainnet * Follow the on-screen instructions to deploy to mainnet
* Each contract to deploy should have a script `build/mycontract.deploy.ts` to return its init data cell
* The deployment wallet is configured in `build/deploy.config.json` (file will be created if not found)

1
build/.gitignore vendored

@ -1,3 +1,4 @@
*.fif *.fif
*.fc *.fc
*.cell *.cell
deploy.config.json

41
build/build.ts

@ -1,5 +1,5 @@
// This is a simple generic build script in TypeScript that should work for most projects without modification // This is a simple generic build script in TypeScript that should work for most projects without modification
// It assumes that it is running from the repo root, and the directories are organized this way: // The script assumes that it is running from the repo root, and the directories are organized this way:
// ./build/ - directory for build artifacts exists // ./build/ - directory for build artifacts exists
// ./contracts/*.fc - root contracts that are deployed separately are here // ./contracts/*.fc - root contracts that are deployed separately are here
// ./contracts/imports/*.fc - shared utility code that should be imported as compilation dependency is here // ./contracts/imports/*.fc - shared utility code that should be imported as compilation dependency is here
@ -12,31 +12,33 @@ import process from "process";
import child_process from "child_process"; import child_process from "child_process";
import glob from "fast-glob"; import glob from "fast-glob";
console.log(`=================================================================`); async function main() {
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..`);
// make sure func compiler is available // make sure func compiler is available
let funcVersion = ""; let funcVersion = "";
try { try {
funcVersion = child_process.execSync("func -V").toString(); funcVersion = child_process.execSync("func -V").toString();
} catch (e) {} } catch (e) {}
if (!funcVersion.includes(`Func build information`)) { if (!funcVersion.includes(`Func build information`)) {
console.log(`\nFATAL ERROR: 'func' executable is not found, is it installed and in path?`); console.log(`\nFATAL ERROR: 'func' executable is not found, is it installed and in path?`);
process.exit(1); process.exit(1);
} }
// make sure fift cli is available // make sure fift cli is available
let fiftVersion = ""; let fiftVersion = "";
try { try {
fiftVersion = child_process.execSync("fift -V").toString(); fiftVersion = child_process.execSync("fift -V").toString();
} catch (e) {} } catch (e) {}
if (!fiftVersion.includes(`Fift build information`)) { if (!fiftVersion.includes(`Fift build information`)) {
console.log(`\nFATAL ERROR: 'fift' executable is not found, is it installed and in path?`); console.log(`\nFATAL ERROR: 'fift' executable is not found, is it installed and in path?`);
process.exit(1); process.exit(1);
} }
const rootContracts = glob.sync(["contracts/*.fc", "contracts/*.func"]); // go over all the root contracts in the contracts directory
for (const rootContract of rootContracts) { const rootContracts = glob.sync(["contracts/*.fc", "contracts/*.func"]);
for (const rootContract of rootContracts) {
// compile a new root contract // compile a new root contract
console.log(`\n* Found root contract '${rootContract}' - let's compile it:`); console.log(`\n* Found root contract '${rootContract}' - let's compile it:`);
const contractName = path.parse(rootContract).name; const contractName = path.parse(rootContract).name;
@ -132,9 +134,12 @@ for (const rootContract of rootContracts) {
console.log(` - Build artifact created '${cellArtifact}'`); console.log(` - Build artifact created '${cellArtifact}'`);
fs.unlinkSync(fiftCellArtifact); fs.unlinkSync(fiftCellArtifact);
} }
}
console.log(``);
} }
console.log(``); main();
// helpers // helpers

110
build/deploy.ts

@ -0,0 +1,110 @@
// This is a simple generic deploy script in TypeScript that should work for most projects without modification
// Every contract you want to deploy should have a mycontract.deploy.ts script that returns its init data
// The script assumes that it is running from the repo root, and the directories are organized this way:
// ./build/ - directory for build artifacts (mycontract.cell) and deploy init data scripts (mycontract.deploy.ts)
// ./build/deploy.config.json - JSON config file with secret mnemonic of deploying wallet (will be created if not found)
import fs from "fs";
import path from "path";
import glob from "fast-glob";
import { Address, Cell, CommonMessageInfo, contractAddress, fromNano, InternalMessage, SendMode, StateInit, toNano, TonClient, WalletContract, WalletV3R2Source } from "ton";
import { mnemonicNew, mnemonicToWalletKey } from "ton-crypto";
import { BN } from "bn.js";
async function main() {
console.log(`=================================================================`);
console.log(`Deploy script running, let's find some contracts to deploy..`);
// make sure we have a wallet mnemonic to deploy from (or create one if not found)
const deployConfigJson = `build/deploy.config.json`;
let deployerMnemonic;
if (!fs.existsSync(deployConfigJson)) {
console.log(`\n* Config file '${deployConfigJson}' not found, creating a new wallet for deploy..`);
deployerMnemonic = (await mnemonicNew(24)).join(" ");
const deployWalletJsonContent = { created: new Date().toISOString(), deployerMnemonic };
fs.writeFileSync(deployConfigJson, JSON.stringify(deployWalletJsonContent, null, 2));
console.log(` - Created new wallet in '${deployConfigJson}' - keep this file secret!`);
} else {
console.log(`\n* Config file '${deployConfigJson}' found and will be used for deployment!`);
const deployConfigJsonContent = require(__dirname + "/../" + deployConfigJson);
if (!deployConfigJsonContent.deployerMnemonic) {
console.log(` - ERROR: '${deployConfigJson}' does not have the key 'deployerMnemonic'`);
process.exit(1);
}
deployerMnemonic = deployConfigJsonContent.deployerMnemonic;
}
// open the wallet and make sure it has enough TON
const client = new TonClient({ endpoint: "https://toncenter.com/api/v2/jsonRPC" });
const walletKey = await mnemonicToWalletKey(deployerMnemonic.split(" "));
const walletContract = WalletContract.create(client, WalletV3R2Source.create({ publicKey: walletKey.publicKey, workchain: 0 }));
console.log(` - Wallet address used for deployment is: ${walletContract.address.toFriendly()}`);
const walletBalance = await client.getBalance(walletContract.address);
if (walletBalance.lt(toNano(1))) {
console.log(` - ERROR: Wallet has less than 1 TON for gas (${fromNano(walletBalance)} TON), please send some TON for gas first`);
process.exit(1);
}
// go over all the contracts we have deploy scripts for
const rootContracts = glob.sync(["build/*.deploy.ts"]);
for (const rootContract of rootContracts) {
// deploy a new root contract
console.log(`\n* Found root contract to deploy '${rootContract}':`);
const contractName = path.parse(path.parse(rootContract).name).name;
// prepare the init data cell
const deployInit = require(__dirname + "/../" + rootContract);
if (typeof deployInit.initData !== "function") {
console.log(` - ERROR: '${rootContract}' does not have 'initData()' function`);
process.exit(1);
}
const initDataCell = deployInit.initData() as Cell;
// prepare the init code cell
const cellArtifact = `build/${contractName}.cell`;
if (!fs.existsSync(cellArtifact)) {
console.log(` - ERROR: '${cellArtifact}' not found, did you build?`);
process.exit(1);
}
const initCodeCell = Cell.fromBoc(fs.readFileSync(cellArtifact))[0];
// deploy by sending an internal message to the deploying wallet
sleep(1000); // to make sure we don't fail due to throttling
const newContractAddress = contractAddress({ workchain: 0, initialData: initDataCell, initialCode: initCodeCell });
console.log(` - About to deploy contract to new address: ${newContractAddress.toFriendly()}`);
const seqno = await getSeqNo(client, walletContract.address);
sleep(1000); // to make sure we don't fail due to throttling
const transfer = await walletContract.createTransfer({
secretKey: walletKey.secretKey,
seqno: seqno,
sendMode: SendMode.PAY_GAS_SEPARATLY + SendMode.IGNORE_ERRORS,
order: new InternalMessage({
to: newContractAddress,
value: new BN(0.5),
bounce: false,
body: new CommonMessageInfo({ stateInit: new StateInit({ data: initDataCell, code: initCodeCell }) }),
}),
});
await client.sendExternalMessage(walletContract, transfer);
console.log(` - Contract deployed successfully!`);
}
console.log(``);
}
main();
// helpers
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function getSeqNo(client: TonClient, walletAddress: Address) {
if (await client.isContractDeployed(walletAddress)) {
let res = await client.callGetMethod(walletAddress, "seqno");
return parseInt(res.stack[0][1], 16);
} else {
return 0;
}
}

10
build/main.deploy.ts

@ -0,0 +1,10 @@
import * as main from "../contracts/main";
import { Address } from "ton";
// return the init Cell of the contract storage (according to load_data() contract method)
export function initData() {
return main.data({
ownerAddress: Address.parseFriendly("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N").address,
counter: 0,
});
}

37
package-lock.json generated

@ -20,6 +20,7 @@
"prettier": "^2.6.2", "prettier": "^2.6.2",
"ton": "^9.6.3", "ton": "^9.6.3",
"ton-contract-executor": "^0.4.8", "ton-contract-executor": "^0.4.8",
"ton-crypto": "^3.1.0",
"ts-node": "^10.4.0", "ts-node": "^10.4.0",
"typescript": "^4.5.4" "typescript": "^4.5.4"
} }
@ -1438,9 +1439,9 @@
} }
}, },
"node_modules/ton-crypto": { "node_modules/ton-crypto": {
"version": "2.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/ton-crypto/-/ton-crypto-2.1.0.tgz", "resolved": "https://registry.npmjs.org/ton-crypto/-/ton-crypto-3.1.0.tgz",
"integrity": "sha512-PZnmCOShfgq9tCRM8E7hG8nCkpkOyZvDLPXmZN92ZEBrfTT0NKKf0imndkxG5DkgWMjc6IKfgpnEaJDH9qN6ZQ==", "integrity": "sha512-OgUuGoT8UKvm5jvRd/fiCo46MCrlOMt9Nr7nPQC3vkLjKmbDk+qJ4gQsO14IZwrOm5xkhDMlF5ZTVH/kN9y0gg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"jssha": "3.2.0", "jssha": "3.2.0",
@ -1457,6 +1458,17 @@
"jssha": "3.2.0" "jssha": "3.2.0"
} }
}, },
"node_modules/ton/node_modules/ton-crypto": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ton-crypto/-/ton-crypto-2.1.0.tgz",
"integrity": "sha512-PZnmCOShfgq9tCRM8E7hG8nCkpkOyZvDLPXmZN92ZEBrfTT0NKKf0imndkxG5DkgWMjc6IKfgpnEaJDH9qN6ZQ==",
"dev": true,
"dependencies": {
"jssha": "3.2.0",
"ton-crypto-primitives": "2.0.0",
"tweetnacl": "1.0.3"
}
},
"node_modules/ts-node": { "node_modules/ts-node": {
"version": "10.7.0", "version": "10.7.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz",
@ -2660,6 +2672,19 @@
"teslabot": "^1.3.0", "teslabot": "^1.3.0",
"ton-crypto": "2.1.0", "ton-crypto": "2.1.0",
"tweetnacl": "1.0.3" "tweetnacl": "1.0.3"
},
"dependencies": {
"ton-crypto": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ton-crypto/-/ton-crypto-2.1.0.tgz",
"integrity": "sha512-PZnmCOShfgq9tCRM8E7hG8nCkpkOyZvDLPXmZN92ZEBrfTT0NKKf0imndkxG5DkgWMjc6IKfgpnEaJDH9qN6ZQ==",
"dev": true,
"requires": {
"jssha": "3.2.0",
"ton-crypto-primitives": "2.0.0",
"tweetnacl": "1.0.3"
}
}
} }
}, },
"ton-compiler": { "ton-compiler": {
@ -2692,9 +2717,9 @@
} }
}, },
"ton-crypto": { "ton-crypto": {
"version": "2.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/ton-crypto/-/ton-crypto-2.1.0.tgz", "resolved": "https://registry.npmjs.org/ton-crypto/-/ton-crypto-3.1.0.tgz",
"integrity": "sha512-PZnmCOShfgq9tCRM8E7hG8nCkpkOyZvDLPXmZN92ZEBrfTT0NKKf0imndkxG5DkgWMjc6IKfgpnEaJDH9qN6ZQ==", "integrity": "sha512-OgUuGoT8UKvm5jvRd/fiCo46MCrlOMt9Nr7nPQC3vkLjKmbDk+qJ4gQsO14IZwrOm5xkhDMlF5ZTVH/kN9y0gg==",
"dev": true, "dev": true,
"requires": { "requires": {
"jssha": "3.2.0", "jssha": "3.2.0",

4
package.json

@ -6,8 +6,9 @@
"author": "", "author": "",
"scripts": { "scripts": {
"prettier": "npx prettier --write '{test,contracts,build}/**/*.{ts,js,json}'", "prettier": "npx prettier --write '{test,contracts,build}/**/*.{ts,js,json}'",
"test": "mocha --exit test/**/*.spec.ts",
"build": "ts-node ./build/build.ts", "build": "ts-node ./build/build.ts",
"test": "mocha --exit test/**/*.spec.ts" "deploy": "ts-node ./build/deploy.ts"
}, },
"devDependencies": { "devDependencies": {
"@types/bn.js": "^5.1.0", "@types/bn.js": "^5.1.0",
@ -21,6 +22,7 @@
"prettier": "^2.6.2", "prettier": "^2.6.2",
"ton": "^9.6.3", "ton": "^9.6.3",
"ton-contract-executor": "^0.4.8", "ton-contract-executor": "^0.4.8",
"ton-crypto": "^3.1.0",
"ts-node": "^10.4.0", "ts-node": "^10.4.0",
"typescript": "^4.5.4" "typescript": "^4.5.4"
}, },

Loading…
Cancel
Save