diff --git a/.gitmodules b/.gitmodules index 20e41d75..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "cargo-near/src/commands/new/new-project-template"] - path = cargo-near/src/commands/new/new-project-template - url = https://github.com/near/cargo-near-new-project-template diff --git a/README.md b/README.md index 20eb493f..4199e424 100644 --- a/README.md +++ b/README.md @@ -96,12 +96,6 @@ Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as below, without any additional terms or conditions. -Note, fetching the git repository of cargo-near, don't forget to pull the submodules after cloning: - -```console -git submodule update --init -``` - ## License Licensed under either of diff --git a/cargo-near/Cargo.toml b/cargo-near/Cargo.toml index c7bbb5f7..289e83c4 100644 --- a/cargo-near/Cargo.toml +++ b/cargo-near/Cargo.toml @@ -10,7 +10,6 @@ repository = "https://github.com/near/cargo-near" license = "MIT OR Apache-2.0" keywords = ["cargo", "near", "contract", "abi", "build"] categories = ["development-tools", "development-tools::cargo-plugins", "development-tools::build-utils", "command-line-utilities"] -include = ["/src"] [package.metadata.wix] upgrade-guid = "FFBAE83D-C3FA-45DD-9F19-C8F312E905C5" diff --git a/cargo-near/src/commands/new/mod.rs b/cargo-near/src/commands/new/mod.rs index 9528f99a..f048aafd 100644 --- a/cargo-near/src/commands/new/mod.rs +++ b/cargo-near/src/commands/new/mod.rs @@ -103,7 +103,7 @@ const NEW_PROJECT_FILES: &[NewProjectFile] = &[ }, NewProjectFile { file_path: "Cargo.toml", - content: include_str!("new-project-template/Cargo.toml"), + content: include_str!("new-project-template/Cargo.toml.template"), }, NewProjectFile { file_path: "README.md", diff --git a/cargo-near/src/commands/new/new-project-template b/cargo-near/src/commands/new/new-project-template deleted file mode 160000 index 9d53496b..00000000 --- a/cargo-near/src/commands/new/new-project-template +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9d53496b4d2bd207d5e815e350a7c28696592068 diff --git a/cargo-near/src/commands/new/new-project-template/.github/workflows/deploy-production.yml b/cargo-near/src/commands/new/new-project-template/.github/workflows/deploy-production.yml new file mode 100644 index 00000000..80623177 --- /dev/null +++ b/cargo-near/src/commands/new/new-project-template/.github/workflows/deploy-production.yml @@ -0,0 +1,27 @@ +name: Deploy to production +on: + push: + branches: [main] + +jobs: + test: + uses: ./.github/workflows/test.yml + + deploy-staging: + name: Deploy to production + needs: [test] + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install cargo-near CLI + run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/cargo-near/releases/download/cargo-near-v0.4.1/cargo-near-installer.sh | sh + - name: Deploy to production + run: | + cargo near deploy "${{ vars.NEAR_CONTRACT_PRODUCTION_ACCOUNT_ID }}" \ + without-init-call \ + network-config "${{ vars.NEAR_CONTRACT_PRODUCTION_NETWORK }}" \ + sign-with-plaintext-private-key \ + --signer-public-key "${{ vars.NEAR_CONTRACT_PRODUCTION_ACCOUNT_PUBLIC_KEY }}" \ + --signer-private-key "${{ secrets.NEAR_CONTRACT_PRODUCTION_ACCOUNT_PRIVATE_KEY }}" \ + send diff --git a/cargo-near/src/commands/new/new-project-template/.github/workflows/deploy-staging.yml b/cargo-near/src/commands/new/new-project-template/.github/workflows/deploy-staging.yml new file mode 100644 index 00000000..d61228da --- /dev/null +++ b/cargo-near/src/commands/new/new-project-template/.github/workflows/deploy-staging.yml @@ -0,0 +1,52 @@ +name: Deploy to staging +on: + pull_request: + +jobs: + test: + uses: ./.github/workflows/test.yml + + deploy-staging: + name: Deploy to staging subaccount + permissions: + pull-requests: write + needs: [test] + runs-on: ubuntu-latest + env: + NEAR_CONTRACT_PR_STAGING_ACCOUNT_ID: gh-${{ github.event.number }}.${{ vars.NEAR_CONTRACT_STAGING_ACCOUNT_ID }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install near CLI + run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/near-cli-rs/releases/download/v0.7.0/near-cli-rs-installer.sh | sh + - name: Create staging account + if: github.event.action == 'opened' || github.event.action == 'reopened' + run: | + near account create-account fund-myself "${{ env.NEAR_CONTRACT_PR_STAGING_ACCOUNT_ID }}" '10 NEAR' \ + use-manually-provided-public-key "${{ vars.NEAR_CONTRACT_STAGING_ACCOUNT_PUBLIC_KEY }}" \ + sign-as "${{ vars.NEAR_CONTRACT_STAGING_ACCOUNT_ID }}" \ + network-config "${{ vars.NEAR_CONTRACT_STAGING_NETWORK }}" \ + sign-with-plaintext-private-key \ + --signer-public-key "${{ vars.NEAR_CONTRACT_STAGING_ACCOUNT_PUBLIC_KEY }}" \ + --signer-private-key "${{ secrets.NEAR_CONTRACT_STAGING_ACCOUNT_PRIVATE_KEY }}" \ + send + + - name: Install cargo-near CLI + run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/cargo-near/releases/download/cargo-near-v0.4.1/cargo-near-installer.sh | sh + - name: Deploy to staging + run: | + cargo near deploy "${{ env.NEAR_CONTRACT_PR_STAGING_ACCOUNT_ID }}" \ + without-init-call \ + network-config "${{ vars.NEAR_CONTRACT_STAGING_NETWORK }}" \ + sign-with-plaintext-private-key \ + --signer-public-key "${{ vars.NEAR_CONTRACT_STAGING_ACCOUNT_PUBLIC_KEY }}" \ + --signer-private-key "${{ secrets.NEAR_CONTRACT_STAGING_ACCOUNT_PRIVATE_KEY }}" \ + send + + - name: Comment on pull request + env: + GH_TOKEN: ${{ github.token }} + run: | + gh pr comment "${{ github.event.number }}" --body "Staging contract is deployed to ["'`'"${{ env.NEAR_CONTRACT_PR_STAGING_ACCOUNT_ID }}"'`'" account](https://explorer.${{ vars.NEAR_CONTRACT_STAGING_NETWORK }}.near.org/accounts/${{ env.NEAR_CONTRACT_PR_STAGING_ACCOUNT_ID }})" diff --git a/cargo-near/src/commands/new/new-project-template/.github/workflows/test.yml b/cargo-near/src/commands/new/new-project-template/.github/workflows/test.yml new file mode 100644 index 00000000..7f847afd --- /dev/null +++ b/cargo-near/src/commands/new/new-project-template/.github/workflows/test.yml @@ -0,0 +1,32 @@ +name: Test +on: + workflow_call: + +jobs: + code-formatting: + name: Code Formatting + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - run: cargo fmt --check + + code-linter: + name: Code Linter + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Run cargo clippy + run: | + rustup component add clippy + cargo clippy --all-features --workspace --tests -- --warn clippy::all --warn clippy::nursery + + tests: + name: Tests + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Run cargo test + run: cargo test diff --git a/cargo-near/src/commands/new/new-project-template/.github/workflows/undeploy-staging.yml b/cargo-near/src/commands/new/new-project-template/.github/workflows/undeploy-staging.yml new file mode 100644 index 00000000..48a8fc7a --- /dev/null +++ b/cargo-near/src/commands/new/new-project-template/.github/workflows/undeploy-staging.yml @@ -0,0 +1,25 @@ +name: Undeploy staging +on: + pull_request: + types: [closed] + +jobs: + cleanup-staging: + name: Cleanup staging account + runs-on: ubuntu-latest + env: + NEAR_CONTRACT_PR_STAGING_ACCOUNT_ID: gh-${{ github.event.number }}.${{ vars.NEAR_CONTRACT_STAGING_ACCOUNT_ID }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install near CLI + run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/near-cli-rs/releases/download/v0.7.0/near-cli-rs-installer.sh | sh + - name: Remove staging account + run: | + near account delete-account "${{ env.NEAR_CONTRACT_PR_STAGING_ACCOUNT_ID }}" \ + beneficiary "${{ vars.NEAR_CONTRACT_STAGING_ACCOUNT_ID }}" \ + network-config "${{ vars.NEAR_CONTRACT_STAGING_NETWORK }}" \ + sign-with-plaintext-private-key \ + --signer-public-key "${{ vars.NEAR_CONTRACT_STAGING_ACCOUNT_PUBLIC_KEY }}" \ + --signer-private-key "${{ secrets.NEAR_CONTRACT_STAGING_ACCOUNT_PRIVATE_KEY }}" \ + send diff --git a/cargo-near/src/commands/new/new-project-template/.gitignore b/cargo-near/src/commands/new/new-project-template/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/cargo-near/src/commands/new/new-project-template/.gitignore @@ -0,0 +1 @@ +/target diff --git a/cargo-near/src/commands/new/new-project-template/Cargo.toml.template b/cargo-near/src/commands/new/new-project-template/Cargo.toml.template new file mode 100644 index 00000000..a89ed18b --- /dev/null +++ b/cargo-near/src/commands/new/new-project-template/Cargo.toml.template @@ -0,0 +1,31 @@ +[package] +name = "cargo-near-new-project-name" +description = "cargo-near-new-project-description" +version = "0.1.0" +edition = "2021" +# TODO: Fill out the repository field to help NEAR ecosystem tools to discover your project. +# NEP-0330 is automatically implemented for all contracts built with near-sdk-rs. +# Link to the repository will be available via `contract_source_metadata` view-function. +#repository = "https://github.com/xxx/xxx" + +[lib] +crate-type = ["cdylib", "rlib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +near-sdk = "5.0.0-alpha.1" + +[dev-dependencies] +near-workspaces = { version = "0.9.0", default-features = false, features = ["install", "unstable"] } +tokio = { version = "1.12.0", features = ["full"] } +serde_json = "1" + +[profile.release] +codegen-units = 1 +# Tell `rustc` to optimize for small code size. +opt-level = "z" +lto = true +debug = false +panic = "abort" +# Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801 +overflow-checks = true diff --git a/cargo-near/src/commands/new/new-project-template/README.md b/cargo-near/src/commands/new/new-project-template/README.md new file mode 100644 index 00000000..c4cd97a0 --- /dev/null +++ b/cargo-near/src/commands/new/new-project-template/README.md @@ -0,0 +1,37 @@ +# cargo-near-new-project-name + +cargo-near-new-project-description + +## How to Build Locally? + +Install [`cargo-near`](https://github.com/near/cargo-near) and run: + +```bash +cargo near build +``` + +## How to Test Locally? + +```bash +cargo test +``` + +## How to Deploy? + +Deployment is automated with GitHub Actions CI/CD pipeline. +To deploy manually, install [`cargo-near`](https://github.com/near/cargo-near) and run: + +```bash +cargo near deploy +``` + +## Useful Links + +- [cargo-near](https://github.com/near/cargo-near) - NEAR smart contract development toolkit for Rust +- [near CLI](https://near.cli.rs) - Iteract with NEAR blockchain from command line +- [NEAR Rust SDK Documentation](https://docs.near.org/sdk/rust/introduction) +- [NEAR Documentation](https://docs.near.org) +- [NEAR StackOverflow](https://stackoverflow.com/questions/tagged/nearprotocol) +- [NEAR Discord](https://near.chat) +- [NEAR Telegram Developers Community Group](https://t.me/neardev) +- NEAR DevHub: [Telegram](https://t.me/neardevhub), [Twitter](https://twitter.com/neardevhub) diff --git a/cargo-near/src/commands/new/new-project-template/rust-toolchain.toml b/cargo-near/src/commands/new/new-project-template/rust-toolchain.toml new file mode 100644 index 00000000..5879410d --- /dev/null +++ b/cargo-near/src/commands/new/new-project-template/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.73.0" +components = ["rustfmt"] +targets = ["wasm32-unknown-unknown"] diff --git a/cargo-near/src/commands/new/new-project-template/src/lib.rs b/cargo-near/src/commands/new/new-project-template/src/lib.rs new file mode 100644 index 00000000..b80b9327 --- /dev/null +++ b/cargo-near/src/commands/new/new-project-template/src/lib.rs @@ -0,0 +1,78 @@ +use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use near_sdk::collections::LookupMap; +use near_sdk::{env, near_bindgen, AccountId, BorshStorageKey}; + +#[near_bindgen] +#[derive(BorshDeserialize, BorshSerialize)] +#[borsh(crate = "near_sdk::borsh")] +pub struct StatusMessage { + records: LookupMap, +} + +#[derive(BorshSerialize, BorshStorageKey)] +#[borsh(crate = "near_sdk::borsh")] +enum StorageKey { + StatusMessageRecords, +} + +impl Default for StatusMessage { + fn default() -> Self { + Self { + records: LookupMap::new(StorageKey::StatusMessageRecords), + } + } +} + +#[near_bindgen] +impl StatusMessage { + pub fn set_status(&mut self, message: String) { + let account_id = env::predecessor_account_id(); + self.records.insert(&account_id, &message); + } + + pub fn get_status(&self, account_id: AccountId) -> Option { + self.records.get(&account_id) + } +} + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(test)] +mod tests { + use near_sdk::test_utils::{accounts, VMContextBuilder}; + use near_sdk::testing_env; + + use super::*; + + // Allows for modifying the environment of the mocked blockchain + fn get_context(predecessor_account_id: AccountId) -> VMContextBuilder { + let mut builder = VMContextBuilder::new(); + builder + .current_account_id(accounts(0)) + .signer_account_id(predecessor_account_id.clone()) + .predecessor_account_id(predecessor_account_id); + builder + } + + #[test] + fn set_get_message() { + let mut context = get_context(accounts(1)); + // Initialize the mocked blockchain + testing_env!(context.build()); + + // Set the testing environment for the subsequent calls + testing_env!(context.predecessor_account_id(accounts(1)).build()); + + let mut contract = StatusMessage::default(); + contract.set_status("hello".to_string()); + assert_eq!( + "hello".to_string(), + contract.get_status(accounts(1)).unwrap() + ); + } + + #[test] + fn get_nonexistent_message() { + let contract = StatusMessage::default(); + assert_eq!(None, contract.get_status("francis.near".parse().unwrap())); + } +} diff --git a/cargo-near/src/commands/new/new-project-template/tests/test_basics.rs b/cargo-near/src/commands/new/new-project-template/tests/test_basics.rs new file mode 100644 index 00000000..de6963fd --- /dev/null +++ b/cargo-near/src/commands/new/new-project-template/tests/test_basics.rs @@ -0,0 +1,33 @@ +use serde_json::json; + +#[tokio::test] +async fn test_contract_is_operational() -> Result<(), Box> { + let sandbox = near_workspaces::sandbox().await?; + let contract_wasm = near_workspaces::compile_project("./").await?; + + let contract = sandbox.dev_deploy(&contract_wasm).await?; + + let user1_account = sandbox.dev_create_account().await?; + let user2_account = sandbox.dev_create_account().await?; + + let outcome = user1_account + .call(contract.id(), "set_status") + .args_json(json!({"message": "test status"})) + .transact() + .await?; + assert!(outcome.is_success()); + + let user1_message_outcome = contract + .view("get_status") + .args_json(json!({"account_id": user1_account.id()})) + .await?; + assert_eq!(user1_message_outcome.json::()?, "test status"); + + let user2_message_outcome = contract + .view("get_status") + .args_json(json!({"account_id": user2_account.id()})) + .await?; + assert_eq!(user2_message_outcome.result, b"null"); + + Ok(()) +}