Skip to content

Commit

Permalink
Merge pull request #17 from garikbesson/rust-version
Browse files Browse the repository at this point in the history
Added rust version of contract, reorganized repo structure
  • Loading branch information
gagdiez authored Mar 12, 2024
2 parents 85e7b2c + ff26b7c commit 428ae8d
Show file tree
Hide file tree
Showing 22 changed files with 303 additions and 13,092 deletions.
23 changes: 14 additions & 9 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
node_modules
build
neardev
yarn.lock
.parcel-cache/
dist
**/videos
**/screenshots
.idea
# Rust
**/target
**/Cargo.lock

# TypeScript
**/package-lock.json
**/node_modules/
**/build/
**/yarn.lock
**/.tsimp

# Frontend
**/dist/
**/.parcel-cache
21 changes: 0 additions & 21 deletions LICENSE

This file was deleted.

32 changes: 32 additions & 0 deletions contract-rs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "contract-rs"
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 https://github.com/near/cargo-near.
# 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 = { version = "5.0.0", features = ["legacy"] }

[dev-dependencies]
near-sdk = { version = "5.0.0", features = ["unit-testing"] }
near-workspaces = { version = "0.10.0", features = ["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
61 changes: 61 additions & 0 deletions contract-rs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Coin Flip 🪙
[![](https://img.shields.io/badge/⋈%20Examples-Basics-green)](https://docs.near.org/tutorials/welcome)
[![](https://img.shields.io/badge/Gitpod-Ready-orange)](https://gitpod.io/#/https://github.com/near-examples/coin-flip-js)
[![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fnear-examples%2Fcoin-flip-js%2Fbadge%3Fref%3Dmain&style=flat&label=Tests&ref=main)](https://actions-badge.atrox.dev/near-examples/coin-flip-js/goto?ref=main)

Coin Flip is a game were the player tries to guess the outcome of a coin flip. It is one of the simplest contracts implementing random numbers.


# What This Example Shows

1. How to store and retrieve information in the NEAR network.
2. How to integrate a smart contract in a web frontend.
3. How to generate random numbers in a contract.

<br />

# Quickstart

1. Make sure you have installed [rust](https://rust.org/).
2. Install the [`NEAR CLI`](https://github.com/near/near-cli#setup)

<br />

## 1. Build, Test and Deploy
To build the contract you can execute the `./build.sh` script, which will in turn run:

```bash
rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --release
```

Then, deploy your contract using following commands:

```bash
export CONTRACT_ID=test.near
near deploy "'$CONTRACT_ID'" ./target/wasm32-unknown-unknown/release/contract.wasm
```

<br />

## 2. Retrieve the user points

`points_of` is a `view` method.

`View` methods can be called for **free** by anyone, even people **without a NEAR account**!

```bash
# Use near-cli to get the greeting
near view $CONTRACT_ID points_of '{"player": "'$CONTRACT_ID'"}'
```

<br />

## 3. Flip a coin
`flip_coin` changes the contract's state, for which it is a `call` method.

`Call` methods can only be invoked using a NEAR account, since the account needs to pay GAS for the transaction. In this case, we are asking the account we created in step 1 to sign the transaction.

```bash
near call $CONTRACT_ID flip_coin '{"player_guess": "tails"}' --accountId $CONTRACT_ID
```
4 changes: 4 additions & 0 deletions contract-rs/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rustfmt"]
targets = ["wasm32-unknown-unknown"]
82 changes: 82 additions & 0 deletions contract-rs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Find all our documentation at https://docs.near.org
use near_sdk::borsh::{BorshSerialize, BorshDeserialize};
use near_sdk::env::{self, log_str};
use near_sdk::{near_bindgen, AccountId, BorshStorageKey};
use near_sdk::collections::UnorderedMap;

#[derive(BorshSerialize, BorshStorageKey)]
#[borsh(crate = "near_sdk::borsh")]
enum StorageKey {
Points,
}

pub(crate) fn simulate_coin_flip() -> String {
// Here we get a first byte of a random seed
let random_seed = *env::random_seed().get(0).unwrap() as i8;

// If a first byte is EVEN we choose heads, otherwise tails
if let 0 = random_seed % 2 {
return "heads".to_string()
} else {
return "tails".to_string()
};
}

// Define the contract structure
#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize)]
#[borsh(crate = "near_sdk::borsh")]
pub struct Contract {
points: UnorderedMap<AccountId, u8>,
}

// Define the default, which automatically initializes the contract
impl Default for Contract {
fn default() -> Self {
Self {
points: UnorderedMap::new(StorageKey::Points)
}
}
}

// Implement the contract structure
#[near_bindgen]
impl Contract {
/*
Flip a coin. Pass in the side (heads or tails) and a random number will be chosen
indicating whether the flip was heads or tails. If you got it right, you get a point.
*/
pub fn flip_coin(&mut self, player_guess: String) -> String {
// Check who called the method
let player: AccountId = env::predecessor_account_id();
log_str(&format!("{player} chose {player_guess}"));

// Simulate a Coin Flip
let outcome = simulate_coin_flip();

// Get the current player points
let mut player_points = self.points.get(&player).unwrap_or(0);

// Check if their guess was right and modify the points accordingly
if outcome.eq(&player_guess) {
player_points = player_points + 1;
} else {
player_points = player_points.saturating_sub(1);
};

log_str(&format!("player_points: {player_points}"));

// Store the new points
self.points.insert(&player, &player_points);

return outcome;
}

// View how many points a specific player has
pub fn points_of(&self, player: AccountId) -> u8 {
let points = self.points.get(&player).unwrap_or(0);
log_str(&format!("Points for {player}: {points}"));

return points;
}
}
82 changes: 82 additions & 0 deletions contract-rs/tests/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use near_workspaces::{types::NearToken, Account, Contract};
use serde_json::json;

#[tokio::test]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let worker = near_workspaces::sandbox().await?;
let contract_wasm = near_workspaces::compile_project("./").await?;
let contract = worker.dev_deploy(&contract_wasm).await?;

// create accounts
let account = worker.dev_create_account().await?;
let alice = account
.create_subaccount("alice")
.initial_balance(NearToken::from_near(30))
.transact()
.await?
.into_result()?;

// begin tests
test_user_has_no_points(&alice, &contract).await?;
test_points_are_correctly_computed(&alice, &contract).await?;
Ok(())
}

async fn test_user_has_no_points(
user: &Account,
contract: &Contract,
) -> Result<(), Box<dyn std::error::Error>> {
let points: u8 = user
.call(contract.id(), "points_of")
.args_json(json!({ "player": user.id()}))
.transact()
.await?
.json()?;

assert_eq!(points, 0);
println!(" Passed ✅ test_user_has_no_points");
Ok(())
}

async fn test_points_are_correctly_computed(
user: &Account,
contract: &Contract,
) -> Result<(), Box<dyn std::error::Error>> {
let mut tails_counter = 0;
let mut heads_counter = 0;
let mut expected_points = 0;

let mut i = 0;
while i < 10 {
let outcome: String = user.call(contract.id(), "flip_coin")
.args_json(json!({"player_guess": "tails"}))
.transact()
.await?
.json()?;

if outcome.eq("tails") {
tails_counter = tails_counter + 1;
expected_points = expected_points + 1;
} else {
heads_counter = heads_counter + 1;
if expected_points > 0 {
expected_points = expected_points - 1;
}
}
i = i + 1;
}

assert!(heads_counter >= 2);
assert!(tails_counter >= 2);

let points: u8 = user
.call(contract.id(), "points_of")
.args_json(json!({ "player": user.id()}))
.transact()
.await?
.json()?;

assert_eq!(points, expected_points);
println!(" Passed ✅ test_points_are_correctly_computed");
Ok(())
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
"test": "ava"
},
"devDependencies": {
"@types/bn.js": "^5.1.0",
"@types/node": "^18.6.2",
"ava": "^4.2.0",
"near-workspaces": "^3.2.1",
"ava": "^4.3.3",
"near-workspaces": "^3.2.2",
"typescript": "^4.6.4",
"ts-node": "^10.8.0",
"typescript": "^4.7.2"
"@types/bn.js": "^5.1.0"
},
"dependencies": {}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Worker, NearAccount } from 'near-workspaces';
import anyTest, { TestFn } from 'ava';
import * as path from 'path';

const test = anyTest as TestFn<{
worker: Worker;
Expand All @@ -12,10 +13,10 @@ test.beforeEach(async (t) => {

// Deploy contract
const root = worker.rootAccount;
const contract = await root.createSubAccount('test-account');

// Get wasm file path from package.json test script in folder above
await contract.deploy(process.argv[2]);
const contractPath = path.join(__dirname, "../../build/contract.wasm");
const contract = await root.devDeploy(contractPath);

// Save state for test runs, it is unique for each test
t.context.worker = worker;
Expand Down
9 changes: 5 additions & 4 deletions contract/package.json → contract-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
"scripts": {
"build": "./build.sh",
"deploy": "./deploy.sh",
"test": "echo no unit testing"
"test": "npm run build && npm run test:integration",
"test:integration": "cd integration-tests && npm test"
},
"dependencies": {
"near-cli": "^3.4.0",
"near-cli": "^4.0.10",
"near-sdk-js": "1.0.0"
},
"devDependencies": {
"typescript": "^4.8.4",
"ts-morph": "^16.0.0"
"ts-morph": "^21.0.1",
"typescript": "^5.3.3"
}
}
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 428ae8d

Please sign in to comment.