-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
PiVortex
committed
Aug 30, 2024
1 parent
793d3a4
commit 481757f
Showing
8 changed files
with
372 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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())), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |