Skip to content

Commit bc92dc9

Browse files
authored
fix(authenticators): Nested config encoding, use base64 (#339)
Fixes nested Authenticator AnyOf/AllOf `config` encoding to be array of bytes: - implements manual `serde` serializer; - use base64.
1 parent 32c6fd2 commit bc92dc9

File tree

3 files changed

+77
-13
lines changed

3 files changed

+77
-13
lines changed

v4-client-rs/client/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ telemetry = [
2424
[dependencies]
2525
anyhow.workspace = true
2626
async-trait.workspace = true
27+
base64 = "0.22"
2728
bigdecimal.workspace = true
2829
bip32 = { version = "0.5", default-features = false, features = ["bip39", "alloc", "secp256k1"] }
2930
cosmrs = "0.21.1"

v4-client-rs/client/src/node/client/authenticators.rs

+41-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use super::*;
22

33
use anyhow::{anyhow as err, ensure, Error};
4+
use base64::prelude::*;
45
use dydx_proto::dydxprotocol::accountplus::{
56
AccountAuthenticator, GetAuthenticatorsRequest, MsgAddAuthenticator, MsgRemoveAuthenticator,
67
};
7-
use serde::{Deserialize, Serialize};
8+
use serde::ser;
89

910
/// [`NodeClient`] Authenticator requests dispatcher.
1011
pub struct Authenticators<'a> {
@@ -13,20 +14,19 @@ pub struct Authenticators<'a> {
1314

1415
/// [`Authenticator`] type.
1516
/// An authenticator can be composed by a single or multiple types.
16-
#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
17-
#[serde(tag = "type", content = "config")]
17+
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
1818
pub enum Authenticator {
1919
/// Enables authentication via a specific key.
2020
SignatureVerification(Vec<u8>),
2121
/// Restricts authentication to certain message types. Configured using string bytes, with
2222
/// different message types separated by commas.
23-
MessageFilter(Vec<u8>),
23+
MessageFilter(String),
2424
/// Restricts authentication to certain subaccount constraints. Configured using string bytes,
2525
/// with different IDs separated by commas.
26-
SubaccountFilter(Vec<u8>),
26+
SubaccountFilter(String),
2727
/// Restricts transactions to specific CLOB pair IDs. Configured using string bytes, with
2828
/// different subaccount numbers separated by commas.
29-
ClobPairIdFilter(Vec<u8>),
29+
ClobPairIdFilter(String),
3030
/// Composable type, restricts authentication if any sub-authenticator is valid.
3131
AnyOf(Vec<Authenticator>),
3232
/// Composable type, restricts authentication if all sub-authenticators are valid.
@@ -125,10 +125,10 @@ impl Authenticator {
125125
Authenticator::AllOf(types) | Authenticator::AnyOf(types) => {
126126
Ok(serde_json::to_string(&types)?.into_bytes())
127127
}
128-
Authenticator::SignatureVerification(v)
129-
| Authenticator::MessageFilter(v)
128+
Authenticator::SignatureVerification(v) => Ok(v.clone()),
129+
Authenticator::MessageFilter(v)
130130
| Authenticator::ClobPairIdFilter(v)
131-
| Authenticator::SubaccountFilter(v) => Ok(v.clone()),
131+
| Authenticator::SubaccountFilter(v) => Ok(v.clone().into_bytes()),
132132
}
133133
}
134134

@@ -172,6 +172,38 @@ impl Authenticator {
172172
}
173173
}
174174

175+
impl serde::Serialize for Authenticator {
176+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
177+
where
178+
S: serde::Serializer,
179+
{
180+
let mut json_obj = serde_json::Map::new();
181+
182+
// type (tag)
183+
json_obj.insert(
184+
"type".to_string(),
185+
serde_json::Value::String(self.type_to_str().to_string()),
186+
);
187+
188+
// config (content)
189+
let config_bytes = match self {
190+
Authenticator::SignatureVerification(bytes) => bytes.to_owned(),
191+
Authenticator::MessageFilter(string)
192+
| Authenticator::SubaccountFilter(string)
193+
| Authenticator::ClobPairIdFilter(string) => string.clone().into_bytes(),
194+
Authenticator::AnyOf(auths) | Authenticator::AllOf(auths) => {
195+
serde_json::to_string(auths)
196+
.map_err(|e| ser::Error::custom(format!("JSON serialization error: {}", e)))?
197+
.into_bytes()
198+
}
199+
};
200+
let base64 = BASE64_STANDARD.encode(config_bytes);
201+
json_obj.insert("config".to_string(), serde_json::Value::String(base64));
202+
203+
serde_json::Value::Object(json_obj).serialize(serializer)
204+
}
205+
}
206+
175207
#[cfg(test)]
176208
mod tests {
177209
use super::*;

v4-client-rs/client/tests/test_node_authenticators.rs

+35-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use env::TestEnv;
44
use anyhow::Error;
55
use bigdecimal::BigDecimal;
66
use dydx::node::*;
7-
use rand::{thread_rng, Rng};
7+
use rand::{rng, Rng};
88
use serial_test::serial;
99
use std::str::FromStr;
1010
use tokio::time::{sleep, Duration};
@@ -24,15 +24,15 @@ async fn test_node_auth_list() -> Result<(), Error> {
2424

2525
#[tokio::test]
2626
#[serial]
27-
async fn test_node_auth_add_allof() -> Result<(), Error> {
27+
async fn test_node_auth_add_allof_all_types() -> Result<(), Error> {
2828
let env = TestEnv::testnet().await?;
2929
let mut node = env.node;
3030
let mut account = env.account;
3131
let address = account.address().clone();
3232
let paccount = env.wallet.account_offline(1)?;
3333

3434
let authenticator = Authenticator::AllOf(vec![
35-
Authenticator::SignatureVerification(paccount.public_key().to_bytes()),
35+
Authenticator::SignatureVerification(paccount.public_key().to_bytes().into()),
3636
Authenticator::MessageFilter("dydxprotocol.clob.MsgPlaceOrder".into()),
3737
Authenticator::SubaccountFilter("0".into()),
3838
Authenticator::ClobPairIdFilter("0,1".into()),
@@ -52,6 +52,37 @@ async fn test_node_auth_add_allof() -> Result<(), Error> {
5252
Ok(())
5353
}
5454

55+
#[tokio::test]
56+
#[serial]
57+
async fn test_node_auth_add_allof_nested_msgs() -> Result<(), Error> {
58+
let env = TestEnv::testnet().await?;
59+
let mut node = env.node;
60+
let mut account = env.account;
61+
let address = account.address().clone();
62+
let paccount = env.wallet.account_offline(1)?;
63+
64+
let authenticator = Authenticator::AllOf(vec![
65+
Authenticator::SignatureVerification(paccount.public_key().to_bytes()),
66+
Authenticator::AnyOf(vec![
67+
Authenticator::MessageFilter("/dydxprotocol.clob.MsgPlaceOrder".into()),
68+
Authenticator::MessageFilter("/dydxprotocol.clob.MsgCancelOrder".into()),
69+
]),
70+
]);
71+
node.authenticators()
72+
.add(&mut account, address, authenticator)
73+
.await?;
74+
75+
sleep(Duration::from_secs(3)).await;
76+
77+
let list = node
78+
.authenticators()
79+
.list(account.address().clone())
80+
.await?;
81+
assert!(!list.is_empty());
82+
83+
Ok(())
84+
}
85+
5586
#[tokio::test]
5687
#[serial]
5788
async fn test_node_auth_add_single() -> Result<(), Error> {
@@ -111,7 +142,7 @@ async fn test_node_auth_place_order_short_term() -> Result<(), Error> {
111142
.market(OrderSide::Buy, BigDecimal::from_str("0.001")?)
112143
.price(10) // Low slippage price to not execute
113144
.until(height.ahead(10))
114-
.build(thread_rng().gen_range(0..100_000_000))?;
145+
.build(rng().random_range(0..100_000_000))?;
115146

116147
// Push order by permissioned account
117148
node.place_order(&mut paccount, order).await?;

0 commit comments

Comments
 (0)