Skip to content

Commit 88137cc

Browse files
committed
adapter - Dummy adapter refactor
- use DUMMY_CHAIN_INFO in tests - sentry - routes - POST /v5/channel/dummy-deposit
1 parent ef51c8b commit 88137cc

File tree

15 files changed

+343
-169
lines changed

15 files changed

+343
-169
lines changed

adapter/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ license = "AGPL-3.0"
1212

1313
[features]
1414

15-
# Enables testing untilites for working with Ganache.
15+
# Enables testing untilites for working with Ganache and the Dummy adapter.
1616
test-util = ["primitives/test-util"]
1717

1818
[dependencies]
@@ -39,6 +39,9 @@ once_cell = "1.8"
3939
parse-display = "0.5"
4040

4141
[dev-dependencies]
42+
# we require the test-util future for testing
43+
primitives = { path = "../primitives", features = ["test-util"] }
44+
4245
byteorder = "1.4"
4346
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
4447
pretty_assertions = "1"

adapter/src/dummy.rs

Lines changed: 188 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,48 @@ use crate::{
66
Error,
77
};
88
use async_trait::async_trait;
9-
use dashmap::{mapref::entry::Entry, DashMap};
109

11-
use once_cell::sync::Lazy;
1210
use primitives::{
13-
Address, Chain, ChainId, ChainOf, Channel, ChannelId, ToETHChecksum, ValidatorId,
11+
config::ChainInfo, Address, ChainId, ChainOf, Channel, ToETHChecksum, ValidatorId,
1412
};
15-
use std::{collections::HashMap, sync::Arc};
13+
use std::collections::HashMap;
14+
15+
#[doc(inline)]
16+
pub use self::deposit::Deposits;
1617

1718
pub type Adapter<S> = crate::Adapter<Dummy, S>;
1819

19-
/// The Dummy Chain to be used with this adapter
20-
/// The Chain is not applicable to the adapter, however, it is required for
21-
/// applications because of the `authentication` & [`Channel`] interactions.
22-
pub static DUMMY_CHAIN: Lazy<Chain> = Lazy::new(|| Chain {
23-
chain_id: ChainId::new(1),
24-
rpc: "http://dummy.com".parse().expect("Should parse ApiUrl"),
25-
outpace: "0x0000000000000000000000000000000000000000"
26-
.parse()
27-
.unwrap(),
28-
});
20+
#[cfg(feature = "test-util")]
21+
#[cfg_attr(docsrs, doc(cfg(feature = "test-util")))]
22+
pub mod test_util {
23+
use once_cell::sync::Lazy;
24+
use primitives::{
25+
config::{ChainInfo, TokenInfo, DUMMY_CONFIG},
26+
Chain,
27+
};
28+
pub static DUMMY_TOKEN: Lazy<TokenInfo> =
29+
Lazy::new(|| DUMMY_CONFIG.chains["Dummy"].tokens["DUMMY"].clone());
30+
31+
pub static DUMMY_CHAIN_INFO: Lazy<ChainInfo> =
32+
Lazy::new(|| DUMMY_CONFIG.chains["Dummy"].clone());
33+
34+
/// The Dummy Chain to be used with this adapter
35+
/// The Chain is not applicable to the adapter, however, it is required for
36+
/// applications because of the `authentication` & [`Channel`] interactions.
37+
pub static DUMMY_CHAIN: Lazy<Chain> = Lazy::new(|| DUMMY_CHAIN_INFO.chain.clone());
38+
}
39+
40+
#[derive(Debug, Clone)]
41+
pub struct Options {
42+
/// The identity used for the Adapter.
43+
pub dummy_identity: ValidatorId,
44+
/// The authentication tokens that will be used by the adapter
45+
/// for returning & validating authentication tokens of requests.
46+
pub dummy_auth_tokens: HashMap<Address, String>,
47+
/// The [`ChainInfo`] that will be used for the [`Session`]s and
48+
/// also for the deposits.
49+
pub dummy_chain: ChainInfo,
50+
}
2951

