diff --git a/.commitsar.yml b/.commitsar.yml new file mode 100644 index 0000000..cf1a273 --- /dev/null +++ b/.commitsar.yml @@ -0,0 +1,2 @@ +commits: + upstreamBranch: origin/main diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9989d74 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,35 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 120 +trim_trailing_whitespace = true + +[*.md] +# double whitespace at end of line +# denotes a line break in Markdown +trim_trailing_whitespace = false +indent_size = unset +max_line_length = 150 + +[{*.yml,*.yaml}] +indent_size = 2 +max_line_length = 150 + +# Ignore paths +[{.git/**/*,**/*.lock,**/Move.toml}] +charset = unset +end_of_line = unset +indent_size = unset +indent_style = unset +insert_final_newline = unset +max_line_length = unset +trim_trailing_whitespace = unset diff --git a/.github/actions/diffs/action.yml b/.github/actions/diffs/action.yml new file mode 100644 index 0000000..5f366ee --- /dev/null +++ b/.github/actions/diffs/action.yml @@ -0,0 +1,27 @@ +name: Detect Changes +description: Defines variables indicating the parts of the code that changed +outputs: + isMove: + description: True when changes happened to the Move code + value: "${{ steps.diff.outputs.isMove }}" + isRust: + description: True when changes happened to the Rust code + value: "${{ steps.diff.outputs.isRust }}" + +runs: + using: composite + steps: + - uses: actions/checkout@v3 + - name: Detect Changes + uses: dorny/paths-filter@v2.11.1 + id: diff + with: + filters: | + isMove: + - 'contracts/**' + - '.github/workflows/rust.yml' + isRust: + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/rust.yml' diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..6976fb3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,30 @@ +# Documentation for all configuration options: +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + day: "sunday" + commit-message: + prefix: "fix" + prefix-development: "chore" + include: "scope" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-patch"] + open-pull-requests-limit: 20 + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "sunday" + commit-message: + prefix: "chore" + include: "scope" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-patch"] diff --git a/.github/workflows/combine-prs.yml b/.github/workflows/combine-prs.yml new file mode 100644 index 0000000..68e7805 --- /dev/null +++ b/.github/workflows/combine-prs.yml @@ -0,0 +1,172 @@ +# Adapted from https://github.com/hrvey/combine-prs-workflow +# SPDX-FileCopyrightText: 2020 Hrvey +# SPDX-License-Identifier: MIT + +name: 'Combine PRs' + +on: + # Combine PRs every Monday morning (after PRs were created on Sunday). + schedule: + - cron: '14 3 * * 1' + # Manual job + workflow_dispatch: + inputs: + branchPrefix: + description: 'Branch prefix to find combinable PRs based on' + required: true + default: 'dependabot' + mustBeGreen: + description: 'Only combine PRs that are green (status is success). Set to false if repo does not run checks' + type: boolean + required: true + default: true + combinedBranchName: + description: 'Name of the branch to combine PRs into' + required: true + default: 'deps/combined-prs' + combinedTitle: + description: 'Title of the combined PR' + required: true + default: 'fix(deps): update non-major dependencies' + ignoreLabel: + description: 'Exclude PRs with this label' + required: true + default: 'major' + combinedLabels: + description: 'Labels to assign (comma-separated list)' + required: true + default: 'dependencies' + +env: + defaultTitle: 'fix(deps): update non-major dependencies' + branchPrefix: ${{ inputs.branchPrefix || 'dependabot' }} + mustBeGreen: ${{ inputs.mustBeGreen || github.event_name == 'schedule' }} + combinedBranchName: ${{ inputs.combinedBranchName || 'deps/combined-prs' }} + ignoreLabel: ${{ inputs.ignoreLabel || 'major' }} + combinedLabels: ${{ inputs.combinedLabels || 'dependencies' }} + +jobs: + combine-prs: + runs-on: ubuntu-latest + + steps: + - uses: actions/github-script@v6 + id: create-combined-pr + name: Create Combined PR + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const pulls = await github.paginate('GET /repos/:owner/:repo/pulls', { + owner: context.repo.owner, + repo: context.repo.repo + }); + let branchesAndPRStrings = []; + let baseBranch = null; + let baseBranchSHA = null; + for (const pull of pulls) { + const branch = pull['head']['ref']; + console.log('Pull for branch: ' + branch); + if (branch.startsWith('${{ env.branchPrefix }}')) { + console.log('Branch matched prefix: ' + branch); + let statusOK = true; + if(${{ env.mustBeGreen }}) { + console.log('Checking green status: ' + branch); + const stateQuery = `query($owner: String!, $repo: String!, $pull_number: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number:$pull_number) { + commits(last: 1) { + nodes { + commit { + statusCheckRollup { + state + } + } + } + } + } + } + }` + const vars = { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pull['number'] + }; + const result = await github.graphql(stateQuery, vars); + const [{ commit }] = result.repository.pullRequest.commits.nodes; + const state = commit.statusCheckRollup.state + console.log('Validating status: ' + state); + if(state != 'SUCCESS') { + console.log('Discarding ' + branch + ' with status ' + state); + statusOK = false; + } + } + console.log('Checking labels: ' + branch); + const labels = pull['labels']; + for(const label of labels) { + const labelName = label['name']; + console.log('Checking label: ' + labelName); + if(labelName == '${{ env.ignoreLabel }}') { + console.log('Discarding ' + branch + ' with label ' + labelName); + statusOK = false; + } + } + if (statusOK) { + console.log('Adding branch to array: ' + branch); + const prString = '#' + pull['number'] + ' ' + pull['title']; + branchesAndPRStrings.push({ branch, prString }); + baseBranch = pull['base']['ref']; + baseBranchSHA = pull['base']['sha']; + } + } + } + if (branchesAndPRStrings.length == 0) { + core.setFailed('No PRs/branches matched criteria'); + return; + } + try { + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: 'refs/heads/' + '${{ env.combinedBranchName }}', + sha: baseBranchSHA + }); + } catch (error) { + console.log(error); + core.setFailed('Failed to create combined branch - maybe a branch by that name already exists?'); + return; + } + + let combinedPRs = []; + let mergeFailedPRs = []; + for(const { branch, prString } of branchesAndPRStrings) { + try { + await github.rest.repos.merge({ + owner: context.repo.owner, + repo: context.repo.repo, + base: '${{ env.combinedBranchName }}', + head: branch, + }); + console.log('Merged branch ' + branch); + combinedPRs.push(prString); + } catch (error) { + console.log('Failed to merge branch ' + branch); + mergeFailedPRs.push(prString); + } + } + + console.log('Creating combined PR'); + const combinedPRsString = combinedPRs.join('\n'); + let body = '✅ This PR was created by the Combine PRs action from the following PRs:\n' + combinedPRsString; + if(mergeFailedPRs.length > 0) { + const mergeFailedPRsString = mergeFailedPRs.join('\n'); + body += '\n\n⚠️ The following PRs were left out due to merge conflicts:\n' + mergeFailedPRsString + } + await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '${{ inputs.combinedTitle || env.defaultTitle }}', + head: '${{ env.combinedBranchName }}', + base: baseBranch, + body: body, + labels: '${{ env.combinedLabels }}'.split(",") + }); diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..18dd4a0 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,30 @@ +name: Lint + +on: [pull_request] + +jobs: + conventional_commit: + runs-on: ubuntu-latest + name: Check conventional commit messages + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Check commit messages + uses: aevea/commitsar@v0.20.1 + - name: Check PR title + uses: amannn/action-semantic-pull-request@v5.2.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + editorconfig: + runs-on: ubuntu-latest + name: Check editorconfig + steps: + - uses: actions/checkout@v3 + - uses: editorconfig-checker/action-editorconfig-checker@v1.0.0 + typos: + runs-on: ubuntu-latest + name: Check spelling + steps: + - uses: actions/checkout@v3 + - uses: crate-ci/typos@v1.14.9 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..a8ac2f1 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,139 @@ +name: Rust + +on: + pull_request: + # Run CI on the main branch after every merge. + # This is important to fill the GitHub Actions cache in a way that PRs can see it. + push: + branches: + - main + # Run CI on the main branch every Sunday. + schedule: + - cron: '14 3 * * 0' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + # Disable incremental compilation. + # + # Incremental compilation is useful as part of an edit-build-test-edit cycle, + # as it lets the compiler avoid recompiling code that hasn't changed. However, + # on CI, we're not making small edits; we're almost always building the entire + # project from scratch. Thus, incremental compilation on CI actually + # introduces *additional* overhead to support making future builds + # faster...but no future builds will ever occur in any given CI environment. + # + # See https://matklad.github.io/2021/09/04/fast-rust-builds.html#ci-workflow + # for details. + CARGO_INCREMENTAL: 0 + # Allow more retries for network requests in cargo (downloading crates) and + # rustup (installing toolchains). This should help to reduce flaky CI failures + # from transient network timeouts or other issues. + CARGO_NET_RETRY: 10 + RUSTUP_MAX_RETRIES: 10 + # Don't emit giant backtraces in the CI logs. + RUST_BACKTRACE: short + RUSTDOCFLAGS: -D warnings + SUI_TAG: sui-v1.1.1 + +jobs: + diff: + runs-on: [ubuntu-ghcloud] + outputs: + isRust: ${{ steps.diff.outputs.isRust }} + isMove: ${{ steps.diff.outputs.isMove }} + steps: + - uses: actions/checkout@v3 + - name: Detect Changes + uses: "./.github/actions/diffs" + id: diff + + test: + name: Test Rust code + needs: diff + if: ${{ github.event_name == 'schedule' || needs.diff.outputs.isRust == 'true' }} + runs-on: ubuntu-ghcloud + steps: + - uses: actions/checkout@v3 + - run: rustup update stable + - uses: Swatinem/rust-cache@v2 + if: github.ref == 'refs/heads/main' + - uses: Swatinem/rust-cache@v2 + if: github.ref != 'refs/heads/main' + with: + save-if: "false" + - run: sudo apt-get install protobuf-compiler + - run: cargo test --verbose + + lint: + name: Lint Rust code + needs: diff + if: ${{ github.event_name == 'schedule' || needs.diff.outputs.isRust == 'true' }} + runs-on: ubuntu-ghcloud + steps: + - uses: actions/checkout@v3 + - run: rustup update stable + - uses: Swatinem/rust-cache@v2 + if: github.ref == 'refs/heads/main' + - uses: Swatinem/rust-cache@v2 + if: github.ref != 'refs/heads/main' + with: + save-if: "false" + - run: sudo apt-get install protobuf-compiler + - run: cargo install cargo-sort@1.0.9 + - name: Check formatting with rustfmt + run: > + cargo fmt --all -- --check + --config group_imports=StdExternalCrate,imports_granularity=Crate,imports_layout=HorizontalVertical + - name: Check sorting of dependencies + run: cargo sort -w -c + - name: Lint using clippy + run: cargo clippy --all-features --tests --no-deps -- -D warnings + + build: + name: Build Rust code + needs: diff + if: ${{ github.event_name == 'schedule' || needs.diff.outputs.isRust == 'true' }} + runs-on: ubuntu-ghcloud + steps: + - uses: actions/checkout@v3 + - run: rustup update stable + - uses: Swatinem/rust-cache@v2 + if: github.ref == 'refs/heads/main' + - uses: Swatinem/rust-cache@v2 + if: github.ref != 'refs/heads/main' + with: + save-if: "false" + - run: sudo apt-get install protobuf-compiler + - name: Build Rust code + run: cargo build --verbose + + test-move: + name: Test Move code + needs: diff + if: ${{ github.event_name == 'schedule' || needs.diff.outputs.isMove == 'true' }} + runs-on: ubuntu-ghcloud + env: + SUI_BIN: "/home/runner/.cargo/bin/sui" + steps: + - uses: actions/checkout@v3 + - run: rustup update stable + - name: Restore cached sui binary + id: cache-sui-restore + uses: actions/cache/restore@v3 + with: + path: ${{ env.SUI_BIN }} + key: ${{ runner.os }}-${{ runner.arch }}-${{ env.SUI_TAG }} + - name: Install sui + if: steps.cache-sui-restore.outputs.cache-hit != 'true' + run: cargo install --locked --git https://github.com/MystenLabs/sui.git --tag $SUI_TAG --debug sui + - run: sui move test -p contracts/* + - name: Cache sui binary + if: ${{ github.ref == 'refs/heads/main' && steps.cache-sui-restore.outputs.cache-hit != 'true' }} + uses: actions/cache/save@v3 + with: + path: ${{ env.SUI_BIN }} + key: ${{ steps.cache-sui-restore.outputs.cache-primary-key }} diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 0000000..ee6657c --- /dev/null +++ b/_typos.toml @@ -0,0 +1,2 @@ +[files] +extend-exclude = ["crates/scion/src/test_data"] diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..2b9b1f1 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1,3 @@ +[toolchain] +channel = "stable" +components = [ "clippy", "rustfmt" ]