From c5c3911de6ad77ed795fe1f6efb79a8e155acbb5 Mon Sep 17 00:00:00 2001 From: silverpill Date: Mon, 29 Aug 2022 19:17:16 +0000 Subject: [PATCH] Require chain ID field in payment options of ethereum type --- ...__actor_profile__clear_payment_options.sql | 9 +++++ src/activitypub/actors/attachments.rs | 7 +++- src/database/migrate.rs | 4 +- src/mastodon_api/accounts/types.rs | 2 +- src/mastodon_api/subscriptions/views.rs | 7 +++- src/models/profiles/types.rs | 37 ++++++++++++++----- src/utils/caip2.rs | 16 +++++++- 7 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 migrations/V0028__actor_profile__clear_payment_options.sql diff --git a/migrations/V0028__actor_profile__clear_payment_options.sql b/migrations/V0028__actor_profile__clear_payment_options.sql new file mode 100644 index 0000000..2ab013a --- /dev/null +++ b/migrations/V0028__actor_profile__clear_payment_options.sql @@ -0,0 +1,9 @@ +UPDATE actor_profile +SET payment_options = ( + -- remove all payment options except links + SELECT COALESCE ( + jsonb_agg(opt) FILTER (WHERE opt ->> 'payment_type' = '1'), + '[]' + ) + FROM jsonb_array_elements(actor_profile.payment_options) AS opt +); diff --git a/src/activitypub/actors/attachments.rs b/src/activitypub/actors/attachments.rs index 9310a44..de1aba1 100644 --- a/src/activitypub/actors/attachments.rs +++ b/src/activitypub/actors/attachments.rs @@ -68,8 +68,9 @@ pub fn attach_payment_option( payment_option: PaymentOption, ) -> ActorAttachment { match payment_option { + // Local actors can't have payment links PaymentOption::Link(_) => unimplemented!(), - PaymentOption::EthereumSubscription => { + PaymentOption::EthereumSubscription(_) => { let name = "EthereumSubscription".to_string(); let subscription_page_url = get_subscription_page_url(instance_url, user_id); @@ -132,6 +133,7 @@ pub fn parse_extra_field( #[cfg(test)] mod tests { + use crate::utils::caip2::ChainId; use crate::utils::id::new_uuid; use super::*; @@ -155,7 +157,8 @@ mod tests { #[test] fn test_payment_option() { let user_id = new_uuid(); - let payment_option = PaymentOption::EthereumSubscription; + let payment_option = + PaymentOption::ethereum_subscription(ChainId::ethereum_mainnet()); let subscription_page_url = format!("https://example.com/profile/{}/subscription", user_id); let attachment = attach_payment_option( diff --git a/src/database/migrate.rs b/src/database/migrate.rs index 17175eb..f66796c 100644 --- a/src/database/migrate.rs +++ b/src/database/migrate.rs @@ -12,9 +12,9 @@ pub async fn apply_migrations(db_client: &mut Client) { for migration in migration_report.applied_migrations() { log::info!( - "Migration Applied - Name: {}, Version: {}", - migration.name(), + "migration applied: version {} ({})", migration.version(), + migration.name(), ); } } diff --git a/src/mastodon_api/accounts/types.rs b/src/mastodon_api/accounts/types.rs index cb20635..2e75070 100644 --- a/src/mastodon_api/accounts/types.rs +++ b/src/mastodon_api/accounts/types.rs @@ -98,7 +98,7 @@ impl Account { .map(|option| { match option { PaymentOption::Link(link) => link.href, - PaymentOption::EthereumSubscription => { + PaymentOption::EthereumSubscription(_) => { get_subscription_page_url(instance_url, &profile.id) }, } diff --git a/src/mastodon_api/subscriptions/views.rs b/src/mastodon_api/subscriptions/views.rs index eecf030..49314a6 100644 --- a/src/mastodon_api/subscriptions/views.rs +++ b/src/mastodon_api/subscriptions/views.rs @@ -63,6 +63,9 @@ pub async fn subscriptions_enabled( let mut maybe_payment_option = None; match subscription_settings.into_inner() { SubscriptionSettings::Ethereum => { + let ethereum_config = config.blockchain.as_ref() + .and_then(|conf| conf.ethereum_config()) + .ok_or(HttpError::NotSupported)?; let contract_set = maybe_blockchain.as_ref().as_ref() .ok_or(HttpError::NotSupported)?; let wallet_address = current_user @@ -78,7 +81,9 @@ pub async fn subscriptions_enabled( if !is_registered { return Err(ValidationError("recipient is not registered").into()); }; - maybe_payment_option = Some(PaymentOption::EthereumSubscription); + maybe_payment_option = Some(PaymentOption::ethereum_subscription( + ethereum_config.chain_id.clone(), + )); }; }, SubscriptionSettings::Monero { } => { diff --git a/src/models/profiles/types.rs b/src/models/profiles/types.rs index 0927c22..e06a586 100644 --- a/src/models/profiles/types.rs +++ b/src/models/profiles/types.rs @@ -15,6 +15,7 @@ use crate::activitypub::identifiers::local_actor_id; use crate::database::json_macro::{json_from_sql, json_to_sql}; use crate::errors::{ConversionError, ValidationError}; use crate::ethereum::identity::DidPkh; +use crate::utils::caip2::ChainId; use super::validators::{ validate_username, validate_display_name, @@ -76,17 +77,26 @@ pub struct PaymentLink { pub href: String, } +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct EthereumSubscription { + chain_id: ChainId, +} + #[derive(Clone, Debug)] pub enum PaymentOption { Link(PaymentLink), - EthereumSubscription, + EthereumSubscription(EthereumSubscription), } impl PaymentOption { + pub fn ethereum_subscription(chain_id: ChainId) -> Self { + Self::EthereumSubscription(EthereumSubscription { chain_id }) + } + fn payment_type(&self) -> PaymentType { match self { Self::Link(_) => PaymentType::Link, - Self::EthereumSubscription => PaymentType::EthereumSubscription, + Self::EthereumSubscription(_) => PaymentType::EthereumSubscription, } } } @@ -109,7 +119,11 @@ impl<'de> Deserialize<'de> for PaymentOption { .map_err(DeserializerError::custom)?; Self::Link(link) }, - PaymentType::EthereumSubscription => Self::EthereumSubscription, + PaymentType::EthereumSubscription => { + let payment_info = EthereumSubscription::deserialize(value) + .map_err(DeserializerError::custom)?; + Self::EthereumSubscription(payment_info) + }, }; Ok(payment_option) } @@ -125,7 +139,9 @@ impl Serialize for PaymentOption { match self { Self::Link(link) => link.serialize(FlatMapSerializer(&mut map))?, - Self::EthereumSubscription => (), + Self::EthereumSubscription(payment_info) => { + payment_info.serialize(FlatMapSerializer(&mut map))? + }, }; map.end() } @@ -384,14 +400,15 @@ mod tests { #[test] fn test_payment_option_ethereum_subscription_serialization() { - let json_data = r#"{"payment_type":2,"name":null,"href":null}"#; + let json_data = r#"{"payment_type":2,"chain_id":"eip155:1","name":null}"#; let payment_option: PaymentOption = serde_json::from_str(json_data).unwrap(); - assert!(matches!( - payment_option, - PaymentOption::EthereumSubscription, - )); + let payment_info = match payment_option { + PaymentOption::EthereumSubscription(ref payment_info) => payment_info, + _ => panic!("wrong option"), + }; + assert_eq!(payment_info.chain_id, ChainId::ethereum_mainnet()); let serialized = serde_json::to_string(&payment_option).unwrap(); - assert_eq!(serialized, r#"{"payment_type":2}"#); + assert_eq!(serialized, r#"{"payment_type":2,"chain_id":"eip155:1"}"#); } #[test] diff --git a/src/utils/caip2.rs b/src/utils/caip2.rs index 756f69c..8c27d16 100644 --- a/src/utils/caip2.rs +++ b/src/utils/caip2.rs @@ -2,7 +2,13 @@ use std::str::FromStr; use regex::Regex; -use serde::{Deserialize, Deserializer, de::Error as DeserializerError}; +use serde::{ + Deserialize, + Deserializer, + Serialize, + Serializer, + de::Error as DeserializerError, +}; const CAIP2_RE: &str = r"(?P[-a-z0-9]{3,8}):(?P[-a-zA-Z0-9]{1,32})"; const CAIP2_ETHEREUM_NAMESPACE: &str = "eip155"; @@ -47,6 +53,14 @@ impl ToString for ChainId { } } +impl Serialize for ChainId { + fn serialize(&self, serializer: S) -> Result + where S: Serializer + { + serializer.serialize_str(&self.to_string()) + } +} + impl<'de> Deserialize<'de> for ChainId { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>