Skip to content

Commit

Permalink
add factory
Browse files Browse the repository at this point in the history
  • Loading branch information
PiVortex committed Aug 30, 2024
1 parent 793d3a4 commit 481757f
Show file tree
Hide file tree
Showing 8 changed files with 372 additions and 0 deletions.
33 changes: 33 additions & 0 deletions factory/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "contract"
description = "Factory Contract Example"
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.3.0", features = ["unstable"] }

[dev-dependencies]
near-sdk = { version = "5.3.0", features = ["unit-testing"] }
near-workspaces = { version = "0.10.0", features = ["unstable"] }
tokio = { version = "1.12.0", features = ["full"] }
serde_json = "1"
chrono = "0.4.38"

[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
4 changes: 4 additions & 0 deletions factory/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"]
Binary file added factory/src/auction-contract/auction.wasm
Binary file not shown.
106 changes: 106 additions & 0 deletions factory/src/deploy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use near_sdk::serde::Serialize;
use near_sdk::json_types::{U128, U64};
use near_sdk::{env, log, near, AccountId, NearToken, Promise, PromiseError};

use crate::{Contract, ContractExt, NEAR_PER_STORAGE, NO_DEPOSIT, TGAS};

pub type TokenId = String;

#[derive(Serialize)]
#[serde(crate = "near_sdk::serde")]
struct AuctionInitArgs {
end_time: U64,
auctioneer: AccountId,
ft_contract: AccountId,
nft_contract: AccountId,
token_id: TokenId,
starting_price: U128,
}

#[near]
impl Contract {
#[payable]
pub fn deploy_new_auction(
&mut self,
name: String,
end_time: U64,
auctioneer: AccountId,
ft_contract: AccountId,
nft_contract: AccountId,
token_id: TokenId,
starting_price: U128,
) -> Promise {
// Assert the sub-account is valid
let current_account = env::current_account_id().to_string();
let subaccount: AccountId = format!("{name}.{current_account}").parse().unwrap();
assert!(
env::is_valid_account_id(subaccount.as_bytes()),
"Invalid subaccount"
);

// Assert enough tokens are attached to create the account and deploy the contract
let attached = env::attached_deposit();

let code = self.code.clone().unwrap();
let contract_bytes = code.len() as u128;
let minimum_needed = NEAR_PER_STORAGE.saturating_mul(contract_bytes);
assert!(
attached >= minimum_needed,
"Attach at least {minimum_needed} yⓃ"
);

let args = &AuctionInitArgs {
end_time,
auctioneer,
ft_contract,
nft_contract,
token_id,
starting_price,
};

let init_args = near_sdk::serde_json::to_vec(args).unwrap();

let promise = Promise::new(subaccount.clone())
.create_account()
.transfer(attached)
.deploy_contract(code)
.function_call(
"init".to_owned(),
init_args,
NO_DEPOSIT,
TGAS.saturating_mul(5),
);

// Add callback
promise.then(
Self::ext(env::current_account_id()).create_factory_subaccount_and_deploy_callback(
subaccount,
env::predecessor_account_id(),
attached,
),
)
}

#[private]
pub fn create_factory_subaccount_and_deploy_callback(
&mut self,
account: AccountId,
user: AccountId,
attached: NearToken,
#[callback_result] create_deploy_result: Result<(), PromiseError>,
) -> bool {
if let Ok(_result) = create_deploy_result {
log!("Correctly created and deployed to {}", account);
return true;
};

log!(
"Error creating {}, returning {}yⓃ to {}",
account,
attached,
user
);
Promise::new(user).transfer(attached);
false
}
}
31 changes: 31 additions & 0 deletions factory/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Find all our documentation at https://docs.near.org
use near_sdk::store::LazyOption;
use near_sdk::{near, Gas, NearToken};

mod deploy;
mod manager;

const NEAR_PER_STORAGE: NearToken = NearToken::from_yoctonear(10u128.pow(18)); // 10e18yⓃ
const AUCTION_CONTRACT: &[u8] = include_bytes!("./auction-contract/auction.wasm");
const TGAS: Gas = Gas::from_tgas(1); // 10e12yⓃ
const NO_DEPOSIT: NearToken = NearToken::from_near(0); // 0yⓃ

// Define the contract structure
#[near(contract_state)]
pub struct Contract {
// Since a contract is something big to store, we use LazyOptions
// this way it is not deserialized on each method call
code: LazyOption<Vec<u8>>,
// Please note that it is much more efficient to **not** store this
// code in the state, and directly use `DEFAULT_CONTRACT`
// However, this does not enable to update the stored code.
}

// Define the default, which automatically initializes the contract
impl Default for Contract {
fn default() -> Self {
Self {
code: LazyOption::new("code".as_bytes(), Some(AUCTION_CONTRACT.to_vec())),
}
}
}
19 changes: 19 additions & 0 deletions factory/src/manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use near_sdk::{env, near};

use crate::{Contract, ContractExt};

#[near]
impl Contract {
#[private]
pub fn update_auction_contract(&mut self) {
// This method receives the code to be stored in the contract directly
// from the contract's input. In this way, it avoids the overhead of
// deserializing parameters, which would consume a huge amount of GAS
self.code.set(env::input());
}

pub fn get_code(&self) -> &Vec<u8> {
// If a contract wants to update themselves, they can ask for the code needed
self.code.get().as_ref().unwrap()
}
}
Binary file added factory/tests/fungible_token.wasm
Binary file not shown.
179 changes: 179 additions & 0 deletions factory/tests/tests_basics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use chrono::Utc;
use near_sdk::{json_types::U128, NearToken};
use near_sdk::{near, AccountId, Gas};
use near_workspaces::result::ExecutionFinalResult;
use near_workspaces::{Account, Contract};
use serde_json::json;

const TEN_NEAR: NearToken = NearToken::from_near(10);
const FT_WASM_FILEPATH: &str = "./tests/fungible_token.wasm";

#[near(serializers = [json])]
pub struct Bid {
pub bidder: AccountId,
pub bid: U128,
}

#[tokio::test]

async fn test_contract_is_operational() -> Result<(), Box<dyn std::error::Error>> {
let sandbox = near_workspaces::sandbox().await?;

let root: near_workspaces::Account = sandbox.root_account()?;

// Create accounts
let alice = create_subaccount(&root, "alice").await?;
let auctioneer = create_subaccount(&root, "auctioneer").await?;
let contract_account = create_subaccount(&root, "contract").await?;
let nft_account = create_subaccount(&root, "nft").await?;

let ft_wasm = std::fs::read(FT_WASM_FILEPATH)?;
let ft_contract = sandbox.dev_deploy(&ft_wasm).await?;

// Initialize FT contract
let res = ft_contract
.call("new_default_meta")
.args_json(serde_json::json!({
"owner_id": root.id(),
"total_supply": U128(1_000_000),
}))
.transact()
.await?;

assert!(res.is_success());

// Skip creating NFT contract as we are are only going to test making a bid to the auction

// Deploy factory contract
let contract_wasm = near_workspaces::compile_project("./").await?;
let contract = contract_account.deploy(&contract_wasm).await?.unwrap();

// Create auction by calling factory contract
let now = Utc::now().timestamp();
let a_minute_from_now = (now + 60) * 1000000000;
let starting_price = U128(10_000);

let deploy_new_auction: ExecutionFinalResult = contract
.call("deploy_new_auction")
.args_json(
json!({"name": "new-auction", "end_time": a_minute_from_now.to_string(),"auctioneer": auctioneer.id(),"ft_contract": ft_contract.id(),"nft_contract": nft_account.id(),"token_id":"1", "starting_price":starting_price }),
)
.max_gas()
.deposit(NearToken::from_near(5))
.transact()
.await?;

assert!(deploy_new_auction.is_success());

let auction_account_id: AccountId = format!("new-auction.{}", contract.id())
.parse()
.unwrap();

// Register accounts
for account_id in [
alice.id().clone(),
auction_account_id.clone(),
]
.iter()
{
let register = ft_contract
.call("storage_deposit")
.args_json(serde_json::json!({ "account_id": account_id }))
.deposit(NearToken::from_yoctonear(8000000000000000000000))
.transact()
.await?;

assert!(register.is_success());
}

// Transfer FTs
let transfer_amount = U128(150_000);

let root_transfer_alice =
ft_transfer(&root, alice.clone(), ft_contract.clone(), transfer_amount).await?;
assert!(root_transfer_alice.is_success());

// Alice makes a bid
let alice_bid = ft_transfer_call(
alice.clone(),
ft_contract.id(),
&auction_account_id,
U128(50_000),
)
.await?;

assert!(alice_bid.is_success());

let highest_bid_alice: Bid = alice.view(&auction_account_id, "get_highest_bid").args_json({}).await?.json()?;
assert_eq!(highest_bid_alice.bid, U128(50_000));
assert_eq!(highest_bid_alice.bidder, *alice.id());

let contract_account_balance: U128 = ft_balance_of(&ft_contract, &auction_account_id).await?;
assert_eq!(contract_account_balance, U128(50_000));
let alice_balance_after_bid: U128 = ft_balance_of(&ft_contract, alice.id()).await?;
assert_eq!(alice_balance_after_bid, U128(100_000));

Ok(())
}

async fn create_subaccount(
root: &near_workspaces::Account,
name: &str,
) -> Result<near_workspaces::Account, Box<dyn std::error::Error>> {
let subaccount = root
.create_subaccount(name)
.initial_balance(TEN_NEAR)
.transact()
.await?
.unwrap();

Ok(subaccount)
}

async fn ft_transfer(
root: &near_workspaces::Account,
account: Account,
ft_contract: Contract,
transfer_amount: U128,
) -> Result<ExecutionFinalResult, Box<dyn std::error::Error>> {
let transfer = root
.call(ft_contract.id(), "ft_transfer")
.args_json(serde_json::json!({
"receiver_id": account.id(),
"amount": transfer_amount
}))
.deposit(NearToken::from_yoctonear(1))
.transact()
.await?;
Ok(transfer)
}

async fn ft_balance_of(
ft_contract: &Contract,
account_id: &AccountId,
) -> Result<U128, Box<dyn std::error::Error>> {
let result = ft_contract
.view("ft_balance_of")
.args_json(json!({"account_id": account_id}))
.await?
.json()?;

Ok(result)
}

async fn ft_transfer_call(
account: Account,
ft_contract_id: &AccountId,
receiver_id: &AccountId,
amount: U128,
) -> Result<ExecutionFinalResult, Box<dyn std::error::Error>> {
let transfer = account
.call(ft_contract_id, "ft_transfer_call")
.args_json(serde_json::json!({
"receiver_id": receiver_id, "amount":amount, "msg": "0" }))
.deposit(NearToken::from_yoctonear(1))
.gas(Gas::from_tgas(300))
.transact()
.await?;
Ok(transfer)
}

0 comments on commit 481757f

Please sign in to comment.