Skip to content

Commit 71af607

Browse files
authored
Merge pull request #416 from AdExNetwork/issue-391-get-spender-info
GET total_deposited + spender_leaf route completed + tests
2 parents 387fb4b + 5d8b0fe commit 71af607

File tree

8 files changed

+380
-32
lines changed

8 files changed

+380
-32
lines changed

primitives/src/sentry.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::{
22
balances::BalancesState,
33
channel_v5::Channel as ChannelV5,
4+
spender::Spender,
45
validator::{ApproveState, Heartbeat, MessageTypes, NewState, Type as MessageType},
56
Address, Balances, BigNum, Channel, ChannelId, ValidatorId, IPFS,
67
};
@@ -207,6 +208,12 @@ pub struct SuccessResponse {
207208
pub success: bool,
208209
}
209210

211+
#[derive(Serialize, Deserialize, Debug)]
212+
#[serde(rename_all = "camelCase")]
213+
pub struct SpenderResponse {
214+
pub spender: Spender,
215+
}
216+
210217
#[derive(Serialize, Deserialize, Debug)]
211218
pub struct ValidatorMessage {
212219
pub from: ValidatorId,

primitives/src/spender.rs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,23 @@ pub struct Deposit {
99
pub still_on_create2: UnifiedNum,
1010
}
1111

12-
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
12+
#[derive(Serialize, Deserialize, Debug, Clone)]
13+
#[serde(rename_all = "camelCase")]
14+
pub struct SpenderLeaf {
15+
pub total_spent: UnifiedNum,
16+
// merkle_proof: [u8; 32], // TODO
17+
}
18+
19+
#[derive(Debug, Clone, Serialize, Deserialize)]
20+
pub struct Spender {
21+
pub total_deposited: UnifiedNum,
22+
pub spender_leaf: Option<SpenderLeaf>,
23+
}
24+
25+
#[derive(Debug, Clone, PartialEq, Eq)]
1326
pub struct Spendable {
1427
pub spender: Address,
1528
pub channel: Channel,
16-
#[serde(flatten)]
1729
pub deposit: Deposit,
1830
}
1931

@@ -27,23 +39,19 @@ pub struct Aggregate {
2739
}
2840
#[cfg(feature = "postgres")]
2941
mod postgres {
30-
use std::convert::TryFrom;
31-
use tokio_postgres::{Error, Row};
32-
3342
use super::*;
43+
use tokio_postgres::Row;
3444

35-
impl TryFrom<Row> for Spendable {
36-
type Error = Error;
37-
38-
fn try_from(row: Row) -> Result<Self, Self::Error> {
39-
Ok(Spendable {
40-
spender: row.try_get("spender")?,
41-
channel: row.try_get("channel")?,
45+
impl From<Row> for Spendable {
46+
fn from(row: Row) -> Self {
47+
Self {
48+
spender: row.get("spender"),
49+
channel: row.get("channel"),
4250
deposit: Deposit {
43-
total: row.try_get("total")?,
44-
still_on_create2: row.try_get("still_on_create2")?,
51+
total: row.get("total"),
52+
still_on_create2: row.get("still_on_create2"),
4553
},
46-
})
54+
}
4755
}
4856
}
4957
}

primitives/src/unified_num.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,29 @@ impl UnifiedNum {
120120
/// Transform the UnifiedNum precision 8 to a new precision
121121
pub fn to_precision(self, precision: u8) -> BigNum {
122122
let inner = BigNum::from(self.0);
123+
123124
match precision.cmp(&Self::PRECISION) {
124125
Ordering::Equal => inner,
125126
Ordering::Less => inner.div_floor(&BigNum::from(10).pow(Self::PRECISION - precision)),
126127
Ordering::Greater => inner.mul(&BigNum::from(10).pow(precision - Self::PRECISION)),
127128
}
128129
}
129130

131+
/// Transform the BigNum of a given precision to UnifiedNum with precision 8
132+
/// If the resulting value is larger that what UnifiedNum can hold, it will return `None`
133+
pub fn from_precision(amount: BigNum, precision: u8) -> Option<Self> {
134+
// conversation to the UnifiedNum precision is happening with BigNum
135+
let from_precision = match precision.cmp(&Self::PRECISION) {
136+
Ordering::Equal => amount,
137+
Ordering::Less => amount.mul(&BigNum::from(10).pow(Self::PRECISION - precision)),
138+
Ordering::Greater => {
139+
amount.div_floor(&BigNum::from(10).pow(precision - Self::PRECISION))
140+
}
141+
};
142+
// only at the end, see if it fits in `u64`
143+
from_precision.to_u64().map(Self)
144+
}
145+
130146
pub fn to_float_string(self) -> String {
131147
let mut string_value = self.0.to_string();
132148
let value_length = string_value.len();
@@ -361,7 +377,7 @@ mod test {
361377
}
362378

363379
#[test]
364-
fn test_convert_unified_num_to_new_precision() {
380+
fn test_convert_unified_num_to_new_precision_and_from_precision() {
365381
let dai_precision: u8 = 18;
366382
let usdt_precision: u8 = 6;
367383
let same_precision = UnifiedNum::PRECISION;
@@ -371,7 +387,13 @@ mod test {
371387
// 321.00000000
372388
let dai_unified = UnifiedNum::from(32_100_000_000_u64);
373389
let dai_expected = BigNum::from(321_u64) * dai_power;
374-
assert_eq!(dai_expected, dai_unified.to_precision(dai_precision));
390+
let dai_bignum = dai_unified.to_precision(dai_precision);
391+
assert_eq!(dai_expected, dai_bignum);
392+
assert_eq!(
393+
dai_unified,
394+
UnifiedNum::from_precision(dai_bignum, dai_precision)
395+
.expect("Should not overflow the UnifiedNum")
396+
);
375397

376398
// 321.00000777 - should floor to 321.000007 (precision 6)
377399
let usdt_unified = UnifiedNum::from(32_100_000_777_u64);
@@ -389,6 +411,20 @@ mod test {
389411
same_unified.to_precision(same_precision),
390412
"It should not make any adjustments to the precision"
391413
);
414+
415+
// `u64::MAX + 1` should return `None`
416+
let larger_bignum = BigNum::from(u64::MAX) + BigNum::from(1);
417+
418+
// USDT - 18446744073709.551616
419+
assert!(UnifiedNum::from_precision(larger_bignum.clone(), usdt_precision).is_none());
420+
421+
assert_eq!(
422+
// DAI - 18.446744073709551616 (MAX + 1)
423+
Some(UnifiedNum::from(1844674407)),
424+
// UnifiedNum - 18.44674407
425+
UnifiedNum::from_precision(larger_bignum, dai_precision),
426+
"Should floor the large BigNum"
427+
);
392428
}
393429
}
394430

primitives/src/util/tests/prep_db.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ lazy_static! {
4343
addresses
4444
};
4545

46-
// These are the real Addresses of these stablecoins, however, they are only used for testing!
46+
// These are the Goerli testnet Addresses of these stablecoins
4747
pub static ref TOKENS: HashMap<String, Address> = {
4848
let mut tokens = HashMap::new();
4949

50-
tokens.insert("DAI".into(), "0x6b175474e89094c44da98b954eedeac495271d0f".parse::<Address>().expect("Should parse"));
51-
tokens.insert("USDT".into(), "0xdac17f958d2ee523a2206206994597c13d831ec7".parse::<Address>().expect("failed to parse id"));
52-
tokens.insert("USDC".into(), "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48".parse::<Address>().expect("failed to parse id"));
50+
tokens.insert("DAI".into(), "0x73967c6a0904aa032c103b4104747e88c566b1a2".parse::<Address>().expect("Should parse"));
51+
tokens.insert("USDT".into(), "0x509ee0d083ddf8ac028f2a56731412edd63223b9".parse::<Address>().expect("failed to parse id"));
52+
tokens.insert("USDC".into(), "0x44dcfcead37be45206af6079648988b29284b2c6".parse::<Address>().expect("failed to parse id"));
5353
tokens
5454
};
5555

sentry/src/db/event_aggregate.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use chrono::{DateTime, Utc};
22
use futures::pin_mut;
33
use primitives::{
44
balances::UncheckedState,
5+
channel_v5::Channel as ChannelV5,
56
sentry::{EventAggregate, MessageResponse},
67
validator::{ApproveState, Heartbeat, NewState},
78
Address, BigNum, Channel, ChannelId, ValidatorId,
@@ -34,6 +35,23 @@ pub async fn latest_approve_state(
3435
.map_err(PoolError::Backend)
3536
}
3637

38+
pub async fn latest_approve_state_v5(
39+
pool: &DbPool,
40+
channel: &ChannelV5,
41+
) -> Result<Option<MessageResponse<ApproveState>>, PoolError> {
42+
let client = pool.get().await?;
43+
44+
let select = client.prepare("SELECT \"from\", msg, received FROM validator_messages WHERE channel_id = $1 AND \"from\" = $2 AND msg ->> 'type' = 'ApproveState' ORDER BY received DESC LIMIT 1").await?;
45+
let rows = client
46+
.query(&select, &[&channel.id(), &channel.follower])
47+
.await?;
48+
49+
rows.get(0)
50+
.map(MessageResponse::<ApproveState>::try_from)
51+
.transpose()
52+
.map_err(PoolError::Backend)
53+
}
54+
3755
pub async fn latest_new_state(
3856
pool: &DbPool,
3957
channel: &Channel,
@@ -59,6 +77,24 @@ pub async fn latest_new_state(
5977
.map_err(PoolError::Backend)
6078
}
6179

80+
pub async fn latest_new_state_v5(
81+
pool: &DbPool,
82+
channel: &ChannelV5,
83+
state_root: &str,
84+
) -> Result<Option<MessageResponse<NewState<UncheckedState>>>, PoolError> {
85+
let client = pool.get().await?;
86+
87+
let select = client.prepare("SELECT \"from\", msg, received FROM validator_messages WHERE channel_id = $1 AND \"from\" = $2 AND msg ->> 'type' = 'NewState' AND msg->> 'stateRoot' = $3 ORDER BY received DESC LIMIT 1").await?;
88+
let rows = client
89+
.query(&select, &[&channel.id(), &channel.leader, &state_root])
90+
.await?;
91+
92+
rows.get(0)
93+
.map(MessageResponse::<NewState<UncheckedState>>::try_from)
94+
.transpose()
95+
.map_err(PoolError::Backend)
96+
}
97+
6298
pub async fn latest_heartbeats(
6399
pool: &DbPool,
64100
channel_id: &ChannelId,

sentry/src/db/spendable.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::convert::TryFrom;
2-
31
use primitives::{spender::Spendable, Address, ChannelId};
42

53
use super::{DbPool, PoolError};
@@ -43,7 +41,30 @@ pub async fn fetch_spendable(
4341

4442
let row = client.query_opt(&statement, &[spender, channel_id]).await?;
4543

46-
Ok(row.map(Spendable::try_from).transpose()?)
44+
Ok(row.map(Spendable::from))
45+
}
46+
47+
static UPDATE_SPENDABLE_STATEMENT: &str = "INSERT INTO spendable(spender, channel_id, channel, total, still_on_create2) VALUES($1, $2, $3, $4, $5) ON CONFLICT ON CONSTRAINT spendable_pkey DO UPDATE SET total = $4, still_on_create2 = $5 WHERE spendable.spender = $1 AND spendable.channel_id = $2 RETURNING spender, channel_id, channel, total, still_on_create2";
48+
49+
// Updates spendable entry deposit or inserts a new spendable entry if it doesn't exist
50+
pub async fn update_spendable(pool: DbPool, spendable: &Spendable) -> Result<Spendable, PoolError> {
51+
let client = pool.get().await?;
52+
let statement = client.prepare(UPDATE_SPENDABLE_STATEMENT).await?;
53+
54+
let row = client
55+
.query_one(
56+
&statement,
57+
&[
58+
&spendable.spender,
59+
&spendable.channel.id(),
60+
&spendable.channel,
61+
&spendable.deposit.total,
62+
&spendable.deposit.still_on_create2,
63+
],
64+
)
65+
.await?;
66+
67+
Ok(Spendable::from(row))
4768
}
4869

4970
#[cfg(test)]

sentry/src/lib.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ use routes::analytics::{advanced_analytics, advertiser_analytics, analytics, pub
2626
use routes::campaign::{create_campaign, update_campaign};
2727
use routes::cfg::config;
2828
use routes::channel::{
29-
channel_list, channel_validate, create_channel, create_validator_messages, last_approved,
29+
channel_list, channel_validate, create_channel, create_validator_messages, get_spender_limits,
30+
last_approved,
3031
};
3132
use slog::Logger;
3233
use std::collections::HashMap;
@@ -63,6 +64,7 @@ lazy_static! {
6364
static ref ADVERTISER_ANALYTICS_BY_CHANNEL_ID: Regex = Regex::new(r"^/analytics/for-advertiser/0x([a-zA-Z0-9]{64})/?$").expect("The regex should be valid");
6465
static ref PUBLISHER_ANALYTICS_BY_CHANNEL_ID: Regex = Regex::new(r"^/analytics/for-publisher/0x([a-zA-Z0-9]{64})/?$").expect("The regex should be valid");
6566
static ref CREATE_EVENTS_BY_CHANNEL_ID: Regex = Regex::new(r"^/channel/0x([a-zA-Z0-9]{64})/events/?$").expect("The regex should be valid");
67+
static ref CHANNEL_SPENDER_LEAF_AND_TOTAL_DEPOSITED: Regex = Regex::new(r"^/v5/channel/0x([a-zA-Z0-9]{64})/spender/0x([a-zA-Z0-9]{40})/?$").expect("This regex should be valid");
6668
}
6769

6870
static INSERT_EVENTS_BY_CAMPAIGN_ID: Lazy<Regex> = Lazy::new(|| {
@@ -373,6 +375,24 @@ async fn channels_router<A: Adapter + 'static>(
373375
req = ChannelLoad.call(req, app).await?;
374376

375377
list_channel_event_aggregates(req, app).await
378+
} else if let (Some(caps), &Method::GET) = (
379+
CHANNEL_SPENDER_LEAF_AND_TOTAL_DEPOSITED.captures(&path),
380+
method,
381+
) {
382+
let param = RouteParams(vec![
383+
caps.get(1)
384+
.map_or("".to_string(), |m| m.as_str().to_string()), // channel ID
385+
caps.get(2)
386+
.map_or("".to_string(), |m| m.as_str().to_string()), // spender addr
387+
]);
388+
req.extensions_mut().insert(param);
389+
req = Chain::new()
390+
.chain(AuthRequired)
391+
.chain(ChannelLoad)
392+
.apply(req, app)
393+
.await?;
394+
395+
get_spender_limits(req, app).await
376396
} else {
377397
Err(ResponseError::NotFound)
378398
}
@@ -513,9 +533,9 @@ pub mod test_util {
513533
Application,
514534
};
515535

516-
/// Uses production configuration to setup the correct Contract addresses for tokens.
536+
/// Uses development and therefore the goreli testnet addresses of the tokens
517537
pub async fn setup_dummy_app() -> Application<DummyAdapter> {
518-
let config = configuration("production", None).expect("Should get Config");
538+
let config = configuration("development", None).expect("Should get Config");
519539
let adapter = DummyAdapter::init(
520540
DummyAdapterOptions {
521541
dummy_identity: IDS["leader"],

0 commit comments

Comments
 (0)