Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
05a8e50
feat: add extra accruals to contract state
vasyafromrussia Feb 27, 2024
a905714
feat: register assets
vasyafromrussia Feb 28, 2024
da5b9cd
refactor: move AccountRecord to the contract crate
vasyafromrussia Feb 28, 2024
a4ae8a7
feat: add versioned AccountRecord
vasyafromrussia Feb 28, 2024
c4473e9
refactor: add 'Asset' argument to recording function
vasyafromrussia Feb 28, 2024
cc1b84b
feat: migration on record, record to the correct collection
vasyafromrussia Mar 1, 2024
e68383b
fix: view balance methods take data from both new and legacy collections
vasyafromrussia Mar 1, 2024
4906ca6
test: fix writing methods for account records
vasyafromrussia Mar 5, 2024
ef8e64e
feat: add draft for claim all
vasyafromrussia Mar 8, 2024
780adcb
feat: add draft for claim all
vasyafromrussia Mar 8, 2024
2151196
feat: claim all nep-141 tokens
vasyafromrussia Mar 14, 2024
748b7c1
Merge branch 'feature/nep141' of https://github.com/sweatco/sweat-cla…
vasyafromrussia Mar 14, 2024
680fe99
feat: check attached deposit for recording NEAR
vasyafromrussia Mar 14, 2024
9acb30b
feat: add send for NEAR
vasyafromrussia Mar 14, 2024
943b030
test: prepare tests for claim all
vasyafromrussia Mar 15, 2024
529c54e
test: add base test for claim all
vasyafromrussia Mar 19, 2024
4bde175
fix: get correct accruals
vasyafromrussia Mar 20, 2024
17f6029
refactor: move accruals to a structure
vasyafromrussia Mar 21, 2024
d5ea48e
fix: record extra accruals to a proper collection
vasyafromrussia Mar 21, 2024
1ba6ab5
test: complete test for multiple claim
vasyafromrussia Mar 22, 2024
f6aecf0
test: add unit test for multi claim
vasyafromrussia Mar 22, 2024
f13cef0
feat: add burn for all kinds of assets
vasyafromrussia Mar 25, 2024
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
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"rust-analyzer.linkedProjects": [
"./contract/Cargo.toml",
"./contract/Cargo.toml"
]
}
1 change: 1 addition & 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 @@ -19,6 +19,7 @@ claim-model = { path = "model" }
sweat-model = { git = "https://github.com/sweatco/sweat-near", rev = "bb9acde42fd6ef4f3e4c2a69d4ade8503df40f4a" }

near-workspaces = "0.10.0"
near_contract_standards = "5.0.0"
near-sdk = { git = "https://github.com/sweatco/near-sdk-rs", rev = "8c48b26cc48d969c1e5f3162141fe9c824fccecd" }
near-contract-standards = { git = "https://github.com/sweatco/near-sdk-rs", rev = "8c48b26cc48d969c1e5f3162141fe9c824fccecd" }

Expand Down
1 change: 1 addition & 0 deletions contract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ integration-test = []

[dependencies]
near-sdk = { workspace = true }
near-contract-standards = { workspace = true }

claim-model = { workspace = true }
33 changes: 33 additions & 0 deletions contract/src/asset/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use std::collections::HashMap;

use claim_model::{api::AssetsApi, Asset};
use near_sdk::{near_bindgen, store::UnorderedMap, AccountId};

use crate::{common::AssetExt, Contract, ContractExt, StorageKey};

#[near_bindgen]
impl AssetsApi for Contract {
fn get_assets(&self) -> HashMap<Asset, AccountId> {
self.assets.into_iter().map(|(k, v)| (k.clone(), v.clone())).collect()
}

fn register_asset(&mut self, asset: Asset, contract_id: AccountId) {
let asset = asset.normalize();

self.assert_oracle();
self.assets.insert(asset.clone(), contract_id);
self.accruals.extra.insert(asset.clone(), UnorderedMap::new(StorageKey::ExtraAccrualsEntry(asset)));

}
}

impl Contract {
pub(crate) fn get_token_account_id(&self, asset: &Asset) -> AccountId {
let asset = asset.normalize();
if asset.is_default() {
self.token_account_id.clone()
} else {
self.assets.get(&asset).expect("Asset not found").clone()
}
}
}
1 change: 1 addition & 0 deletions contract/src/asset/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub(crate) mod api;
90 changes: 68 additions & 22 deletions contract/src/burn/api.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,40 @@
use claim_model::{
api::BurnApi,
event::{emit, BurnData, EventKind},
TokensAmount, UnixTimestamp,
Asset, TokensAmount, UnixTimestamp,
};
use near_sdk::{json_types::U128, near_bindgen, require, PromiseOrValue};

use crate::{
common::{now_seconds, UnixTimestampExtension},
Contract, ContractExt,
get_default_asset, Contract, ContractExt,
};

