diff --git a/cargo-near/src/commands/new/mod.rs b/cargo-near/src/commands/new/mod.rs index c5e748c6..050b7a9a 100644 --- a/cargo-near/src/commands/new/mod.rs +++ b/cargo-near/src/commands/new/mod.rs @@ -2,7 +2,7 @@ #[interactive_clap(input_context = near_cli_rs::GlobalContext)] #[interactive_clap(output_context = NewContext)] pub struct New { - /// Enter a new project name to create a contract: + /// Enter a new project name (path to the project) to create a contract: pub project_dir: near_cli_rs::types::path_buf::PathBuf, } @@ -14,13 +14,27 @@ impl NewContext { _previous_context: near_cli_rs::GlobalContext, scope: &::InteractiveClapContextScope, ) -> color_eyre::eyre::Result { - let project_dir = scope.project_dir.clone(); - std::process::Command::new("cargo") - .arg("new") - .arg(&project_dir) - .arg("--lib") + const SOURCE_DIR: &str = "./cargo-near/src/commands/new/prototype_for_project/"; + let new_project_dir = scope.project_dir.clone(); + + std::process::Command::new("mkdir") + .arg(&new_project_dir) + .output() + .expect("failed to execute process"); + + std::process::Command::new("cp") + .arg("-r") + .arg(SOURCE_DIR) + .arg(&new_project_dir) .output() .expect("failed to execute process"); + + std::process::Command::new("git") + .arg("init") + .current_dir(&new_project_dir) + .output() + .expect("failed to execute process"); + Ok(Self) } } diff --git a/cargo-near/src/commands/new/prototype_for_project/.github/workflows/deploy-production.yml b/cargo-near/src/commands/new/prototype_for_project/.github/workflows/deploy-production.yml new file mode 100644 index 00000000..16620218 --- /dev/null +++ b/cargo-near/src/commands/new/prototype_for_project/.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 --no-abi "${{ 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/prototype_for_project/.github/workflows/deploy-staging.yml b/cargo-near/src/commands/new/prototype_for_project/.github/workflows/deploy-staging.yml new file mode 100644 index 00000000..4efa6e5a --- /dev/null +++ b/cargo-near/src/commands/new/prototype_for_project/.github/workflows/deploy-staging.yml @@ -0,0 +1,43 @@ +name: Deploy to staging +on: + pull_request: + +jobs: + test: + uses: ./.github/workflows/test.yml + + deploy-staging: + name: Deploy to staging subaccount + 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 + 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 --no-abi "${{ 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 diff --git a/cargo-near/src/commands/new/prototype_for_project/.github/workflows/test.yml b/cargo-near/src/commands/new/prototype_for_project/.github/workflows/test.yml new file mode 100644 index 00000000..7f847afd --- /dev/null +++ b/cargo-near/src/commands/new/prototype_for_project/.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/prototype_for_project/.github/workflows/undeploy-staging.yml b/cargo-near/src/commands/new/prototype_for_project/.github/workflows/undeploy-staging.yml new file mode 100644 index 00000000..48a8fc7a --- /dev/null +++ b/cargo-near/src/commands/new/prototype_for_project/.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/prototype_for_project/.gitignore b/cargo-near/src/commands/new/prototype_for_project/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/cargo-near/src/commands/new/prototype_for_project/.gitignore @@ -0,0 +1 @@ +/target diff --git a/cargo-near/src/commands/new/prototype_for_project/Cargo.toml b/cargo-near/src/commands/new/prototype_for_project/Cargo.toml new file mode 100644 index 00000000..80b597be --- /dev/null +++ b/cargo-near/src/commands/new/prototype_for_project/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "cargo-near-new-project" +description = "New NEAR Protocol smart contract" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +near-sdk = { git = "https://github.com/near/near-sdk-rs" } +serde_json = "1.0.108" + +[dev-dependencies] +near-workspaces = { version = "0.9.0", features = ["unstable"] } +tokio = { version = "1.12.0", features = ["full"] } + +[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/prototype_for_project/README.md b/cargo-near/src/commands/new/prototype_for_project/README.md new file mode 100644 index 00000000..34b53e82 --- /dev/null +++ b/cargo-near/src/commands/new/prototype_for_project/README.md @@ -0,0 +1,37 @@ +# + + + +## 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/prototype_for_project/rust-toolchain.toml b/cargo-near/src/commands/new/prototype_for_project/rust-toolchain.toml new file mode 100644 index 00000000..5879410d --- /dev/null +++ b/cargo-near/src/commands/new/prototype_for_project/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/prototype_for_project/src/lib.rs b/cargo-near/src/commands/new/prototype_for_project/src/lib.rs new file mode 100644 index 00000000..b80b9327 --- /dev/null +++ b/cargo-near/src/commands/new/prototype_for_project/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/prototype_for_project/tests/test_basics.rs b/cargo-near/src/commands/new/prototype_for_project/tests/test_basics.rs new file mode 100644 index 00000000..de6963fd --- /dev/null +++ b/cargo-near/src/commands/new/prototype_for_project/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(()) +}