Require chain ID field in payment options of ethereum type

This commit is contained in:
silverpill 2022-08-29 19:17:16 +00:00
parent 690a03946e
commit c5c3911de6
7 changed files with 65 additions and 17 deletions

View file

@ -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
);

View file

@ -68,8 +68,9 @@ pub fn attach_payment_option(
payment_option: PaymentOption, payment_option: PaymentOption,
) -> ActorAttachment { ) -> ActorAttachment {
match payment_option { match payment_option {
// Local actors can't have payment links
PaymentOption::Link(_) => unimplemented!(), PaymentOption::Link(_) => unimplemented!(),
PaymentOption::EthereumSubscription => { PaymentOption::EthereumSubscription(_) => {
let name = "EthereumSubscription".to_string(); let name = "EthereumSubscription".to_string();
let subscription_page_url = let subscription_page_url =
get_subscription_page_url(instance_url, user_id); get_subscription_page_url(instance_url, user_id);
@ -132,6 +133,7 @@ pub fn parse_extra_field(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::utils::caip2::ChainId;
use crate::utils::id::new_uuid; use crate::utils::id::new_uuid;
use super::*; use super::*;
@ -155,7 +157,8 @@ mod tests {
#[test] #[test]
fn test_payment_option() { fn test_payment_option() {
let user_id = new_uuid(); let user_id = new_uuid();
let payment_option = PaymentOption::EthereumSubscription; let payment_option =
PaymentOption::ethereum_subscription(ChainId::ethereum_mainnet());
let subscription_page_url = let subscription_page_url =
format!("https://example.com/profile/{}/subscription", user_id); format!("https://example.com/profile/{}/subscription", user_id);
let attachment = attach_payment_option( let attachment = attach_payment_option(

View file

@ -12,9 +12,9 @@ pub async fn apply_migrations(db_client: &mut Client) {
for migration in migration_report.applied_migrations() { for migration in migration_report.applied_migrations() {
log::info!( log::info!(
"Migration Applied - Name: {}, Version: {}", "migration applied: version {} ({})",
migration.name(),
migration.version(), migration.version(),
migration.name(),
); );
} }
} }

View file

@ -98,7 +98,7 @@ impl Account {
.map(|option| { .map(|option| {
match option { match option {
PaymentOption::Link(link) => link.href, PaymentOption::Link(link) => link.href,
PaymentOption::EthereumSubscription => { PaymentOption::EthereumSubscription(_) => {
get_subscription_page_url(instance_url, &profile.id) get_subscription_page_url(instance_url, &profile.id)
}, },
} }

View file

@ -63,6 +63,9 @@ pub async fn subscriptions_enabled(
let mut maybe_payment_option = None; let mut maybe_payment_option = None;
match subscription_settings.into_inner() { match subscription_settings.into_inner() {
SubscriptionSettings::Ethereum => { 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() let contract_set = maybe_blockchain.as_ref().as_ref()
.ok_or(HttpError::NotSupported)?; .ok_or(HttpError::NotSupported)?;
let wallet_address = current_user let wallet_address = current_user
@ -78,7 +81,9 @@ pub async fn subscriptions_enabled(
if !is_registered { if !is_registered {
return Err(ValidationError("recipient is not registered").into()); 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 { } => { SubscriptionSettings::Monero { } => {

View file

@ -15,6 +15,7 @@ use crate::activitypub::identifiers::local_actor_id;
use crate::database::json_macro::{json_from_sql, json_to_sql}; use crate::database::json_macro::{json_from_sql, json_to_sql};
use crate::errors::{ConversionError, ValidationError}; use crate::errors::{ConversionError, ValidationError};
use crate::ethereum::identity::DidPkh; use crate::ethereum::identity::DidPkh;
use crate::utils::caip2::ChainId;
use super::validators::{ use super::validators::{
validate_username, validate_username,
validate_display_name, validate_display_name,
@ -76,17 +77,26 @@ pub struct PaymentLink {
pub href: String, pub href: String,
} }
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EthereumSubscription {
chain_id: ChainId,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum PaymentOption { pub enum PaymentOption {
Link(PaymentLink), Link(PaymentLink),
EthereumSubscription, EthereumSubscription(EthereumSubscription),
} }
impl PaymentOption { impl PaymentOption {
pub fn ethereum_subscription(chain_id: ChainId) -> Self {
Self::EthereumSubscription(EthereumSubscription { chain_id })
}
fn payment_type(&self) -> PaymentType { fn payment_type(&self) -> PaymentType {
match self { match self {
Self::Link(_) => PaymentType::Link, 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)?; .map_err(DeserializerError::custom)?;
Self::Link(link) 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) Ok(payment_option)
} }
@ -125,7 +139,9 @@ impl Serialize for PaymentOption {
match self { match self {
Self::Link(link) => link.serialize(FlatMapSerializer(&mut map))?, Self::Link(link) => link.serialize(FlatMapSerializer(&mut map))?,
Self::EthereumSubscription => (), Self::EthereumSubscription(payment_info) => {
payment_info.serialize(FlatMapSerializer(&mut map))?
},
}; };
map.end() map.end()
} }
@ -384,14 +400,15 @@ mod tests {
#[test] #[test]
fn test_payment_option_ethereum_subscription_serialization() { 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(); let payment_option: PaymentOption = serde_json::from_str(json_data).unwrap();
assert!(matches!( let payment_info = match payment_option {
payment_option, PaymentOption::EthereumSubscription(ref payment_info) => payment_info,
PaymentOption::EthereumSubscription, _ => panic!("wrong option"),
)); };
assert_eq!(payment_info.chain_id, ChainId::ethereum_mainnet());
let serialized = serde_json::to_string(&payment_option).unwrap(); 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] #[test]

View file

@ -2,7 +2,13 @@
use std::str::FromStr; use std::str::FromStr;
use regex::Regex; 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<namespace>[-a-z0-9]{3,8}):(?P<reference>[-a-zA-Z0-9]{1,32})"; const CAIP2_RE: &str = r"(?P<namespace>[-a-z0-9]{3,8}):(?P<reference>[-a-zA-Z0-9]{1,32})";
const CAIP2_ETHEREUM_NAMESPACE: &str = "eip155"; const CAIP2_ETHEREUM_NAMESPACE: &str = "eip155";
@ -47,6 +53,14 @@ impl ToString for ChainId {
} }
} }
impl Serialize for ChainId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for ChainId { impl<'de> Deserialize<'de> for ChainId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de> where D: Deserializer<'de>