Skip to content

Commit

Permalink
Store user jars in map (#24)
Browse files Browse the repository at this point in the history
* wip

* some more migration progress

* fixed all todos, removed JarState

* fixed claim

* all tests passing

* store last jar index in contract

* fixed and updated integration tests

* claim jars by indices

* remove account id from withdraw api

* remeasure after claim

* return slices instead of vector for account jars getter

* remove account id parameter from restake

* added documentation

* renamed index to ID

* added ids to get principal and get interest

* store last jar id for each user

* ID -> Id

* increment jar index in one place

* delete jar only after full claim and withdraw, some test fixes

* new github token

* Rename

* delete empty jar on after_claim

* extracted claim_interest

* removed unreachable test part

* build push binary in docker

---------

Co-authored-by: Vasily Styagov <[email protected]>
  • Loading branch information
VladasZ and vasyafromrussia authored Sep 25, 2023
1 parent d2bbae1 commit c10c09f
Show file tree
Hide file tree
Showing 29 changed files with 1,152 additions and 862 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v3

- name: Build
run: make build
run: make build-in-docker

- name: Upload binary
uses: actions/upload-artifact@v3
Expand Down Expand Up @@ -72,4 +72,4 @@ jobs:
with:
message: Updated binary
branch: main
github_token: ${{ secrets.GITHUB_TOKEN }}
github_token: ${{ secrets.ACTIONS_TOKEN }}
8 changes: 1 addition & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@ jobs:
uses: actions/checkout@v3

- name: Build
run: make build-in-docker

- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: sweat-jar
path: res/sweat_jar.wasm
run: make build

lint:
runs-on: ubuntu-latest
Expand Down
6 changes: 1 addition & 5 deletions contract/src/assert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@ use near_sdk::{require, AccountId};

use crate::{
common::{Timestamp, TokenAmount},
jar::model::{Jar, JarState},
jar::model::Jar,
product::model::Product,
};

pub(crate) fn assert_is_not_closed(jar: &Jar) {
assert_ne!(jar.state, JarState::Closed, "Jar is closed");
}

pub(crate) fn assert_sufficient_balance(jar: &Jar, amount: TokenAmount) {
require!(jar.principal >= amount, "Insufficient balance");
}
Expand Down
110 changes: 79 additions & 31 deletions contract/src/claim/api.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::cmp;

use near_sdk::{env, ext_contract, is_promise_success, json_types::U128, near_bindgen, PromiseOrValue};
use near_sdk::{env, ext_contract, is_promise_success, json_types::U128, near_bindgen, AccountId, PromiseOrValue};

use crate::{
common::{TokenAmount, GAS_FOR_AFTER_CLAIM},
event::{emit, ClaimEventItem, EventKind},
ft_interface::FungibleTokenInterface,
jar::model::{Jar, JarIndex},
jar::model::{Jar, JarId},
Contract, ContractExt, Promise,
};

Expand All @@ -20,11 +20,11 @@ pub trait ClaimApi {
/// interest across all jars is zero, the returned value will also be zero.
fn claim_total(&mut self) -> PromiseOrValue<U128>;

/// Claims interest from specific deposit jars with provided indices.
/// Claims interest from specific deposit jars with provided IDs.
///
/// # Arguments
///
/// * `jar_indices` - A `Vec<JarIndex>` containing the indices of the deposit jars from which interest is being claimed.
/// * `jar_ids` - A `Vec<JarId>` containing the IDs of the deposit jars from which interest is being claimed.
/// * `amount` - An optional `TokenAmount` specifying the desired amount of tokens to claim. If provided, the method
/// will attempt to claim this specific amount of tokens. If not provided or if the specified amount
/// is greater than the total available interest in the provided jars, the method will claim the maximum
Expand All @@ -34,7 +34,7 @@ pub trait ClaimApi {
///
/// A `PromiseOrValue<TokenAmount>` representing the amount of tokens claimed. If the total available interest
/// across the specified jars is zero or the provided `amount` is zero, the returned value will also be zero.
fn claim_jars(&mut self, jar_indices: Vec<JarIndex>, amount: Option<U128>) -> PromiseOrValue<U128>;
fn claim_jars(&mut self, jar_ids: Vec<JarId>, amount: Option<U128>) -> PromiseOrValue<U128>;
}

#[ext_contract(ext_self)]
Expand All @@ -46,18 +46,19 @@ pub trait ClaimCallbacks {
impl ClaimApi for Contract {
fn claim_total(&mut self) -> PromiseOrValue<U128> {
let account_id = env::predecessor_account_id();
let jar_indices = self.account_jar_ids(&account_id);
self.claim_jars(jar_indices, None)
let jar_ids = self.account_jars(&account_id).iter().map(|a| a.id).collect();
self.claim_jars(jar_ids, None)
}

fn claim_jars(&mut self, jar_indices: Vec<JarIndex>, amount: Option<U128>) -> PromiseOrValue<U128> {
fn claim_jars(&mut self, jar_ids: Vec<JarId>, amount: Option<U128>) -> PromiseOrValue<U128> {
let account_id = env::predecessor_account_id();
let now = env::block_timestamp_ms();

let unlocked_jars: Vec<Jar> = jar_indices
.into_iter()
.map(|index| self.get_jar_internal(index))
.filter(|jar| !jar.is_pending_withdraw && jar.account_id == account_id)
let unlocked_jars: Vec<Jar> = self
.account_jars(&account_id)
.iter()
.filter(|jar| !jar.is_pending_withdraw && jar_ids.contains(&jar.id))
.cloned()
.collect();

let mut total_interest_to_claim: TokenAmount = 0;
Expand All @@ -71,57 +72,104 @@ impl ClaimApi for Contract {
cmp::min(available_interest, amount.0 - total_interest_to_claim)
});

let updated_jar = jar.claimed(available_interest, interest_to_claim, now).locked();
self.jars.replace(jar.index, updated_jar);
self.get_jar_mut_internal(&jar.account_id, jar.id)
.claim(available_interest, interest_to_claim, now)
.lock();

if interest_to_claim > 0 {
total_interest_to_claim += interest_to_claim;

event_data.push(ClaimEventItem {
index: jar.index,
id: jar.id,
interest_to_claim: U128(interest_to_claim),
});
}
}

if total_interest_to_claim > 0 {
self.ft_contract()
.transfer(&account_id, total_interest_to_claim, "claim", &None)
.then(after_claim_call(
U128(total_interest_to_claim),
unlocked_jars,
EventKind::Claim(event_data),
))
.into()
self.claim_interest(
&account_id,
U128(total_interest_to_claim),
unlocked_jars,
EventKind::Claim(event_data),
)
} else {
PromiseOrValue::Value(U128(0))
}
}
}

#[near_bindgen]
impl ClaimCallbacks for Contract {
#[private]
fn after_claim(&mut self, claimed_amount: U128, jars_before_transfer: Vec<Jar>, event: EventKind) -> U128 {
if is_promise_success() {
impl Contract {
#[cfg(test)]
fn claim_interest(
&mut self,
_account_id: &AccountId,
claimed_amount: U128,
jars_before_transfer: Vec<Jar>,
event: EventKind,
) -> PromiseOrValue<U128> {
PromiseOrValue::Value(self.after_claim_internal(claimed_amount, jars_before_transfer, event, true))
}

#[cfg(not(test))]
fn claim_interest(
&mut self,
account_id: &AccountId,
claimed_amount: U128,
jars_before_transfer: Vec<Jar>,
event: EventKind,
) -> PromiseOrValue<U128> {
self.ft_contract()
.transfer(account_id, claimed_amount.0, "claim", &None)
.then(after_claim_call(claimed_amount, jars_before_transfer, event))
.into()
}

fn after_claim_internal(
&mut self,
claimed_amount: U128,
jars_before_transfer: Vec<Jar>,
event: EventKind,
is_promise_success: bool,
) -> U128 {
if is_promise_success {
for jar_before_transfer in jars_before_transfer {
self.get_jar_mut_internal(jar_before_transfer.index).unlock();
let jar = self.get_jar_mut_internal(&jar_before_transfer.account_id, jar_before_transfer.id);

jar.unlock();

if let Some(ref cache) = jar.cache {
if cache.interest == 0 && jar.principal == 0 {
self.delete_jar(jar_before_transfer);
}
}
}

emit(event);

claimed_amount
} else {
for jar_before_transfer in jars_before_transfer {
self.jars
.replace(jar_before_transfer.index, jar_before_transfer.unlocked());
let account_id = jar_before_transfer.account_id.clone();
let jar_id = jar_before_transfer.id;

*self.get_jar_mut_internal(&account_id, jar_id) = jar_before_transfer.unlocked();
}

U128(0)
}
}
}

#[near_bindgen]
impl ClaimCallbacks for Contract {
#[private]
fn after_claim(&mut self, claimed_amount: U128, jars_before_transfer: Vec<Jar>, event: EventKind) -> U128 {
self.after_claim_internal(claimed_amount, jars_before_transfer, event, is_promise_success())
}
}

#[cfg(not(test))]
fn after_claim_call(claimed_amount: U128, jars_before_transfer: Vec<Jar>, event: EventKind) -> Promise {
ext_self::ext(env::current_account_id())
.with_static_gas(GAS_FOR_AFTER_CLAIM)
Expand Down
57 changes: 1 addition & 56 deletions contract/src/claim/mod.rs
Original file line number Diff line number Diff line change
@@ -1,57 +1,2 @@
pub mod api;

#[cfg(test)]
mod tests {
use near_sdk::{json_types::U128, test_utils::accounts, PromiseOrValue};

use crate::{
claim::api::ClaimApi,
common::{tests::Context, u32::U32, udecimal::UDecimal, MS_IN_YEAR},
jar::{api::JarApi, model::Jar},
product::model::{Apy, Product},
};

#[test]
fn claim_total_when_nothing_to_claim() {
let alice = accounts(0);
let admin = accounts(1);

let product = generate_product();
let jar = Jar::generate(0, &alice, &product.id).principal(100_000_000);
let mut context = Context::new(admin).with_products(&[product]).with_jars(&[jar]);

context.switch_account(&alice);
let result = context.contract.claim_total();

if let PromiseOrValue::Value(value) = result {
assert_eq!(0, value.0);
} else {
panic!();
}
}

#[test]
fn claim_partially_when_having_tokens_to_claim() {
let alice = accounts(0);
let admin = accounts(1);

let product = generate_product();
let jar = Jar::generate(0, &alice, &product.id).principal(100_000_000_000);
let mut context = Context::new(admin).with_products(&[product]).with_jars(&[jar.clone()]);

context.set_block_timestamp_in_days(365);

context.switch_account(&alice);
context.contract.claim_jars(vec![jar.index], Some(U128(100)));

let jar = context.contract.get_jar(U32(jar.index));
assert_eq!(100, jar.claimed_balance.0);
}

fn generate_product() -> Product {
Product::generate("product")
.enabled(true)
.lockup_term(MS_IN_YEAR)
.apy(Apy::Constant(UDecimal::new(12, 2)))
}
}
mod tests;
Loading

0 comments on commit c10c09f

Please sign in to comment.