diff --git a/.github/workflows/protocol-devchain-anvil.yml b/.github/workflows/protocol-devchain-anvil.yml index 22e3fb5e967..3fa4e8f4d30 100644 --- a/.github/workflows/protocol-devchain-anvil.yml +++ b/.github/workflows/protocol-devchain-anvil.yml @@ -4,10 +4,24 @@ on: branches: - master - 'release/**' + tags: + - core-contracts.v* + pull_request: + branches: [release/core-contracts/*, master] + paths: + - 'packages/protocol/**' + workflow_dispatch: + inputs: + npm_tag: + description: 'NPM TAG e.g. alpha, pre-merge (default: canary) ' + required: true + type: string env: # Increment these to force cache rebuilding FOUNDRY_CACHE_KEY: 1 + # Supported Foundry version defined at celo-org (GitHub organisation) level, for consistency across workflows. + SUPPORTED_FOUNDRY_VERSION: ${{ vars.SUPPORTED_FOUNDRY_VERSION }} ANVIL_PORT: 8546 jobs: @@ -16,8 +30,8 @@ jobs: run: working-directory: packages/protocol name: Generate anvil - runs-on: ["self-hosted", "org", "npm-publish"] - permissions: + runs-on: ['self-hosted', 'org', 'npm-publish'] + permissions: contents: read pull-requests: read id-token: write @@ -54,14 +68,14 @@ jobs: uses: actions/github-script@v7 with: script: | - const result = ( - await github.rest.repos.listPullRequestsAssociatedWithCommit({ - commit_sha: context.sha, - owner: context.repo.owner, - repo: context.repo.repo, - }) - ).data[0]; - core.setOutput("number", result ? result.number : ""); + const result = ( + await github.rest.repos.listPullRequestsAssociatedWithCommit({ + commit_sha: context.sha, + owner: context.repo.owner, + repo: context.repo.repo, + }) + ).data[0]; + core.setOutput("number", result ? result.number : ""); - name: Set PR Number id: set_pr_number @@ -79,7 +93,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: "nightly-f625d0fa7c51e65b4bf1e8f7931cd1c6e2e285e9" + version: ${{ env.SUPPORTED_FOUNDRY_VERSION }} - name: Install forge dependencies run: forge install @@ -88,10 +102,33 @@ jobs: id: date run: echo "::set-output name=date::$(date +'%Y-%m-%d')" + - name: Akeyless Get Secrets + id: get_auth_token + uses: docker://us-west1-docker.pkg.dev/devopsre/akeyless-public/akeyless-action:latest + with: + api-url: https://api.gateway.akeyless.celo-networks-dev.org + access-id: p-kf9vjzruht6l + static-secrets: '{"/static-secrets/NPM/npm-publish-token":"NPM_TOKEN"}' + + - uses: actions/setup-node@v4 + with: + node-version: '18.x' + registry-url: 'https://registry.npmjs.org' + + - name: 'Setup yarn' + shell: bash + run: | + npm install --global yarn + source ~/.bashrc + + - name: 'Install packages' + shell: bash + run: yarn + - name: Generate migrations and run devchain if: success() || failure() run: ./scripts/foundry/create_and_migrate_anvil_devchain.sh - + - name: Run integration tests against local anvil devchain if: success() || failure() run: | @@ -105,12 +142,23 @@ jobs: sanitized_ref_name=$(echo "${{ github.ref_name }}" | tr -cd '[:alnum:]-_') echo "sanitized_ref_name=${sanitized_ref_name}" >> $GITHUB_ENV - - name: Set package.json version based on GitHub ref - run: | - VERSION=${{ env.PR_NUMBER }} - echo "Setting version to 0.0.$VERSION" - jq ".version = \"0.0.$VERSION\"" .tmp/package.json > .tmp/temp.json && mv .tmp/temp.json .tmp/package.json + - name: + Determine release type and version (or dry run) + # This is what sets the RELEASE_TYPE and RELEASE_VERSION env variables + run: yarn --silent determine-release-version >> "$GITHUB_ENV" + working-directory: packages/protocol + env: + GITHUB_TAG: ${{ github.ref_name }} + NPM_PACKAGE: '@celo/devchain-anvil' + NPM_TAG: ${{ inputs.npm_tag }} + - name: Prepare package for publishing + run: yarn prepare_devchain_anvil_publishing + working-directory: packages/protocol + env: + RELEASE_TYPE: ${{ env.RELEASE_TYPE }} + RELEASE_VERSION: ${{ env.RELEASE_VERSION }} + - name: Upload devchain as artifact uses: actions/upload-artifact@v4 with: @@ -120,23 +168,13 @@ jobs: # https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#artifact-and-log-retention-policy retention-days: 90 - - name: Akeyless Get Secrets - id: get_auth_token - uses: docker://us-west1-docker.pkg.dev/devopsre/akeyless-public/akeyless-action:latest - with: - api-url: https://api.gateway.akeyless.celo-networks-dev.org - access-id: p-kf9vjzruht6l - static-secrets: '{"/static-secrets/NPM/npm-publish-token":"NPM_TOKEN"}' - - - uses: actions/setup-node@v4 - with: - node-version: '18.x' - registry-url: 'https://registry.npmjs.org' - - name: Publish @celo/devchain-anvil run: | cat package.json - npm publish --access public + npm publish $RELEASE_TYPE $DRY_RUN --access public working-directory: packages/protocol/.tmp env: + RELEASE_TYPE: --tag ${{ env.RELEASE_TYPE != '' && env.RELEASE_TYPE || 'canary' }} + RELEASE_VERSION: ${{ env.RELEASE_VERSION }} NODE_AUTH_TOKEN: ${{ env.NPM_TOKEN }} + DRY_RUN: ${{ env.RELEASE_VERSION == '' && '--dry-run' || '' }} diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 95a534c0e53..1814cbe512a 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -27,6 +27,7 @@ "prebuild": "rm -rf ./build", "determine-release-version": "yarn --silent ts-node --preferTsExts ./scripts/determine-release-version.ts", "prepare_contracts_and_abis_publishing": "yarn ts-node --preferTsExts ./scripts/prepare-contracts-and-abis-publishing.ts", + "prepare_devchain_anvil_publishing": "yarn ts-node --preferTsExts ./scripts/change-anvil-devchain-package-version.ts", "validate_abis_exports": "yarn ts-node --preferTsExts ./scripts/validate-abis-package.ts", "sourcify-publish": "yarn ts-node ./scripts/sourcify-publish.ts", "migrate": "./scripts/bash/migrate.sh", diff --git a/packages/protocol/scripts/change-anvil-devchain-package-version.ts b/packages/protocol/scripts/change-anvil-devchain-package-version.ts new file mode 100644 index 00000000000..7937150f7a2 --- /dev/null +++ b/packages/protocol/scripts/change-anvil-devchain-package-version.ts @@ -0,0 +1,35 @@ +import { replacePackageVersionAndMakePublic } from '@celo/protocol/scripts/utils' +import * as child_process from 'child_process' +import * as fs from 'fs' +import * as path from 'path' +import { DEVCHAIN_ANVIL_PACKAGE_SRC_DIR, TSCONFIG_PATH } from './consts' + +function log(...args: any[]) { + // eslint-disable-next-line + console.info('[prepare-devchain-anvil]', ...args) +} + +try { + log('Setting package.json target to ES2020') + const tsconfig = JSON.parse(fs.readFileSync(TSCONFIG_PATH, 'utf8')) + tsconfig.compilerOptions.target = 'ES2020' + fs.writeFileSync(TSCONFIG_PATH, JSON.stringify(tsconfig, null, 4)) + + prepareAnvilDevchainPackage() +} finally { + log('Cleaning up') + child_process.execSync(`git checkout ${TSCONFIG_PATH}`, { stdio: 'inherit' }) +} + +function prepareAnvilDevchainPackage() { + if (process.env.RELEASE_VERSION) { + log('Replacing @celo/devchain-anvil version with RELEASE_VERSION)') + + const packageJsonPath = path.join(DEVCHAIN_ANVIL_PACKAGE_SRC_DIR, 'package.json') + replacePackageVersionAndMakePublic(packageJsonPath) + + return + } + + log('Skipping @celo/devchain-anvil package.json preparation (no RELEASE_VERSION provided)') +} diff --git a/packages/protocol/scripts/consts.ts b/packages/protocol/scripts/consts.ts index 2aed53633ad..d599e87950e 100644 --- a/packages/protocol/scripts/consts.ts +++ b/packages/protocol/scripts/consts.ts @@ -8,6 +8,7 @@ export const CONTRACTS_08_PACKAGE_DESTINATION_DIR = path.join(CONTRACTS_PACKAGE_ export const ABIS_PACKAGE_SRC_DIR = path.join(__dirname, '../abis') export const ABIS_BUILD_DIR = path.join(ABIS_PACKAGE_SRC_DIR, 'src-generated') export const ABIS_DIST_DIR = path.join(ABIS_PACKAGE_SRC_DIR, 'dist') +export const DEVCHAIN_ANVIL_PACKAGE_SRC_DIR = path.join(__dirname, '../.tmp') export const BUILD_EXECUTABLE = path.join(__dirname, 'build.ts') export const TSCONFIG_PATH = path.join(ROOT_DIR, 'tsconfig.json') @@ -35,7 +36,7 @@ export const ProxyContracts = [ 'RegistryProxy', 'SortedOraclesProxy', 'UniswapFeeHandlerSellerProxy', - 'MintGoldScheduleProxy', + 'CeloDistributionScheduleProxy', ] export const CoreContracts = [ @@ -51,7 +52,7 @@ export const CoreContracts = [ 'MultiSig', 'Registry', 'Freezer', - 'MintGoldSchedule', + 'CeloDistributionSchedule', // governance 'Election', diff --git a/packages/protocol/scripts/determine-release-version.ts b/packages/protocol/scripts/determine-release-version.ts index a85f036a38f..08cfa53df44 100644 --- a/packages/protocol/scripts/determine-release-version.ts +++ b/packages/protocol/scripts/determine-release-version.ts @@ -1,6 +1,7 @@ import { execSync } from 'child_process' import { determineNextVersion, getReleaseTypeFromSemVer } from './utils' +const npmPackage = process.env.NPM_PACKAGE?.trim() || '' const npmTag = process.env.NPM_TAG?.trim() || '' const gitTag = process.env.GITHUB_TAG || '' const branchName = execSync('git branch --show-current').toString().trim() @@ -12,7 +13,7 @@ const branchName = execSync('git branch --show-current').toString().trim() // if not on a release branch a dry-run will be done unless an NPM_TAG is provided // in which case we will try to fetch the last published version with that tag and bump or use the canary to get major and start versioning from there the new tag at 0 // (e.g. `@celo/contracts@11.0.0@custom-tag.0`) -const nextVersion = determineNextVersion(gitTag, branchName, npmTag) +const nextVersion = determineNextVersion(gitTag, branchName, npmPackage, npmTag) if (nextVersion === null) { // dry-run will build the package but not publish it diff --git a/packages/protocol/scripts/prepare-contracts-and-abis-publishing.ts b/packages/protocol/scripts/prepare-contracts-and-abis-publishing.ts index 7a58e596dc3..25ba22335eb 100644 --- a/packages/protocol/scripts/prepare-contracts-and-abis-publishing.ts +++ b/packages/protocol/scripts/prepare-contracts-and-abis-publishing.ts @@ -1,3 +1,4 @@ +import { Exports, replacePackageVersionAndMakePublic } from '@celo/protocol/scripts/utils' import * as child_process from 'child_process' import * as fs from 'fs' import * as path from 'path' @@ -116,11 +117,6 @@ function createIndex() { fs.writeFileSync(path.join(ABIS_BUILD_DIR, 'index.ts'), reExports.join('\n')) } -type Exports = Record< - string, - { import?: string; require?: string; types?: string; default?: string } -> - // Helper functions function prepareTargetTypesExports() { const exports: Exports = {} @@ -238,38 +234,22 @@ function processRawJsonsAndPrepareExports() { return exports } -type JSON = Record - -function replacePackageVersionAndMakePublic( - packageJsonPath: string, - onDone?: (json: JSON) => void -) { - const json: JSON = JSON.parse(fs.readFileSync(packageJsonPath).toString()) +function prepareAbisPackageJson(exports: Exports) { + log('Preparing @celo/abis package.json') + const packageJsonPath = path.join(ABIS_PACKAGE_SRC_DIR, 'package.json') if (process.env.RELEASE_VERSION) { - log(`Replacing ${json.name as string} version with provided RELEASE_VERSION`) + log('Replacing @celo/abis version with RELEASE_VERSION)') - json.version = process.env.RELEASE_VERSION - json.private = false - } else { - log('No RELEASE_VERSION provided') - } + replacePackageVersionAndMakePublic(packageJsonPath, (json) => { + log('Setting @celo/abis exports') + json.exports = exports + }) - if (onDone !== undefined) { - onDone(json) + return } - fs.writeFileSync(packageJsonPath, JSON.stringify(json, null, 2)) -} - -function prepareAbisPackageJson(exports: Exports) { - log('Preparing @celo/abis package.json') - const packageJsonPath = path.join(ABIS_PACKAGE_SRC_DIR, 'package.json') - - replacePackageVersionAndMakePublic(packageJsonPath, (json) => { - log('Setting @celo/abis exports') - json.exports = exports - }) + log('Skipping @celo/abis package.json preparation (no RELEASE_VERSION provided)') } function prepareContractsPackage() { diff --git a/packages/protocol/scripts/utils.test.ts b/packages/protocol/scripts/utils.test.ts index d360cb5d172..f438cc927f7 100644 --- a/packages/protocol/scripts/utils.test.ts +++ b/packages/protocol/scripts/utils.test.ts @@ -23,6 +23,7 @@ describe('utils', () => { const nextVersion = determineNextVersion( 'core-contracts.v11.2.3.post-audit', 'release/core-contracts/11.0.0', + '@celo/contracts', 'alpha' ) @@ -33,6 +34,7 @@ describe('utils', () => { const nextVersion = determineNextVersion( 'core-contracts.v11.2.3.pre-audit', 'release/core-contracts/11.0.0', + '@celo/contracts', 'alpha' ) @@ -42,7 +44,12 @@ describe('utils', () => { it('determines for release git branch when major version matches', () => { execSyncMock.mockReturnValue('11.2.3') - const nextVersion = determineNextVersion('', 'release/core-contracts/11.2.3', 'alpha') + const nextVersion = determineNextVersion( + '', + 'release/core-contracts/11.2.3', + '@celo/contracts', + 'alpha' + ) expect(execSyncMock).toHaveBeenCalledTimes(1) expect(execSyncMock).toHaveBeenNthCalledWith( @@ -56,7 +63,12 @@ describe('utils', () => { it("determines for release git branch when major version doesn't match", () => { execSyncMock.mockReturnValue('11.2.3') - const nextVersion = determineNextVersion('', 'release/core-contracts/12.0.0', 'alpha') + const nextVersion = determineNextVersion( + '', + 'release/core-contracts/12.0.0', + '@celo/contracts', + 'alpha' + ) expect(execSyncMock).toHaveBeenCalledTimes(1) expect(execSyncMock).toHaveBeenNthCalledWith( @@ -72,7 +84,12 @@ describe('utils', () => { return '10.2.4-alpha.0' }) - const nextVersion = determineNextVersion('', 'dev/some-branch-name', 'alpha') + const nextVersion = determineNextVersion( + '', + 'dev/some-branch-name', + '@celo/contracts', + 'alpha' + ) expect(execSyncMock).toHaveBeenCalledTimes(1) expect(execSyncMock).toHaveBeenNthCalledWith( @@ -92,7 +109,12 @@ describe('utils', () => { return '10.1.2-canary.2' }) - const nextVersion = determineNextVersion('', 'dev/some-branch-name', 'alpha') + const nextVersion = determineNextVersion( + '', + 'dev/some-branch-name', + '@celo/contracts', + 'alpha' + ) expect(execSyncMock).toHaveBeenCalledTimes(2) expect(execSyncMock).toHaveBeenNthCalledWith( @@ -110,13 +132,18 @@ describe('utils', () => { }) it("doesn't determine anything when wrong tag is provided", () => { - const nextVersion = determineNextVersion('', '', 'tag-with-dashes-at-the-end-') + const nextVersion = determineNextVersion( + '', + '', + '@celo/contracts', + 'tag-with-dashes-at-the-end-' + ) expect(nextVersion).toBeNull() }) it("doesn't determine anything when nothing is provided", () => { - const nextVersion = determineNextVersion('', '', '') + const nextVersion = determineNextVersion('', '', '', '') expect(nextVersion).toBeNull() }) diff --git a/packages/protocol/scripts/utils.ts b/packages/protocol/scripts/utils.ts index 5ddcce2e53b..30f44698deb 100644 --- a/packages/protocol/scripts/utils.ts +++ b/packages/protocol/scripts/utils.ts @@ -1,12 +1,21 @@ import { execSync } from 'child_process' +import * as fs from 'fs' import { SemVer } from 'semver' const DAILY_RELEASE_TAG = 'canary' const WORKING_RELEASE_BRANCH_PREFIX = 'release/core-contracts/' +export type Exports = Record< + string, + { import?: string; require?: string; types?: string; default?: string } +> + +export type JSON = Record + export const determineNextVersion = ( gitTag: string, gitBranch: string, + npmPackage: string, npmTag: string ): SemVer | null => { let nextVersion: SemVer | null = null @@ -21,7 +30,7 @@ export const determineNextVersion = ( `${tempVersion.major}.${tempVersion.minor}.${tempVersion.patch}-pre-audit.0` ) } else if (gitBranch.startsWith(WORKING_RELEASE_BRANCH_PREFIX)) { - const lastVersion = getPreviousVersion(DAILY_RELEASE_TAG, 'latest') + const lastVersion = getPreviousVersion(npmPackage, DAILY_RELEASE_TAG, 'latest') const lastVersionSemVer = new SemVer(lastVersion) // since branch names are of the form release/core-contracts.XX we can check the major from the branch name @@ -34,7 +43,7 @@ export const determineNextVersion = ( ) nextVersion.major = parseInt(major, 10) } else if (isValidNpmTag(npmTag)) { - const lastVersion = getPreviousVersion(npmTag, DAILY_RELEASE_TAG) + const lastVersion = getPreviousVersion(npmPackage, npmTag, DAILY_RELEASE_TAG) nextVersion = new SemVer(lastVersion).inc('prerelease', npmTag) } @@ -46,16 +55,20 @@ export function isValidNpmTag(tag?: string) { } // get the previous version for this tag or if not exists find the previous for the fallback -export function getPreviousVersion(tag = DAILY_RELEASE_TAG, fallbackTag = 'latest') { +export function getPreviousVersion( + npmPackage: string, + tag = DAILY_RELEASE_TAG, + fallbackTag = 'latest' +) { try { - return fetchVersionFromNpm(tag) + return fetchVersionFromNpm(npmPackage, tag) } catch (e) { - return fetchVersionFromNpm(fallbackTag) + return fetchVersionFromNpm(npmPackage, fallbackTag) } } -export function fetchVersionFromNpm(tag: string) { - return execSync(`npm view @celo/contracts@${tag} version`, { +export function fetchVersionFromNpm(npmPackage: string, tag: string) { + return execSync(`npm view ${npmPackage}@${tag} version`, { stdio: ['ignore', 'pipe', 'ignore'], }) .toString() @@ -73,3 +86,25 @@ export function getVersionFromGitTag(matchedTag: RegExpMatchArray) { export function getReleaseTypeFromSemVer(version: SemVer): string | number { return version.prerelease.length ? version.prerelease[0] : 'latest' } + +export function replacePackageVersionAndMakePublic( + packageJsonPath: string, + onDone?: (json: JSON) => void +) { + const json: JSON = JSON.parse(fs.readFileSync(packageJsonPath).toString()) + + if (process.env.RELEASE_VERSION) { + console.info(`Replacing ${json.name as string} version with provided RELEASE_VERSION`) + + json.version = process.env.RELEASE_VERSION + json.private = false + } else { + console.info('No RELEASE_VERSION provided') + } + + if (onDone !== undefined) { + onDone(json) + } + + fs.writeFileSync(packageJsonPath, JSON.stringify(json, null, 2)) +}