Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Updated typed_data following starknet.py bump #88

Merged
merged 13 commits into from
Jul 27, 2024
8 changes: 4 additions & 4 deletions pragma-entities/src/models/entries/entry_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ pub enum EntryError {
InvalidSignature(EcdsaVerifyError),
#[error("could not sign price")]
InvalidSigner,
#[error("unauthorized request")]
Unauthorized,
#[error("unauthorized request: {0}")]
Unauthorized(String),
#[error("invalid timestamp")]
InvalidTimestamp,
#[error("invalid expiry")]
Expand Down Expand Up @@ -72,9 +72,9 @@ impl IntoResponse for EntryError {
StatusCode::BAD_REQUEST,
format!("Invalid signature: {}", err),
),
Self::Unauthorized => (
Self::Unauthorized(reason) => (
StatusCode::UNAUTHORIZED,
"Unauthorized publisher".to_string(),
format!("Unauthorized publisher: {}", reason),
),
Self::InvalidTimestamp => (StatusCode::BAD_REQUEST, "Invalid timestamp".to_string()),
Self::InvalidExpiry => (StatusCode::BAD_REQUEST, "Invalid expiry".to_string()),
Expand Down
29 changes: 10 additions & 19 deletions pragma-node/src/handlers/entries/create_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ use axum::extract::State;
use axum::Json;
use chrono::{DateTime, Utc};
use pragma_entities::{EntryError, NewEntry, PublisherError};
use starknet::core::crypto::{ecdsa_verify, Signature};
use starknet::core::types::FieldElement;

use crate::config::config;
use crate::handlers::entries::{CreateEntryRequest, CreateEntryResponse};
use crate::infra::kafka;
use crate::infra::repositories::publisher_repository;
use crate::types::entries::build_publish_message;
use crate::utils::JsonExtractor;
use crate::types::entries::Entry;
use crate::utils::{assert_request_signature_is_valid, JsonExtractor};
use crate::AppState;

#[utoipa::path(
Expand Down Expand Up @@ -72,19 +71,11 @@ pub async fn create_entries(
account_address
);

let published_message = build_publish_message(&new_entries.entries)?;
let message_hash = published_message.message_hash(account_address);

let signature = Signature {
r: new_entries.signature[0],
s: new_entries.signature[1],
};
if !ecdsa_verify(&public_key, &message_hash, &signature)
.map_err(EntryError::InvalidSignature)?
{
tracing::error!("Invalid signature for message hash {:?}", &message_hash);
return Err(EntryError::Unauthorized);
}
let signature = assert_request_signature_is_valid::<CreateEntryRequest, Entry>(
&new_entries,
&account_address,
&public_key,
)?;

let new_entries_db = new_entries
.entries
Expand Down Expand Up @@ -123,15 +114,15 @@ pub async fn create_entries(

#[cfg(test)]
mod tests {
use crate::types::entries::{BaseEntry, Entry};
use crate::types::entries::{build_publish_message, BaseEntry, Entry};

use super::*;
use rstest::rstest;

#[rstest]
fn test_build_publish_message_empty() {
let entries: Vec<Entry> = vec![];
let typed_data = build_publish_message(&entries).unwrap();
let typed_data = build_publish_message(&entries, None).unwrap();

assert_eq!(typed_data.primary_type, "Request");
assert_eq!(typed_data.domain.name, "Pragma");
Expand All @@ -153,7 +144,7 @@ mod tests {
price: 0,
volume: 0,
}];
let typed_data = build_publish_message(&entries).unwrap();
let typed_data = build_publish_message(&entries, None).unwrap();

assert_eq!(typed_data.primary_type, "Request");
assert_eq!(typed_data.domain.name, "Pragma");
Expand Down
29 changes: 10 additions & 19 deletions pragma-node/src/handlers/entries/create_future_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ use axum::extract::State;
use axum::Json;
use chrono::{DateTime, Utc};
use pragma_entities::{EntryError, NewFutureEntry, PublisherError};
use starknet::core::crypto::{ecdsa_verify, Signature};
use starknet::core::types::FieldElement;

use super::{CreateFutureEntryRequest, CreateFutureEntryResponse};
use crate::types::entries::build_publish_message;

use crate::config::config;
use crate::infra::kafka;
use crate::infra::repositories::publisher_repository;
use crate::utils::JsonExtractor;
use crate::types::entries::FutureEntry;
use crate::utils::{assert_request_signature_is_valid, JsonExtractor};
use crate::AppState;

#[utoipa::path(
Expand Down Expand Up @@ -73,18 +72,11 @@ pub async fn create_future_entries(
account_address
);

let message_hash = build_publish_message(&new_entries.entries)?.message_hash(account_address);
let signature = Signature {
r: new_entries.signature[0],
s: new_entries.signature[1],
};

if !ecdsa_verify(&public_key, &message_hash, &signature)
.map_err(EntryError::InvalidSignature)?
{
tracing::error!("Invalid signature for message hash {:?}", &message_hash);
return Err(EntryError::Unauthorized);
}
let signature = assert_request_signature_is_valid::<CreateFutureEntryRequest, FutureEntry>(
&new_entries,
&account_address,
&public_key,
)?;

let new_entries_db = new_entries
.entries
Expand Down Expand Up @@ -137,23 +129,22 @@ pub async fn create_future_entries(

#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;

use crate::types::entries::{FutureEntry, PerpEntry};
use crate::types::entries::{build_publish_message, FutureEntry, PerpEntry};

#[rstest]
fn test_build_publish_message_empty() {
let entries: Vec<PerpEntry> = vec![];
let typed_data = build_publish_message(&entries).unwrap();
let typed_data = build_publish_message(&entries, None).unwrap();
assert_eq!(typed_data.primary_type, "Request");
assert_eq!(typed_data.domain.name, "Pragma");
assert_eq!(typed_data.domain.version, "1");
assert_eq!(typed_data.message.action, "Publish");
assert_eq!(typed_data.message.entries, entries);

let entries: Vec<FutureEntry> = vec![];
let typed_data = build_publish_message(&entries).unwrap();
let typed_data = build_publish_message(&entries, None).unwrap();
assert_eq!(typed_data.primary_type, "Request");
assert_eq!(typed_data.domain.name, "Pragma");
assert_eq!(typed_data.domain.version, "1");
Expand Down
32 changes: 28 additions & 4 deletions pragma-node/src/handlers/entries/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,20 @@ use crate::{

#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct CreateEntryRequest {
signature: Vec<FieldElement>,
entries: Vec<Entry>,
pub signature: Vec<FieldElement>,
pub entries: Vec<Entry>,
}

impl AsRef<[FieldElement]> for CreateEntryRequest {
fn as_ref(&self) -> &[FieldElement] {
&self.signature
}
}

impl AsRef<[Entry]> for CreateEntryRequest {
fn as_ref(&self) -> &[Entry] {
&self.entries
}
}

#[derive(Debug, Serialize, Deserialize, ToSchema)]
Expand All @@ -42,8 +54,20 @@ pub struct CreateEntryResponse {

#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct CreateFutureEntryRequest {
signature: Vec<FieldElement>,
entries: Vec<FutureEntry>,
pub signature: Vec<FieldElement>,
pub entries: Vec<FutureEntry>,
}

impl AsRef<[FieldElement]> for CreateFutureEntryRequest {
fn as_ref(&self) -> &[FieldElement] {
&self.signature
}
}

impl AsRef<[FutureEntry]> for CreateFutureEntryRequest {
fn as_ref(&self) -> &[FutureEntry] {
&self.entries
}
}

#[derive(Debug, Serialize, Deserialize, ToSchema)]
Expand Down
5 changes: 2 additions & 3 deletions pragma-node/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,14 @@ async fn main() {

// Create the Metrics registry
let metrics_registry = MetricsRegistry::new();
let ws_metrics =
Arc::new(WsMetrics::new(&metrics_registry).expect("Failed to create WsMetrics"));
let ws_metrics = WsMetrics::new(&metrics_registry).expect("Failed to create WsMetrics");

let state = AppState {
offchain_pool,
onchain_pool,
publishers_updates_cache,
pragma_signer,
ws_metrics,
ws_metrics: Arc::new(ws_metrics),
};

tokio::join!(
Expand Down
116 changes: 83 additions & 33 deletions pragma-node/src/types/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,17 @@ pub struct PublishMessage<E: EntryTrait + Serialize> {
pub entries: Vec<E>,
}

pub fn build_publish_message<E>(entries: &[E]) -> Result<TypedData<PublishMessage<E>>, EntryError>
// TODO: Remove this legacy handling while every publishers are on the 2.0 version.
pub fn build_publish_message<E>(
entries: &[E],
is_legacy: Option<bool>,
) -> Result<TypedData<PublishMessage<E>>, EntryError>
where
E: EntryTrait + Serialize + for<'a> Deserialize<'a>,
{
// TODO(akhercha): ugly, refine
let mut is_future = false;
let is_legacy = is_legacy.unwrap_or(false);

// Construct the raw string with placeholders for the entries
let raw_entries: Vec<_> = entries
Expand Down Expand Up @@ -153,38 +158,83 @@ where
})
.collect::<Vec<_>>();

let mut raw_message_json = serde_json::json!({
"domain": {
"name": "Pragma",
"version": "1"
},
"primaryType": "Request",
"message": {
"action": "Publish",
"entries": raw_entries
},
"types": {
"StarkNetDomain": [
{"name": "name", "type": "felt"},
{"name": "version", "type": "felt"}
],
"Request": [
{"name": "action", "type": "felt"},
{"name": "entries", "type": "Entry*"}
],
"Entry": [
{"name": "base", "type": "Base"},
{"name": "pair_id", "type": "felt"},
{"name": "price", "type": "felt"},
{"name": "volume", "type": "felt"},
],
"Base": [
{"name": "publisher", "type": "felt"},
{"name": "source", "type": "felt"},
{"name": "timestamp", "type": "felt"}
]
}
});
// We recently updated our Pragma-SDK. This included a breaking change for how we
// sign the entries before publishing them.
// We want to support our publishers who are still on the older version and
// encourage them to upgrade before removing this legacy code. Until then,
// we support both methods.
// TODO: Remove this legacy handling while every publishers are on the 2.0 version.
let mut raw_message_json = if is_legacy {
serde_json::json!({
"domain": {
"name": "Pragma",
"version": "1"
},
"primaryType": "Request",
"message": {
"action": "Publish",
"entries": raw_entries
},
"types": {
"StarkNetDomain": [
{"name": "name", "type": "felt"},
{"name": "version", "type": "felt"}
],
"Request": [
{"name": "action", "type": "felt"},
{"name": "entries", "type": "Entry*"}
],
"Entry": [
{"name": "base", "type": "Base"},
{"name": "pair_id", "type": "felt"},
{"name": "price", "type": "felt"},
{"name": "volume", "type": "felt"},
],
"Base": [
{"name": "publisher", "type": "felt"},
{"name": "source", "type": "felt"},
{"name": "timestamp", "type": "felt"}
]
}
})
} else {
serde_json::json!({
"domain": {
"name": "Pragma",
"version": "1",
"chainId": "1",
"revision": "0"
},
"primaryType": "Request",
"message": {
"action": "Publish",
"entries": raw_entries
},
"types": {
"StarkNetDomain": [
{"name": "name", "type": "felt"},
{"name": "version", "type": "felt"},
{"name": "chainId", "type": "felt"},
{"name": "revision", "type": "felt"}
],
"Request": [
{"name": "action", "type": "felt"},
{"name": "entries", "type": "Entry*"}
],
"Entry": [
{"name": "base", "type": "Base"},
{"name": "pair_id", "type": "felt"},
{"name": "price", "type": "felt"},
{"name": "volume", "type": "felt"},
],
"Base": [
{"name": "publisher", "type": "felt"},
{"name": "source", "type": "felt"},
{"name": "timestamp", "type": "felt"}
]
}
})
};

// Add the expiration timestamp for the future entries
if is_future {
Expand Down
2 changes: 1 addition & 1 deletion pragma-node/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ pub use aws::PragmaSignerBuilder;
pub use conversion::{convert_via_quote, format_bigdecimal_price, normalize_to_decimals};
pub use custom_extractors::json_extractor::JsonExtractor;
pub use custom_extractors::path_extractor::PathExtractor;
pub use signing::sign_data;
pub use signing::starkex::StarkexPrice;
pub use signing::typed_data::TypedData;
pub use signing::{assert_request_signature_is_valid, sign_data};

use bigdecimal::num_bigint::ToBigInt;
use bigdecimal::{BigDecimal, ToPrimitive};
Expand Down
6 changes: 4 additions & 2 deletions pragma-node/src/utils/signing/mock/typed_data_example.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"StarkNetDomain": [
{ "name": "name", "type": "felt" },
{ "name": "version", "type": "felt" },
{ "name": "chainId", "type": "felt" }
{ "name": "chainId", "type": "felt" },
{ "name": "revision", "type": "felt" }
],
"Person": [
{ "name": "name", "type": "felt" },
Expand All @@ -19,7 +20,8 @@
"domain": {
"name": "StarkNet Mail",
"version": "1",
"chainId": 1
"chainId": "1",
"revision": "0"
},
"message": {
"from": {
Expand Down
Loading
Loading