Skip to content

Commit

Permalink
merge smart contracts repo
Browse files Browse the repository at this point in the history
  • Loading branch information
martyn committed Oct 31, 2024
1 parent 542d010 commit 3cd2d1b
Show file tree
Hide file tree
Showing 9 changed files with 784 additions and 0 deletions.
13 changes: 13 additions & 0 deletions smart_contracts/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[workspace]
members = ["controller", "pool"]
resolver = "2"

[profile.release]
codegen-units = 1
# Tell `rustc` to optimize for small code size.
opt-level = "z"
lto = true
debug = false
panic = "abort"
overflow-checks = false
incremental = false
5 changes: 5 additions & 0 deletions smart_contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SmartPool.near contracts

## Structure
* `pool` contains the smart contract responsible for issuing NEP-141 tokens
* `controller` deploys pool contracts and manages permissions for the pool contracts, as well as keeps track of existing pool contracts that have been deployed
12 changes: 12 additions & 0 deletions smart_contracts/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash
set -e

# Build the pool contract
echo "Building pool contract..."
cargo build --release -p pool --target wasm32-unknown-unknown

wasm-opt -Oz target/wasm32-unknown-unknown/release/pool.wasm -o target/wasm32-unknown-unknown/release/pool_optimized.wasm

# Build the controller contract
echo "Building controller contract..."
cargo build --release -p controller --target wasm32-unknown-unknown
24 changes: 24 additions & 0 deletions smart_contracts/controller/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "controller"
description = "smartpool.near - Main Controller"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
near-sdk = { version = "5.5.0", features = ["unit-testing"] }
near-contract-standards = { version = "5.5.0", features = [] }
near-token = "0.3.0"
schemars = "0.8"
serde_json = "1"

[dev-dependencies]
near-sdk = { version = "5.5.0", features = ["unit-testing"] }
near-workspaces = { version = "0.11.0", features = ["unstable"] }
tokio = { version = "1.12.0", features = ["full"] }
getrandom = { version = "0.2", features = ["js"], default-features = false }
serde = { version = "1.0.188", features = ["derive"] }
#async-std = "1.10.0"
borsh = "1.5.1"
265 changes: 265 additions & 0 deletions smart_contracts/controller/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
// Import necessary modules and crates
use near_sdk::{
env, near_bindgen, AccountId, Promise, Gas, PanicOnDefault, BorshStorageKey,
PromiseResult
};
use near_sdk::store::IterableMap;
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::json_types::U128;

use near_token::NearToken;

// Constants
const GAS_FOR_INIT_POOL: Gas = Gas::from_tgas(50);
const GAS_FOR_CALLBACK: Gas = Gas::from_tgas(50);
const GAS_FOR_FULFILL: Gas = Gas::from_tgas(30);
const MIN_POOL_BALANCE: NearToken = NearToken::from_near(2);

// Include the pool contract WASM code
const POOL_WASM_BYTES: &[u8] = include_bytes!("../../target/wasm32-unknown-unknown/release/pool_optimized.wasm");

#[derive(BorshStorageKey, BorshSerialize)]
pub enum StorageKey {
Pools,
}

// Main controller contract
#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
pub struct MainController {
owner_id: AccountId,
pools: IterableMap<String, AccountId>, // Map of pool IDs to Account IDs
}

