Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ getrandom = { version = "0.2", features = ["custom"] }
borsh = { version = "1.5", features = ["unstable__schema"] }
hex = { version = "0.4.3", features = ["serde"] }
near-sdk = { version = "5.7", features = ["unstable"] }
near-sdk-contract-tools = "3.0.2"
near-contract-standards = "5.7"
near-workspaces = { version = "0.16", features = ["unstable"] }
primitive-types = { version = "0.10.1" }
Expand Down
4 changes: 4 additions & 0 deletions common/src/chunked_append_only_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ impl<T: BorshSerialize + BorshDeserialize, const CHUNK_SIZE: u32>
until_index: self.len(),
}
}

pub fn flush(&mut self) {
self.inner.flush();
}
}

impl<'a, T: BorshSerialize + BorshDeserialize, const CHUNK_SIZE: u32> IntoIterator
Expand Down
1 change: 1 addition & 0 deletions contract/market/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ container_build_command = [
[dependencies]
getrandom.workspace = true
near-sdk.workspace = true
near-sdk-contract-tools.workspace = true
near-contract-standards.workspace = true
templar-common.workspace = true

Expand Down
14 changes: 14 additions & 0 deletions contract/market/src/impl_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ use crate::{Contract, ContractExt};
/// Internal helpers.
impl Contract {
pub fn execute_supply(&mut self, account_id: AccountId, amount: BorrowAssetAmount) {
if self.supply_position_ref(account_id.clone()).is_none() {
self.charge_for_storage(
&account_id,
self.storage_usage_supply_position + self.storage_usage_snapshot * 2,
);
}

let supply_maximum_amount = self.configuration.supply_maximum_amount;
let mut supply_position = self.get_or_create_supply_position_guard(account_id);
let proof = supply_position.accumulate_yield();
Expand All @@ -34,6 +41,13 @@ impl Contract {
// The sign-up step would only be NFT gating or something of
// that sort, which is just an additional pre condition check.
// -- https://github.com/Templar-Protocol/contract-mvp/pull/6#discussion_r1923871982
if self.borrow_position_ref(account_id.clone()).is_none() {
self.charge_for_storage(
&account_id,
self.storage_usage_borrow_position + self.storage_usage_snapshot * 2,
);
}

let mut borrow_position = self.get_or_create_borrow_position_guard(account_id);
let proof = borrow_position.accumulate_interest();
borrow_position.record_collateral_asset_deposit(proof, amount);
Expand Down
66 changes: 61 additions & 5 deletions contract/market/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

use std::ops::{Deref, DerefMut};

use near_sdk::{near, BorshStorageKey, PanicOnDefault};
use near_sdk::{env, near, AccountId, BorshStorageKey, PanicOnDefault};
use near_sdk_contract_tools::standard::nep145::{
Nep145Controller, Nep145ForceUnregister, StorageBalanceBounds,
};
use templar_common::market::{Market, MarketConfiguration};

macro_rules! self_ext {
Expand All @@ -17,19 +20,72 @@ enum StorageKey {
Market,
}

#[derive(PanicOnDefault)]
#[derive(PanicOnDefault, near_sdk_contract_tools::Nep145)]
#[nep145(force_unregister_hook = "Self")]
#[near(contract_state)]
pub struct Contract {
pub market: Market,
storage_usage_snapshot: u64,
storage_usage_supply_position: u64,
storage_usage_borrow_position: u64,
}

#[near]
impl Contract {
#[allow(clippy::unwrap_used, reason = "Infallible")]
#[init]
pub fn new(configuration: MarketConfiguration) -> Self {
Self {
market: Market::new(StorageKey::Market, configuration),
}
let mut market = Market::new(StorageKey::Market, configuration);
let storage_usage_1 = env::storage_usage();
market.snapshots.flush();
let storage_usage_2 = env::storage_usage();
let storage_usage_snapshot = storage_usage_2.saturating_sub(storage_usage_1);

// These values shoud be approximately:
// 161 (fixed cost) +
// borsh serialization length of position record +
// 128 (max account length in bytes)

drop(market.get_or_create_supply_position_guard("0".repeat(64).parse().unwrap()));
let storage_usage_3 = env::storage_usage();
let storage_usage_supply_position = storage_usage_3.saturating_sub(storage_usage_2);

drop(market.get_or_create_borrow_position_guard("0".repeat(64).parse().unwrap()));
let storage_usage_4 = env::storage_usage();
let storage_usage_borrow_position = storage_usage_4.saturating_sub(storage_usage_3);

env::log_str(&format!("Storage usage: {{ \"snapshot\": {storage_usage_snapshot}, \"supply_position\":{storage_usage_supply_position}, \"borrow_position\": {storage_usage_borrow_position} }}"));

let mut self_ = Self {
market,
storage_usage_snapshot,
storage_usage_supply_position,
storage_usage_borrow_position,
};

self_.set_storage_balance_bounds(&StorageBalanceBounds {
min: env::storage_byte_cost().saturating_mul(u128::from(
storage_usage_supply_position.max(storage_usage_borrow_position)
+ 2 * storage_usage_snapshot,
)),
max: None,
});

self_
}

fn charge_for_storage(&mut self, account_id: &AccountId, storage_consumption: u64) {
self.lock_storage(
account_id,
env::storage_byte_cost().saturating_mul(u128::from(storage_consumption)),
)
.unwrap_or_else(|e| env::panic_str(&format!("Storage error: {e}")));
}
}

impl near_sdk_contract_tools::hook::Hook<Self, Nep145ForceUnregister<'_>> for Contract {
fn hook<R>(_: &mut Self, _: &Nep145ForceUnregister, _: impl FnOnce(&mut Self) -> R) -> R {
env::panic_str("force unregistration is not supported")
}
}

Expand Down
19 changes: 15 additions & 4 deletions contract/market/tests/happy_path.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use near_sdk::serde_json::json;
use near_sdk_contract_tools::standard::nep145::StorageBalanceBounds;
use rstest::rstest;
use tokio::join;

Expand Down Expand Up @@ -36,6 +38,17 @@ async fn test_happy() {

assert!(configuration.borrow_mcr.near_equal(dec!("1.2")));

let bounds = c
.contract
.view("storage_balance_bounds")
.args_json(json!({}))
.await
.unwrap()
.json::<StorageBalanceBounds>()
.unwrap();

assert!(!bounds.min.is_zero());

let snapshots_len = c.get_snapshots_len().await;
assert_eq!(snapshots_len, 1, "Should generate single snapshot on init");

Expand All @@ -60,7 +73,7 @@ async fn test_happy() {

assert_eq!(
list_supplys,
[supply_user.id().clone()],
["0".repeat(64).parse().unwrap(), supply_user.id().clone()],
"Supply user should be the only account listed",
);

Expand All @@ -80,7 +93,7 @@ async fn test_happy() {

assert_eq!(
list_borrows,
[borrow_user.id().clone()],
["0".repeat(64).parse().unwrap(), borrow_user.id().clone()],
"Borrow user should be the only account listed",
);

Expand All @@ -98,8 +111,6 @@ async fn test_happy() {
// Step 3: Withdraw some of the borrow asset
let balance_before = c.borrow_asset_balance_of(borrow_user.id()).await;

eprintln!("Price pair: {:#?}", c.get_prices().await);

// Borrowing 1000 borrow tokens with 2000 collateral tokens should be fine given equal price and MCR of 120%.
c.borrow(&borrow_user, 1000).await;

Expand Down
13 changes: 13 additions & 0 deletions contract/market/tests/storage_management.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use test_utils::*;

#[tokio::test]
#[should_panic = "is not registered"]
async fn registration_is_required() {
let SetupEverything { c, supply_user, .. } = setup_everything(|_| {}).await;

let unregistered_account = c.worker.dev_create_account().await.unwrap();
c.borrow_asset_transfer(&supply_user, unregistered_account.id(), 10000)
.await;

c.supply(&unregistered_account, 1000).await;
}
2 changes: 1 addition & 1 deletion mock/ft/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
near-sdk.workspace = true
near-contract-standards.workspace = true
near-sdk-contract-tools = "3.0.2"
near-sdk-contract-tools.workspace = true
1 change: 1 addition & 0 deletions test-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ license.workspace = true
[dependencies]
hex-literal = "0.4"
near-sdk.workspace = true
near-sdk-contract-tools.workspace = true
near-workspaces.workspace = true
templar-common.workspace = true
tokio.workspace = true
Expand Down
27 changes: 26 additions & 1 deletion test-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use near_sdk::{
serde_json::{self, json},
AccountId, Gas, NearToken,
};
use near_sdk_contract_tools::standard::nep145::StorageBalanceBounds;
use near_workspaces::{
network::Sandbox, prelude::*, result::ExecutionSuccess, Account, Contract, DevNetwork, Worker,
};
Expand Down Expand Up @@ -48,6 +49,23 @@ pub struct TestController {
impl TestController {
pub async fn storage_deposits(&self, account: &Account) {
eprintln!("Performing storage deposits for {}...", account.id());
let market_storage_bounds = self
.contract
.view("storage_balance_bounds")
.args_json(json!({}))
.await
.unwrap()
.json::<StorageBalanceBounds>()
.unwrap();
eprintln!("Bounds: {market_storage_bounds:#?}");
account
.call(self.contract.id(), "storage_deposit")
.args_json(json!({}))
.deposit(market_storage_bounds.min)
.transact()
.await
.unwrap()
.unwrap();
account
.call(self.borrow_asset.id(), "storage_deposit")
.args_json(json!({}))
Expand Down Expand Up @@ -717,7 +735,7 @@ pub async fn setup_market(
.await;

let contract = worker.dev_deploy(wasm).await.unwrap();
contract
let init_call = contract
.call("new")
.args_json(json!({
"configuration": configuration,
Expand All @@ -727,6 +745,13 @@ pub async fn setup_market(
.unwrap()
.unwrap();

eprintln!("Init call logs");
eprintln!("--------------");
for log in init_call.logs() {
eprintln!("\t{log}");
}
eprintln!("--------------");

contract
}

Expand Down
Loading