Skip to content

Commit

Permalink
fix: allow multiple recordings in the same block
Browse files Browse the repository at this point in the history
  • Loading branch information
vasyafromrussia committed Feb 15, 2024
1 parent 83831a1 commit f6333cf
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 47 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion contract/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sweat_claim"
version = "1.0.0"
version = "1.0.1"
authors = ["Sweat Economy"]
edition = "2021"

Expand Down
5 changes: 4 additions & 1 deletion contract/src/claim/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#![cfg(test)]

use model::{api::{ClaimApi, RecordApi}, ClaimAvailabilityView, UnixTimestamp};
use model::{
api::{ClaimApi, RecordApi},
ClaimAvailabilityView, UnixTimestamp,
};
use near_sdk::{json_types::U128, PromiseOrValue};

use crate::{
Expand Down
32 changes: 13 additions & 19 deletions contract/src/record/api.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
use model::{
account_record::AccountRecord,
api::RecordApi,
event::{emit, EventKind, RecordData},
event::{BatchedEmitData, RecordData},
};
use near_sdk::{json_types::U128, near_bindgen, require, store::Vector, AccountId};
use near_sdk::{json_types::U128, near_bindgen, store::Vector, AccountId};

use crate::{common::now_seconds, Contract, ContractExt, StorageKey::AccrualsEntry};

const RECORD_EVENT_BATCH_SIZE: usize = 100;

#[near_bindgen]
impl RecordApi for Contract {
fn record_batch_for_hold(&mut self, amounts: Vec<(AccountId, U128)>) {
self.assert_oracle();

let now_seconds = now_seconds();
let mut balances = Vector::new(AccrualsEntry(now_seconds));
let mut total_balance = 0;
let mut event_data = RecordData::new(now_seconds);

let mut event_data = RecordData {
timestamp: now_seconds,
amounts: vec![],
};
let balances = self
.accruals
.entry(now_seconds)
.or_insert_with(|| (Vector::new(AccrualsEntry(now_seconds)), 0));

for (account_id, amount) in amounts {
event_data.amounts.push((account_id.clone(), amount));

let amount = amount.0;
let index = balances.len();
let index = balances.0.len();

total_balance += amount;
balances.push(amount);
balances.1 += amount;
balances.0.push(amount);

if let Some(record) = self.accounts.get_mut(&account_id) {
record.accruals.push((now_seconds, index));
Expand All @@ -42,13 +43,6 @@ impl RecordApi for Contract {
}
}

let existing = self.accruals.insert(now_seconds, (balances, total_balance));

emit(EventKind::Record(event_data));

require!(
existing.is_none(),
format!("Record for this timestamp: {now_seconds} already existed. It was overwritten.")
);
event_data.emit_batched(RECORD_EVENT_BATCH_SIZE);
}
}
29 changes: 23 additions & 6 deletions contract/src/record/tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![cfg(test)]

use model::api::{ClaimApi, RecordApi};
use near_sdk::json_types::U128;
use model::api::{ClaimApi, ConfigApi, RecordApi};
use near_sdk::{json_types::U128, AccountId};

use crate::common::tests::Context;

Expand Down Expand Up @@ -44,11 +44,28 @@ fn record_by_not_oracle() {
}

#[test]
#[should_panic(expected = "Record for this timestamp: 0 already existed. It was overwritten.")]
fn test_record() {
fn test_multiple_records_in_the_same_block() {
let (mut context, mut contract, accounts) = Context::init_with_oracle();

let target_accruals = [10, 20];
let target_accounts = [accounts.alice.clone(), accounts.bob.clone()];
let batches: Vec<Vec<(AccountId, U128)>> = (0..target_accruals.len())
.map(|index| vec![(target_accounts[index].clone(), target_accruals[index].into())])
.collect();

context.switch_account(&accounts.oracle);
contract.record_batch_for_hold(vec![(accounts.alice.clone(), 10.into())]);
contract.record_batch_for_hold(vec![(accounts.alice, 10.into())]);
contract.record_batch_for_hold(batches.get(0).unwrap().clone());
contract.record_batch_for_hold(batches.get(1).unwrap().clone());

let accruals = contract.accruals.get(&0).unwrap();
assert_eq!(accruals.0.len(), target_accruals.len() as u32);
assert_eq!(accruals.1, target_accruals.iter().sum::<u128>());

contract.set_claim_period(0);

for index in 0..target_accruals.len() {
let account = target_accounts.get(index).unwrap();
let balance = contract.get_claimable_balance_for_account(account.clone());
assert_eq!(balance.0, target_accruals.get(index).unwrap().clone());
}
}
69 changes: 50 additions & 19 deletions model/src/event.rs
Original file line number Diff line number Diff line change
@@ -1,68 +1,99 @@
use near_sdk::{
env,
json_types::U128,
log,
serde::{Deserialize, Serialize},
serde_json, AccountId,
};
use near_sdk::{env, json_types::U128, log, serde::Serialize, serde_json, AccountId};

use crate::UnixTimestamp;

pub const PACKAGE_NAME: &str = "sweat_claim";
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Debug)]
#[serde(
crate = "near_sdk::serde",
tag = "event",
content = "data",
rename_all = "snake_case"
)]
pub enum EventKind {
pub enum EventKind<'a> {
Burn(BurnData),
Claim(ClaimData),
Clean(CleanData),
Record(RecordData),
RecordChunk(RecordChunk<'a>),
}

#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Debug)]
#[serde(crate = "near_sdk::serde")]
pub struct BurnData {
pub burnt_amount: U128,
}

#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Debug)]
#[serde(crate = "near_sdk::serde")]
pub struct ClaimData {
pub account_id: AccountId,
pub details: Vec<(UnixTimestamp, U128)>,
pub total_claimed: U128,
}

#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Debug)]
#[serde(crate = "near_sdk::serde")]
pub struct CleanData {
pub account_ids: Vec<AccountId>,
}

#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Debug)]
#[serde(crate = "near_sdk::serde")]
pub struct RecordData {
pub timestamp: UnixTimestamp,
pub amounts: Vec<(AccountId, U128)>,
}

#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Debug)]
#[serde(crate = "near_sdk::serde")]
pub struct RecordChunk<'a> {
pub timestamp: UnixTimestamp,
pub amounts: &'a [(AccountId, U128)],
}