3052
/// Dummy adapter implementation intended for testing.
3153
#[derive(Debug, Clone)]
@@ -34,63 +56,51 @@ pub struct Dummy {
3456
identity: ValidatorId,
3557
/// Static authentication tokens (address => token)
3658
authorization_tokens: HashMap<Address, String>,
59+
chain_info: ChainInfo,
3760
deposits: Deposits,
3861
}
3962

40-
pub struct Options {
41-
pub dummy_identity: ValidatorId,
42-
pub dummy_auth_tokens: HashMap<Address, String>,
43-
}
44-
45-
#[derive(Debug, Clone, Default)]
46-
#[allow(clippy::type_complexity)]
47-
pub struct Deposits(Arc<DashMap<(ChannelId, Address), (usize, Vec<Deposit>)>>);
48-
49-
impl Deposits {
50-
pub fn add_deposit(&self, channel: ChannelId, address: Address, deposit: Deposit) {
51-
match self.0.entry((channel, address)) {
52-
Entry::Occupied(mut deposit_calls) => {
53-
// add the new deposit to the Vec
54-
deposit_calls.get_mut().1.push(deposit);
55-
}
56-
Entry::Vacant(empty) => {
57-
// add the new `(ChannelId, Address)` key and init with index 0 and the passed Deposit
58-
empty.insert((0, vec![deposit]));
59-
}
60-
}
61-
}
62-
63-
pub fn get_next_deposit(&self, channel: ChannelId, address: Address) -> Option<Deposit> {
64-
match self.0.entry((channel, address)) {
65-
Entry::Occupied(mut entry) => {
66-
let (call_index, deposit_calls) = entry.get_mut();
67-
68-
let deposit = deposit_calls.get(*call_index).cloned()?;
69-
70-
// increment the index for the next call
71-
*call_index = call_index
72-
.checked_add(1)
73-
.expect("Deposit call index has overflowed");
74-
Some(deposit)
75-
}
76-
Entry::Vacant(_) => None,
77-
}
78-
}
79-
}
80-
8163
impl Dummy {
8264
pub fn init(opts: Options) -> Self {
8365
Self {
8466
identity: opts.dummy_identity,
8567
authorization_tokens: opts.dummy_auth_tokens,
68+
chain_info: opts.dummy_chain,
8669
deposits: Default::default(),
8770
}
8871
}
8972

90-
pub fn add_deposit_call(&self, channel: ChannelId, address: Address, deposit: Deposit) {
91-
self.deposits.add_deposit(channel, address, deposit)
73+
/// Set the deposit that you want the adapter to return every time
74+
/// when the [`get_deposit()`](Locked::get_deposit) get's called
75+
/// for the give [`ChannelId`] and [`Address`].
76+
///
77+
/// If [`Deposit`] is set to [`None`], it remove the mocked deposit.
78+
///
79+
/// # Panics
80+
///
81+
/// When [`None`] is passed but there was no mocked deposit.
82+
pub fn set_deposit<D: Into<Option<Deposit>>>(
83+
&self,
84+
channel_context: &ChainOf<Channel>,
85+
depositor: Address,
86+
deposit: D,
87+
) {
88+
use deposit::Key;
89+
90+
let key = Key::from_chain_of(channel_context, depositor);
91+
match deposit.into() {
92+
Some(deposit) => {
93+
self.deposits.0.insert(key, deposit);
94+
}
95+
None => {
96+
self.deposits.0.remove(&key).unwrap_or_else(|| {
97+
panic!("Couldn't remove a deposit which doesn't exist for {key:?}")
98+
});
99+
}
100+
};
92101
}
93102
}
103+
94104
#[async_trait]
95105
impl Locked for Dummy {
96106
type Error = Error;
@@ -131,7 +141,7 @@ impl Locked for Dummy {
131141
Some((address, _token)) => Ok(Session {
132142
uid: *address,
133143
era: 0,
134-
chain: DUMMY_CHAIN.clone(),
144+
chain: self.chain_info.chain.clone(),
135145
}),
136146
None => Err(Error::authentication(format!(
137147
"No identity found that matches authentication token: {}",
@@ -145,11 +155,31 @@ impl Locked for Dummy {
145155
channel_context: &ChainOf<Channel>,
146156
depositor_address: Address,
147157
) -> Result<Deposit, crate::Error> {
158+
// validate that the same chain & token are used for the Channel Context
159+
// as the ones setup in the Dummy adapter.
160+
if channel_context.token.address != channel_context.context.token {
161+
return Err(Error::adapter(
162+
"Token context of channel & channel token addresses are different".to_string(),
163+
));
164+
}
165+
166+
if self.chain_info.chain != channel_context.chain
167+
|| self
168+
.chain_info
169+
.find_token(channel_context.context.token)
170+
.is_none()
171+
{
172+
return Err(Error::adapter(
173+
"Channel's Token & Chain not aligned with Dummy adapter's chain".to_string(),
174+
));
175+
}
176+
148177
self.deposits
149-
.get_next_deposit(channel_context.context.id(), depositor_address)
178+
.get_deposit(channel_context, depositor_address)
150179
.ok_or_else(|| {
151180
Error::adapter(format!(
152-
"No more mocked deposits found for depositor {:?}",
181+
"No mocked deposit found for {:?} & depositor {:?}",
182+
channel_context.context.id(),
153183
depositor_address
154184
))
155185
})
@@ -190,43 +220,91 @@ impl Unlockable for Dummy {
190220
}
191221
}
192222

223+
mod deposit {
224+
use crate::primitives::Deposit;
225+
use dashmap::DashMap;
226+
use primitives::{Address, ChainId, ChainOf, Channel, ChannelId};
227+
use std::sync::Arc;
228+
229+
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
230+
pub struct Key {
231+
channel_id: ChannelId,
232+
chain_id: ChainId,
233+
depositor: Address,
234+
}
235+
236+
impl Key {
237+
pub fn from_chain_of(channel_context: &ChainOf<Channel>, depositor: Address) -> Self {
238+
Self {
239+
channel_id: channel_context.context.id(),
240+
chain_id: channel_context.chain.chain_id,
241+
depositor,
242+
}
243+
}
244+
}
245+
246+
/// Mocked deposits for the Dummy adapter.
247+
///
248+
/// These deposits can be set once and the adapter will return
249+
/// the set deposit on every call to [`get_deposit()`](`Locked::get_deposit`).
250+
#[derive(Debug, Clone, Default)]
251+
pub struct Deposits(pub Arc<DashMap<Key, Deposit>>);
252+
253+
impl Deposits {
254+
pub fn new() -> Self {
255+
Self::default()
256+
}
257+
258+
/// Get's the set deposit for the give [`ChannelId`] and [`Address`].
259+
///
260+
/// This method will return [`None`] if the deposit for the pair
261+
/// [`ChannelId`] & [`Address`] was not set.
262+
pub fn get_deposit(
263+
&self,
264+
channel: &ChainOf<Channel>,
265+
depositor: Address,
266+
) -> Option<Deposit> {
267+
self.0
268+
.get(&Key::from_chain_of(channel, depositor))
269+
.map(|dashmap_ref| dashmap_ref.value().clone())
270+
}
271+
}
272+
}
273+
193274
#[cfg(test)]
194275
mod test {
195-
use std::num::NonZeroU8;
196-
197276
use primitives::{
198-
config::TokenInfo,
199-
test_util::{CREATOR, DUMMY_CAMPAIGN, IDS, LEADER},
200-
BigNum, ChainOf, UnifiedNum,
277+
test_util::{CREATOR, DUMMY_CAMPAIGN, IDS, LEADER, PUBLISHER},
278+
BigNum, ChainOf,
201279
};
202280

203-
use super::*;
281+
use super::{
282+
test_util::{DUMMY_CHAIN, DUMMY_CHAIN_INFO, DUMMY_TOKEN},
283+
*,
284+
};
204285

205286
#[tokio::test]
206287
async fn test_deposits_calls() {
207288
let channel = DUMMY_CAMPAIGN.channel;
208289

209290
let channel_context = ChainOf {
210291
context: channel,
211-
token: TokenInfo {
212-
min_campaign_budget: 1_u64.into(),
213-
min_validator_fee: 1_u64.into(),
214-
precision: NonZeroU8::new(UnifiedNum::PRECISION).expect("Non zero u8"),
215-
address: channel.token,
216-
},
292+
token: DUMMY_TOKEN.clone(),
217293
chain: DUMMY_CHAIN.clone(),
218294
};
219295

220296
let dummy_client = Dummy::init(Options {
221297
dummy_identity: IDS[&LEADER],
222298
dummy_auth_tokens: Default::default(),
299+
dummy_chain: DUMMY_CHAIN_INFO.clone(),
223300
});
224301

225-
let address = *CREATOR;
302+
let creator = *CREATOR;
303+
let publisher = *PUBLISHER;
226304

227305
// no mocked deposit calls should cause an Error
228306
{
229-
let result = dummy_client.get_deposit(&channel_context, address).await;
307+
let result = dummy_client.get_deposit(&channel_context, creator).await;
230308

231309
assert!(result.is_err());
232310
}
@@ -235,32 +313,50 @@ mod test {
235313
total: BigNum::from(total),
236314
};
237315

238-
// add two deposit and call 3 times
239-
// also check if different address does not have access to these calls
316+
// add two deposit for CREATOR & PUBLISHER
240317
{
241-
let deposits = [get_deposit(6969), get_deposit(1000)];
242-
dummy_client.add_deposit_call(channel.id(), address, deposits[0].clone());
243-
dummy_client.add_deposit_call(channel.id(), address, deposits[1].clone());
318+
let creator_deposit = get_deposit(6969);
319+
let publisher_deposit = get_deposit(1000);
320+
321+
dummy_client.set_deposit(&channel_context, creator, creator_deposit.clone());
322+
dummy_client.set_deposit(&channel_context, publisher, publisher_deposit.clone());
244323

245-
let first_call = dummy_client
246-
.get_deposit(&channel_context, address)
324+
let creator_actual = dummy_client
325+
.get_deposit(&channel_context, creator)
247326
.await
248-
.expect("Should get first mocked deposit");
249-
assert_eq!(&deposits[0], &first_call);
327+
.expect("Should get mocked deposit");
328+
assert_eq!(&creator_deposit, &creator_actual);
250329

251-
// should not affect the Mocked deposit calls and should cause an error
330+
// calling an non-mocked address, should cause an error
252331
let different_address_call = dummy_client.get_deposit(&channel_context, *LEADER).await;
253332
assert!(different_address_call.is_err());
254333

255-
let second_call = dummy_client
256-
.get_deposit(&channel_context, address)
334+
let publisher_actual = dummy_client
335+
.get_deposit(&channel_context, publisher)
257336
.await
258-
.expect("Should get second mocked deposit");
259-
assert_eq!(&deposits[1], &second_call);
260-
261-
// Third call should error, we've only mocked 2 calls!
262-
let third_call = dummy_client.get_deposit(&channel_context, address).await;
263-
assert!(third_call.is_err());
337+
.expect("Should get mocked deposit");
338+
assert_eq!(&publisher_deposit, &publisher_actual);
264339
}
265340
}
341+
342+
#[test]
343+
#[should_panic]
344+
fn test_set_deposit_to_none_should_panic_on_non_mocked_deposits() {
345+
let channel = DUMMY_CAMPAIGN.channel;
346+
347+
let channel_context = ChainOf {
348+
context: channel,
349+
token: DUMMY_TOKEN.clone(),
350+
chain: DUMMY_CHAIN.clone(),
351+
};
352+
353+
let dummy_client = Dummy::init(Options {
354+
dummy_identity: IDS[&LEADER],
355+
dummy_auth_tokens: Default::default(),
356+
dummy_chain: DUMMY_CHAIN_INFO.clone(),
357+
});
358+
359+
// It should panic when no deposit is set and we try to set it to None
360+
dummy_client.set_deposit(&channel_context, *LEADER, None);
361+
}
266362
}

0 commit comments

Comments
 (0)