#[near_bindgen]
impl MainController {
#[init]
pub fn new(owner_id: AccountId) -> Self {
assert!(!env::state_exists(), "Already initialized");
Self {
owner_id,
pools: IterableMap::new(StorageKey::Pools)
}
}

/// Creates a new pool
#[payable]
pub fn create_pool(&mut self, pool_id: String, name: String, symbol: String) -> Promise {
// Ensure the pool_id is not already used
assert!(!self.pools.contains_key(&pool_id), "Pool with this ID already exists");

// Compute the new pool account ID
let pool_account_id : AccountId = format!("{}.{}", pool_id, env::current_account_id()).parse().unwrap();

// The amount of NEAR to transfer to the new pool contract
let initial_balance = env::attached_deposit();
assert!(initial_balance >= MIN_POOL_BALANCE, "Not enough attached deposit to create pool");

// Create and deploy the pool contract
Promise::new(pool_account_id.clone())
.create_account()
.transfer(initial_balance)
.add_full_access_key(env::signer_account_pk())
.deploy_contract(POOL_WASM_BYTES.to_vec())
.function_call(
"new".to_string(),
near_sdk::serde_json::json!({
"name": name,
"symbol": symbol,
"authorized_account": env::current_account_id(),
"creator": env::predecessor_account_id()
}).to_string().into_bytes(),
NearToken::from_yoctonear(0),
GAS_FOR_INIT_POOL,
)
.then(
Promise::new(env::current_account_id()).function_call(
"on_pool_initialized".to_string(),
near_sdk::serde_json::json!({
"pool_id": pool_id,
"pool_account_id": pool_account_id
}).to_string().into_bytes(),
NearToken::from_yoctonear(0),
GAS_FOR_CALLBACK,
)
)
}

pub fn clear_pools(&mut self) {
assert_eq!(env::predecessor_account_id(), self.owner_id, "Only the contract owner can migrate state");
self.pools.clear();
}

pub fn on_pool_initialized(&mut self, pool_id: String, pool_account_id: AccountId) {
assert_eq!(env::promise_results_count(), 1, "Expected one promise result");
match env::promise_result(0) {
PromiseResult::Successful(_) => {
// Store the pool in our records
self.pools.insert(pool_id.clone(), pool_account_id.clone());
env::log_str(&format!("Pool '{}' created at account '{}'", pool_id, pool_account_id));
},
_ => {
// Handle the failure case
env::panic_str("Failed to initialize pool contract");
}
}
}

pub fn transfer_to_pool(&self, pool_id: String, amount: U128) -> Promise {
// Ensure that only the contract owner can call this function
assert_eq!(
env::predecessor_account_id(),
self.owner_id,
"Only the contract owner can transfer to pools"
);

assert!(self.pools.contains_key(&pool_id), "Pool must exist");

// Construct the pool account ID
let pool_account_id: AccountId = format!("{}.{}", pool_id, env::current_account_id())
.parse()
.expect("Invalid pool account ID");

// Transfer the attached deposit to the pool account
Promise::new(pool_account_id).transfer(NearToken::from_yoctonear(amount.0))
}

pub fn transfer_from_pool(&self, pool_id: String, amount: U128) -> Promise {
// Ensure that only the contract owner can call this function
assert_eq!(
env::predecessor_account_id(),
self.owner_id,
"Only the contract owner can transfer from pools"
);

// Construct the pool account ID
let pool_account_id: AccountId = format!("{}.{}", pool_id, env::current_account_id())
.parse()
.expect("Failed to parse AccountId");

// Initiate a cross-contract call to the pool contract to transfer funds
Promise::new(pool_account_id.clone())
.function_call(
"transfer_to_owner".to_string(),
near_sdk::serde_json::json!({
"amount": amount,
})
.to_string()
.into_bytes(),
NearToken::from_yoctonear(0),
Gas::from_tgas(20), // Adjust gas amount as needed
)
}

pub fn list_pools(&self) -> Vec<String> {
self.pools.keys().map(|k| k.clone()).collect()
}

pub fn fulfill_deposit_iou(&self, pool_id: String, iou_id: u64, amount: U128) -> Promise {
// Ensure that only the contract owner can call this function
assert_eq!(
env::predecessor_account_id(),
self.owner_id,
"Only the contract owner can fulfill deposit IOUs"
);

let pool_account_id: AccountId = format!("{}.{}", pool_id, env::current_account_id())
.parse()
.expect("Failed to parse AccountId");

Promise::new(pool_account_id.clone())
.function_call(
"fulfill_deposit_iou".to_string(),
near_sdk::serde_json::json!({
"iou_id": iou_id,
"amount": amount,
})
.to_string()
.into_bytes(),
NearToken::from_yoctonear(0),
GAS_FOR_FULFILL,
)
}

pub fn fulfill_withdraw_iou(&self, pool_id: String, iou_id: u64, amount: U128) -> Promise {
// Ensure that only the contract owner can call this function
assert_eq!(
env::predecessor_account_id(),
self.owner_id,
"Only the contract owner can fulfill withdraw IOUs"
);

let pool_account_id: AccountId = format!("{}.{}", pool_id, env::current_account_id())
.parse()
.expect("Failed to parse AccountId");

Promise::new(pool_account_id.clone())
.function_call(
"fulfill_withdraw_iou".to_string(),
near_sdk::serde_json::json!({
"iou_id": iou_id,
"amount": amount,
})
.to_string()
.into_bytes(),
NearToken::from_yoctonear(0),
GAS_FOR_FULFILL,
)
}
}