#[near_bindgen]
impl BurnApi for Contract {
fn burn(&mut self) -> PromiseOrValue<U128> {
fn burn(&mut self, asset: Option<Asset>) -> PromiseOrValue<U128> {
self.assert_oracle();

require!(!self.is_service_call_running, "Another service call is running");

self.is_service_call_running = true;

let asset = asset.unwrap_or(get_default_asset());
let mut total_to_burn = 0;
let mut keys_to_remove = vec![];
let now = now_seconds();

for (datetime, (_, total)) in self.accruals.iter() {
let accruals = self.accruals.get_accruals(&asset);

for (datetime, (_, total)) in accruals {
if !datetime.is_within_period(now, self.burn_period) {
keys_to_remove.push(*datetime);
total_to_burn += total;
}
}

if total_to_burn > 0 {
self.burn_external(total_to_burn, keys_to_remove)
self.burn_external(asset, total_to_burn, keys_to_remove)
} else {
self.is_service_call_running = false;

Expand All @@ -43,6 +46,7 @@ impl BurnApi for Contract {
impl Contract {
fn on_burn_internal(
&mut self,
asset: Asset,
total_to_burn: TokensAmount,
keys_to_remove: Vec<UnixTimestamp>,
is_success: bool,
Expand All @@ -53,8 +57,9 @@ impl Contract {
return U128(0);
}

let accruals = self.accruals.get_accruals_mut(&asset);
for datetime in keys_to_remove {
self.accruals.remove(&datetime);
accruals.remove(&datetime);
}

emit(EventKind::Burn(BurnData {
Expand All @@ -67,55 +72,94 @@ impl Contract {

#[cfg(not(test))]
pub(crate) mod prod {
use claim_model::{TokensAmount, UnixTimestamp};
use claim_model::{is_near, Asset, TokensAmount, UnixTimestamp};
use near_contract_standards::fungible_token::{
core::{
ext_ft_core::{self, FungibleTokenCoreExt},
FungibleTokenCore,
},
FungibleToken,
};
use near_sdk::{
env, ext_contract, is_promise_success, json_types::U128, near_bindgen, serde_json::json, Gas, Promise,
PromiseOrValue,
env, ext_contract, is_promise_success, json_types::U128, near_bindgen, serde_json::json, AccountId, Gas,
Promise, PromiseOrValue,
};

use crate::{Contract, ContractExt};
use crate::{common::AssetExt, Contract, ContractExt};

#[ext_contract(ext_self)]
pub trait SelfCallback {
fn on_burn(&mut self, total_to_burn: TokensAmount, keys_to_remove: Vec<UnixTimestamp>) -> U128;
fn on_burn(&mut self, asset: Asset, total_to_burn: TokensAmount, keys_to_remove: Vec<UnixTimestamp>) -> U128;
}

#[near_bindgen]
impl SelfCallback for Contract {
#[private]
fn on_burn(&mut self, total_to_burn: TokensAmount, keys_to_remove: Vec<UnixTimestamp>) -> U128 {
self.on_burn_internal(total_to_burn, keys_to_remove, is_promise_success())
fn on_burn(&mut self, asset: Asset, total_to_burn: TokensAmount, keys_to_remove: Vec<UnixTimestamp>) -> U128 {
self.on_burn_internal(asset, total_to_burn, keys_to_remove, is_promise_success())
}
}

impl Contract {
pub(crate) fn burn_external(
&mut self,
asset: Asset,
total_to_burn: TokensAmount,
keys_to_remove: Vec<UnixTimestamp>,
) -> PromiseOrValue<U128> {
self.burn_promise(&asset, total_to_burn)
.then(
ext_self::ext(env::current_account_id())
.with_static_gas(Gas(5 * Gas::ONE_TERA.0))
.on_burn(asset, total_to_burn, keys_to_remove),
)
.into()
}

fn burn_promise(&mut self, asset: &Asset, total_to_burn: TokensAmount) -> Promise {
if is_near(asset) {
self.burn_near(total_to_burn)
} else if asset.is_default() {
self.burn_sweat(total_to_burn)
} else {
self.burn_ft(asset, total_to_burn)
}
}

fn burn_sweat(&mut self, total_to_burn: TokensAmount) -> Promise {
let args = json!({
"amount": U128(total_to_burn),
})
.to_string()
.as_bytes()
.to_vec();

Promise::new(self.token_account_id.clone())
.function_call("burn".to_string(), args, 0, Gas(5 * Gas::ONE_TERA.0))
.then(
ext_self::ext(env::current_account_id())
.with_static_gas(Gas(5 * Gas::ONE_TERA.0))
.on_burn(total_to_burn, keys_to_remove),
)
.into()
Promise::new(self.token_account_id.clone()).function_call(
"burn".to_string(),
args,
0,
Gas(5 * Gas::ONE_TERA.0),
)
}

fn burn_ft(&mut self, asset: &Asset, total_to_burn: TokensAmount) -> Promise {
let contract_account_id = self.get_token_account_id(&asset);
ext_ft_core::ext(contract_account_id).ft_transfer(
AccountId::new_unchecked("system".to_string()),
U128(total_to_burn),
None,
)
}

fn burn_near(&mut self, total_to_burn: TokensAmount) -> Promise {
Promise::new(AccountId::new_unchecked("system".to_string())).transfer(total_to_burn)
}
}
}

#[cfg(test)]
pub(crate) mod test {
use claim_model::{TokensAmount, UnixTimestamp};
use claim_model::{Asset, TokensAmount, UnixTimestamp};
use near_sdk::{json_types::U128, PromiseOrValue};

use crate::{common::tests::data::get_test_future_success, Contract};
Expand All @@ -125,10 +169,12 @@ pub(crate) mod test {
impl Contract {
pub(crate) fn burn_external(
&mut self,
asset: Asset,
total_to_burn: TokensAmount,
keys_to_remove: Vec<UnixTimestamp>,
) -> PromiseOrValue<U128> {
PromiseOrValue::Value(self.on_burn_internal(
asset,
total_to_burn,
keys_to_remove,
get_test_future_success(EXT_BURN_FUTURE),
Expand Down
47 changes: 28 additions & 19 deletions contract/src/burn/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ fn test_burn_when_outdated_tokens_exist() {
let bob_balance = 200_000;

context.switch_account(&accounts.oracle);
contract.record_batch_for_hold(vec![
(accounts.alice.clone(), U128(alice_balance)),
(accounts.bob.clone(), U128(bob_balance)),
]);
contract.record_batch_for_hold(
vec![
(accounts.alice.clone(), U128(alice_balance)),
(accounts.bob.clone(), U128(bob_balance)),
],
None,
);

context.set_block_timestamp_in_seconds(contract.burn_period as u64 + 100);

Expand All @@ -32,10 +35,10 @@ fn test_burn_when_outdated_tokens_exist() {

assert_eq!(alice_balance + bob_balance, burnt_amount);

let alice_new_balance = contract.get_claimable_balance_for_account(accounts.alice).0;
let alice_new_balance = contract.get_claimable_balance_for_account(accounts.alice, None).0;
assert_eq!(0, alice_new_balance);

let bob_new_balance = contract.get_claimable_balance_for_account(accounts.bob).0;
let bob_new_balance = contract.get_claimable_balance_for_account(accounts.bob, None).0;
assert_eq!(0, bob_new_balance);

assert!(!contract.is_service_call_running);
Expand All @@ -50,10 +53,13 @@ fn test_ext_error_on_burn_when_outdated_tokens_exist() {
let bob_balance = 200_000;

context.switch_account(&accounts.oracle);
contract.record_batch_for_hold(vec![
(accounts.alice.clone(), U128(alice_balance)),
(accounts.bob.clone(), U128(bob_balance)),
]);
contract.record_batch_for_hold(
vec![
(accounts.alice.clone(), U128(alice_balance)),
(accounts.bob.clone(), U128(bob_balance)),
],
None,
);

context.set_block_timestamp_in_seconds(contract.burn_period as u64 + 100);

Expand All @@ -65,28 +71,31 @@ fn test_ext_error_on_burn_when_outdated_tokens_exist() {

assert_eq!(0, burnt_amount);

let alice_new_balance = contract.get_claimable_balance_for_account(accounts.alice).0;
let alice_new_balance = contract.get_claimable_balance_for_account(accounts.alice, None).0;
assert_eq!(0, alice_new_balance);

let bob_new_balance = contract.get_claimable_balance_for_account(accounts.bob).0;
let bob_new_balance = contract.get_claimable_balance_for_account(accounts.bob, None).0;
assert_eq!(0, bob_new_balance);

assert!(!contract.is_service_call_running);
}

#[test]
fn test_burn_when_outdated_tokens_don_not_exist() {
fn test_burn_when_outdated_tokens_do_not_exist() {
let (mut context, mut contract, accounts) = Context::init_with_oracle();
set_test_future_success(EXT_BURN_FUTURE, true);

let alice_balance = 500_000;
let bob_balance = 300_000;

context.switch_account(&accounts.oracle);
contract.record_batch_for_hold(vec![
(accounts.alice.clone(), U128(alice_balance)),
(accounts.bob.clone(), U128(bob_balance)),
]);
contract.record_batch_for_hold(
vec![
(accounts.alice.clone(), U128(alice_balance)),
(accounts.bob.clone(), U128(bob_balance)),
],
None,
);

let burn_result = contract.burn();
let burnt_amount = match burn_result {
Expand All @@ -96,10 +105,10 @@ fn test_burn_when_outdated_tokens_don_not_exist() {

assert_eq!(0, burnt_amount);

let alice_new_balance = contract.get_claimable_balance_for_account(accounts.alice).0;
let alice_new_balance = contract.get_claimable_balance_for_account(accounts.alice, None).0;
assert_eq!(alice_balance, alice_new_balance);

let bob_new_balance = contract.get_claimable_balance_for_account(accounts.bob).0;
let bob_new_balance = contract.get_claimable_balance_for_account(accounts.bob, None).0;
assert_eq!(bob_balance, bob_new_balance);

assert!(!contract.is_service_call_running);
Expand Down
2 changes: 2 additions & 0 deletions contract/src/claim/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub(crate) mod single;
pub(crate) mod total;
Loading