diff --git a/README.md b/README.md index 509e39c1..99c56fbc 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ npm install -g @aeternity/aeproject - [Unit Testing](docs/cli/test.md) - [AEproject Library](docs/lib.md) - [Migration from 3.x.x to 4.x.x](docs/migration-from-3.x.x-to-4.x.x.md) +- [Upcoming Hard-Fork / Ceres Support](docs/ceres-support.md) ## Release Process diff --git a/docs/ceres-support.md b/docs/ceres-support.md new file mode 100644 index 00000000..c223a350 --- /dev/null +++ b/docs/ceres-support.md @@ -0,0 +1,21 @@ +# Upcoming Hard-Fork / Ceres Support + +Aeproject can already be used for testing and setup with the upcoming Ceres hard-fork. + +### Automatic update + +Use `aeproject init --next ` to initialize a new project that has the necessary adjustments for ceres applied. + +Use `aeproject init --update --next` to update an existing project with the adjustments for ceres. For updating existing tests implemented change occurrences of `utils.getSdk()` to `utils.getSdk({ ignoreVersion: true })` or use the same option for manually initialized sdk `Node` and `CompilerHttp`. + +### Manual update + + - change occurrences of `utils.getSdk()` to `utils.getSdk({ ignoreVersion: true })` or use the same option for manually initialized sdk `Node` and `CompilerHttp` + - update `docker/aeternity.yml` to include +```yaml +chain: + hard_forks: + "1": 0 + "6": 1 +``` + - update `docker-compose.yml` to use the `latest` node and compiler tags or specify it manually in running with `aeproject env --nodeVersion latest --compilerVersion latest` \ No newline at end of file diff --git a/docs/cli/init.md b/docs/cli/init.md index 33254ce7..5e70bedd 100644 --- a/docs/cli/init.md +++ b/docs/cli/init.md @@ -17,3 +17,7 @@ aeproject init --update ``` Updates the project structure and needed artifacts to the latest version, as well as installing needed dependencies. + +## Upcoming Hard-fork initialization + +The additional parameter `--next` can be used to either initialize or update a project with changes for an upcoming hard-fork if available. \ No newline at end of file diff --git a/package.json b/package.json index 23714dee..8fdf18ca 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "lint:ci": "eslint --ext .js --max-warnings=0 .", "test": "mocha ./tests/**/*.js --timeout 0 --exit", "prepack": "npm run build:clean && npm run build", - "copy-files": "cp -r ./src/init/artifacts ./.build/cjs/src/init/ && cp -r ./src/init/artifacts ./.build/esm/src/init/ && cp -r ./src/init/update-artifacts ./.build/cjs/src/init/ && cp -r ./src/init/update-artifacts ./.build/esm/src/init/", + "copy-files": "cp -r ./src/init/artifacts ./.build/cjs/src/init/ && cp -r ./src/init/artifacts ./.build/esm/src/init/ && cp -r ./src/init/update-artifacts ./.build/cjs/src/init/ && cp -r ./src/init/update-artifacts ./.build/esm/src/init/ && cp -r ./src/init/next-artifacts ./.build/cjs/src/init/ && cp -r ./src/init/next-artifacts ./.build/esm/src/init/", "link:local": "npm uninstall -g @aeternity/aeproject && npm run build:clean && npm run build && npm link" }, "license": "ISC", diff --git a/src/cli/commands.js b/src/cli/commands.js index ae98305b..23c805f0 100644 --- a/src/cli/commands.js +++ b/src/cli/commands.js @@ -13,8 +13,13 @@ const addInitOption = (program) => { constants.artifactsDest, ) .option("--update", "update project files") + .option( + "--next", + "apply patches to initialise or update for use with the upcoming release", + ) + .option("-y", "overwrite all files in update process") .action(async (folder, option) => { - await init.run(folder, option.update); + await init.run(folder, option.update, option.next, option.y); }); }; diff --git a/src/init/constants.json b/src/init/constants.json index 27101f32..a2768b82 100644 --- a/src/init/constants.json +++ b/src/init/constants.json @@ -2,6 +2,7 @@ "artifactsDest": "./", "artifactsDir": "/artifacts", "updateArtifactsDir": "/update-artifacts", + "nextArtifactsDir": "/next-artifacts", "dependencies": ["@aeternity/aepp-sdk@13"], "devDependencies": ["chai@4", "chai-as-promised@7", "mocha@10"], "uninstallDependencies": ["aeproject", "aeproject-lib"], diff --git a/src/init/init.js b/src/init/init.js index e9214d5b..f14c8769 100644 --- a/src/init/init.js +++ b/src/init/init.js @@ -12,17 +12,30 @@ const { deleteWithPrompt, } = require("../utils/fs-utils"); -async function run(folder, update) { +async function run(folder, update, next, y) { checkNodeVersion(); createFolder(folder); if (update) { - await updateAEprojectProjectLibraries(folder, update); + await updateAEprojectProjectLibraries(folder, update, y); } else { await createAEprojectProjectStructure(folder); } + + // currently implements ceres patches, might be different in the future when ceres is the default + if (next) await patchForNextRelease(folder, y); } +const patchForNextRelease = async (folder, y) => { + print( + "===== updating project file and directory structure for next version =====", + ); + + const fileSource = `${__dirname}${constants.nextArtifactsDir}`; + + await copyFolderRecursiveSync(fileSource, folder, y); +}; + const checkNodeVersion = () => { if (parseInt(process.version.split(".")[0].replace("v", ""), 10) < 16) { print("You need to use Node.js 16 or newer to use aeproject."); @@ -49,10 +62,10 @@ const createAEprojectProjectStructure = async (folder) => { ); }; -const updateAEprojectProjectLibraries = async (folder, update) => { +const updateAEprojectProjectLibraries = async (folder, update, y) => { print("===== updating aeproject ====="); - await updateArtifacts(folder); + await updateArtifacts(folder, y); await installDependencies(folder, update); print("===== aeproject sucessfully initalized ====="); @@ -107,17 +120,17 @@ const setupArtifacts = async (folder) => { ); }; -const updateArtifacts = async (folder) => { +const updateArtifacts = async (folder, y) => { print("===== updating project file and directory structure ====="); const fileSource = `${__dirname}${constants.updateArtifactsDir}`; await constants.deleteArtifacts.reduce(async (promiseAcc, artifact) => { await promiseAcc; - await deleteWithPrompt(artifact); + await deleteWithPrompt(artifact, y); }, Promise.resolve()); - await copyFolderRecursiveSync(fileSource, folder); + await copyFolderRecursiveSync(fileSource, folder, y); }; module.exports = { diff --git a/src/init/next-artifacts/docker-compose.yml b/src/init/next-artifacts/docker-compose.yml new file mode 100644 index 00000000..58e20d5a --- /dev/null +++ b/src/init/next-artifacts/docker-compose.yml @@ -0,0 +1,28 @@ +version: "3.6" +services: + aeproject_node: + image: aeternity/aeternity:${NODE_TAG:-latest}-bundle + hostname: node + environment: + AETERNITY_CONFIG: /home/aeternity/aeternity.yaml + AE__SYSTEM__CUSTOM_PREFUNDED_ACCS_FILE: "/home/aeternity/node/data/aecore/.genesis/accounts_test.json" + volumes: + - "./docker/aeternity.yaml:/home/aeternity/aeternity.yaml" + - "./docker/accounts.json:/home/aeternity/node/data/aecore/.genesis/accounts_test.json" + + aeproject_compiler: + image: aeternity/aesophia_http:${COMPILER_TAG:-latest} + hostname: compiler + ports: + - "3080:3080" + + aeproject_proxy: + image: nginx:latest + hostname: proxy + ports: + - "3001:3001" + volumes: + - "./docker/nginx.conf:/etc/nginx/conf.d/default.conf" + depends_on: + - aeproject_compiler + - aeproject_node diff --git a/src/init/next-artifacts/docker/aeternity.yaml b/src/init/next-artifacts/docker/aeternity.yaml new file mode 100644 index 00000000..90b29d86 --- /dev/null +++ b/src/init/next-artifacts/docker/aeternity.yaml @@ -0,0 +1,50 @@ +http: + external: + gas_limit: 60000000 + internal: + debug_endpoints: true + listen_address: 0.0.0.0 + endpoints: + dry-run: true + +system: + plugin_path: /home/aeternity/node/plugins + plugins: + - name: aeplugin_dev_mode + config: # keeping the old config style at first to stay backwards compatible + keyblock_interval: 0 + microblock_interval: 0 + auto_emit_microblocks: true + +dev_mode: + keyblock_interval: 0 + microblock_interval: 0 + auto_emit_microblocks: true + +fork_management: + network_id: ae_dev + +chain: + hard_forks: + "1": 0 + "6": 1 + persist: true + consensus: + "0": + name: "on_demand" # keeping the old config style at first to stay backwards compatible + type: "on_demand" + +mining: + beneficiary: "ak_RdoCvwe7kxPu2VBv2gQAc1V81sGyTTuxFv36AcvNQYZN7qgut" + beneficiary_reward_delay: 2 + strictly_follow_top: true + +websocket: + channel: + port: 3014 + listen_address: 0.0.0.0 + +logging: + # Controls the overload protection in the logs. + hwm: 50 + level: debug diff --git a/src/init/next-artifacts/test/exampleTest.js b/src/init/next-artifacts/test/exampleTest.js new file mode 100644 index 00000000..d10dce21 --- /dev/null +++ b/src/init/next-artifacts/test/exampleTest.js @@ -0,0 +1,54 @@ +const { assert } = require("chai"); +const { utils } = require("@aeternity/aeproject"); +const chaiAsPromised = require("chai-as-promised"); +const chai = require("chai"); + +chai.use(chaiAsPromised); + +const EXAMPLE_CONTRACT_SOURCE = "./contracts/ExampleContract.aes"; + +describe("ExampleContract", () => { + let aeSdk; + let contract; + + before(async () => { + aeSdk = utils.getSdk({ ignoreVersion: true }); + + // a filesystem object must be passed to the compiler if the contract uses custom includes + const fileSystem = utils.getFilesystem(EXAMPLE_CONTRACT_SOURCE); + + // get content of contract + const sourceCode = utils.getContractContent(EXAMPLE_CONTRACT_SOURCE); + + // initialize the contract instance + contract = await aeSdk.initializeContract({ sourceCode, fileSystem }); + await contract.init(); + + // create a snapshot of the blockchain state + await utils.createSnapshot(aeSdk); + }); + + // after each test roll back to initial state + afterEach(async () => { + await utils.rollbackSnapshot(aeSdk); + }); + + it("ExampleContract: set and get", async () => { + const set = await contract.set(42, { + onAccount: utils.getDefaultAccounts()[1], + }); + assert.equal(set.decodedEvents[0].name, "SetXEvent"); + assert.equal( + set.decodedEvents[0].args[0], + utils.getDefaultAccounts()[1].address, + ); + assert.equal(set.decodedEvents[0].args[1], 42); + + const { decodedResult } = await contract.get(); + assert.equal(decodedResult, 42); + }); + + it("ExampleContract: get undefined when not set before", async () => { + await assert.isRejected(contract.get(), "NOTHING_SET_YET"); + }); +}); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 6a5bbc58..2712e682 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -67,13 +67,13 @@ export function getDefaultAccounts(): MemoryAccount[] { return wallets.map((keypair) => new MemoryAccount(keypair.secretKey)); } -export function getSdk(): AeSdk { - const instance = new Node(networks.devmode.nodeUrl, { ignoreVersion: true }); +export function getSdk(options: {}): AeSdk { + const instance = new Node(networks.devmode.nodeUrl, options); return new AeSdk({ accounts: getDefaultAccounts(), nodes: [{ name: "node", instance }], - onCompiler: new CompilerHttp(networks.devmode.compilerUrl), + onCompiler: new CompilerHttp(networks.devmode.compilerUrl, options), interval: 50, }); } diff --git a/src/utils/fs-utils.js b/src/utils/fs-utils.js index 4eda436e..29ed5a82 100644 --- a/src/utils/fs-utils.js +++ b/src/utils/fs-utils.js @@ -13,7 +13,7 @@ async function prompt(action, target) { return input === "YES" || input === "yes" || input === "Y" || input === "y"; } -async function copyFolderRecursiveSync(srcDir, dstDir) { +async function copyFolderRecursiveSync(srcDir, dstDir, y = false) { let src; let dst; @@ -28,18 +28,18 @@ async function copyFolderRecursiveSync(srcDir, dstDir) { fs.mkdirSync(dst); } - await copyFolderRecursiveSync(src, dst); + await copyFolderRecursiveSync(src, dst, y); } else if (!fs.existsSync(dst)) { fs.writeFileSync(dst, fs.readFileSync(src)); - } else if (await prompt("overwrite", dst)) { + } else if (y || (await prompt("overwrite", dst))) { fs.writeFileSync(dst, fs.readFileSync(src)); } }, Promise.resolve()); } -async function deleteWithPrompt(target) { +async function deleteWithPrompt(target, y = false) { if (fs.existsSync(target)) { - if (await prompt("delete", target)) { + if (y || (await prompt("delete", target))) { fs.rmSync(target, { recursive: true }); } } diff --git a/tests/cli.js b/tests/cli.js index 489686a5..231af75b 100644 --- a/tests/cli.js +++ b/tests/cli.js @@ -92,6 +92,41 @@ describe("command line usage", () => { assert.include(res.stdout, "2 passing"); }); + it("init --update --next", async () => { + const res = await exec("aeproject init --update --next -y", { cwd }); + assert.equal(res.code, 0); + assert.equal(res.stderr, ""); + assert.include( + res.stdout, + "===== updating project file and directory structure =====", + ); + assert.include( + res.stdout, + "===== updating project file and directory structure for next version =====", + ); + + assert.include(file(path.join(cwd, "docker/aeternity.yaml")), "hard_forks"); + assert.include( + file(path.join(cwd, "test/exampleTest.js")), + "ignoreVersion: true", + ); + assert.include( + file(path.join(cwd, "docker-compose.yml")), + "COMPILER_TAG:-latest", + ); + assert.include( + file(path.join(cwd, "docker-compose.yml")), + "NODE_TAG:-latest", + ); + + const resEnv = await exec("aeproject env", { cwd }); + assert.equal(resEnv.code, 0); + assert.isTrue(await isEnvRunning(cwd)); + + assert.include(resEnv.stdout, "aeternity/aeternity latest-bundle"); + assert.include(resEnv.stdout, "aeternity/aesophia_http latest"); + }); + it("env --stop", async () => { const res = await exec("aeproject env --stop", { cwd }); assert.equal(res.code, 0); diff --git a/tests/util.js b/tests/util.js index 4d6870f1..1c990b7e 100644 --- a/tests/util.js +++ b/tests/util.js @@ -13,12 +13,13 @@ async function exec(cmd, options) { } async function prepareLocal() { + cleanLocal(); await exec("npm run link:local"); if (!fs.existsSync(cwd)) fs.mkdirSync(cwd); } function cleanLocal() { - fs.rmSync(cwd, { recursive: true }); + if (fs.existsSync(cwd)) fs.rmSync(cwd, { recursive: true }); } async function linkLocalLib(folder) {