impl RecordData {
pub fn new(timestamp: UnixTimestamp) -> Self {
Self {
timestamp,
amounts: vec![],
}
}
}

pub trait BatchedEmitData {
fn emit_batched(self, batch_size: usize);
}

impl BatchedEmitData for RecordData {
fn emit_batched(self, batch_size: usize) {
if self.amounts.len() <= batch_size {
emit(EventKind::Record(self));
return;
}

for amounts in self.amounts.chunks(batch_size) {
emit(EventKind::RecordChunk(RecordChunk {
timestamp: self.timestamp,
amounts,
}));
}
}
}

#[derive(Serialize, Debug)]
#[serde(crate = "near_sdk::serde", rename_all = "snake_case")]
struct SweatClaimEvent {
struct SweatClaimEvent<'a> {
standard: &'static str,
version: &'static str,
#[serde(flatten)]
event_kind: EventKind,
event_kind: EventKind<'a>,
}

impl From<EventKind> for SweatClaimEvent {
fn from(event_kind: EventKind) -> Self {
impl<'a> From<EventKind<'a>> for SweatClaimEvent<'a> {
fn from(event_kind: EventKind<'a>) -> Self {
Self {
standard: PACKAGE_NAME,
version: VERSION,
Expand All @@ -75,7 +106,7 @@ pub fn emit(event: EventKind) {
log!(SweatClaimEvent::from(event).to_json_event_string());
}

impl SweatClaimEvent {
impl SweatClaimEvent<'_> {
fn to_json_string(&self) -> String {
serde_json::to_string_pretty(self)
.unwrap_or_else(|err| env::panic_str(&format!("Failed to serialize SweatClaimEvent: {err}")))
Expand Down
Binary file modified res/sweat_claim.wasm
Binary file not shown.

0 comments on commit f6333cf

Please sign in to comment.