/* The rest of this file contains tests for the code above */
#[cfg(test)]
mod tests {
use super::*;
use near_sdk::{testing_env, VMContext};
use near_sdk::test_utils::{VMContextBuilder, accounts};

// Helper function to create a testing context
fn get_context(predecessor_account_id: AccountId, attached_deposit: u128) -> VMContext {
VMContextBuilder::new()
.current_account_id("controller.testnet".parse().unwrap())
.signer_account_id(predecessor_account_id.clone())
.predecessor_account_id(predecessor_account_id)
.attached_deposit(NearToken::from_yoctonear(attached_deposit))
.build()
}

#[test]
fn test_new() {
let context = get_context(accounts(0), 0);
testing_env!(context);
let contract = MainController::new(accounts(0));
assert_eq!(contract.owner_id, accounts(0));
// POOL_WASM_BYTES is included at compile time
}

#[test]
fn test_create_pool() {
let context = get_context(accounts(0), MIN_POOL_BALANCE.as_yoctonear());
testing_env!(context);
let mut contract = MainController::new(accounts(0));

// Since we cannot simulate promises in unit tests, we'll directly insert into pools
contract.pools.insert("pool1".to_string(), "pool1.controller.testnet".parse().unwrap());

// Check that the pool was added
let pools = contract.list_pools();
assert_eq!(pools.len(), 1);
assert_eq!(pools[0], "pool1".to_string());
}

#[test]
fn test_list_pools() {
let context = get_context(accounts(0), 0);
testing_env!(context);
let mut contract = MainController::new(accounts(0));

// Insert mock pools
contract.pools.insert("pool1".to_string(), "pool1.controller.testnet".parse().unwrap());
contract.pools.insert("pool2".to_string(), "pool2.controller.testnet".parse().unwrap());

let pools = contract.list_pools();
assert_eq!(pools.len(), 2);
}
}
4 changes: 4 additions & 0 deletions smart_contracts/deploy-testnet.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
source .env && \
cd controller && \
cargo near deploy --no-locked --no-docker smartpool.testnet without-init-call network-config testnet sign-with-seed-phrase "$TESTNET_SEED_PHRASE" --seed-phrase-hd-path 'm/44'\''/397'\''/0'\''' send
23 changes: 23 additions & 0 deletions smart_contracts/pool/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "pool"
description = "smartpool.near - pool contract"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
near-sdk = { version = "5.5.0", features = ["unit-testing"] }
near-contract-standards = { version = "5.5.0", features = [] }
near-token = "0.3.0"
schemars = "0.8"
serde_json = "1"

[dev-dependencies]
near-sdk = { version = "5.5.0", features = ["unit-testing"] }
near-workspaces = { version = "0.11.0", features = ["unstable"] }
tokio = { version = "1.12.0", features = ["full"] }
getrandom = { version = "0.2", features = ["js"], default-features = false }
serde = { version = "1.0.188", features = ["derive"] }
borsh = "1.5.1"
Loading

0 comments on commit 3cd2d1b

Please sign in to comment.