Skip to content

Commit

Permalink
Implement is_authorized_to_create_premint rule (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
ligustah authored Apr 6, 2024
1 parent cc1bbcc commit 34fba54
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 29 deletions.
35 changes: 31 additions & 4 deletions src/chain.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,38 @@
use crate::chain_list::{Chains, CHAINS};
use crate::controller::ControllerCommands;
use crate::types::Premint;
use alloy::network::Ethereum;
use alloy::rpc::types::eth::{TransactionInput, TransactionRequest};
use alloy_primitives::Bytes;
use alloy_provider::Provider;
use alloy_sol_types::SolCall;
use futures_util::StreamExt;
use tokio::sync::mpsc::Sender;

use crate::chain_list::{ChainListProvider, CHAINS};
use crate::controller::ControllerCommands;
use crate::premints::zora_premint_v2::types::PREMINT_FACTORY_ADDR;
use crate::types::Premint;

pub async fn contract_call<T, P>(call: T, provider: P) -> eyre::Result<T::Return>
where
T: SolCall,
P: Provider<Ethereum>,
{
provider
.call(
&TransactionRequest {
to: Some(PREMINT_FACTORY_ADDR),
input: TransactionInput::new(Bytes::from(call.abi_encode())),
..Default::default()
},
None,
)
.await
.map_err(|err| eyre::eyre!("Error calling contract: {:?}", err))
.and_then(|response| {
T::abi_decode_returns(&**response, false)
.map_err(|err| eyre::eyre!("Error decoding contract response: {:?}", err))
})
}

/// Checks for new premints being brought onchain then sends to controller to handle
struct MintChecker {
chain_id: u64,
Expand Down Expand Up @@ -55,7 +82,7 @@ impl MintChecker {
}
}

async fn make_provider(&self) -> eyre::Result<Box<dyn Provider<Ethereum>>> {
async fn make_provider(&self) -> eyre::Result<ChainListProvider> {
let chain = CHAINS.get_chain_by_id(self.chain_id as i64);

match chain {
Expand Down
34 changes: 26 additions & 8 deletions src/chain_list.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use alloy::network::Ethereum;
use alloy_provider::{Provider, ProviderBuilder};
use std::marker::PhantomData;

use alloy::network::{Ethereum, Network};
use alloy_provider::layers::{GasEstimatorProvider, ManagedNonceProvider};
use alloy_provider::{Provider, ProviderBuilder, RootProvider};
use alloy_transport::BoxTransport;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -52,19 +56,31 @@ pub struct Chain {
pub parent: Option<Parent>,
}

async fn connect(url: &String) -> eyre::Result<Box<dyn Provider<Ethereum>>> {
pub type ChainListProvider<N = Ethereum> = GasEstimatorProvider<
N,
BoxTransport,
ManagedNonceProvider<N, BoxTransport, RootProvider<N, BoxTransport>>,
>;

async fn connect<N>(url: &String) -> eyre::Result<ChainListProvider<N>>
where
N: Network,
{
if VARIABLE_REGEX.is_match(url) {
return Err(eyre::eyre!("URL contains variables"));
}

let builder = ProviderBuilder::<_, Ethereum>::default().with_recommended_layers();
let builder = ProviderBuilder::<_, N>::default().with_recommended_layers();
let provider = builder.on_builtin(url).await?;

Ok(Box::new(provider))
Ok(provider)
}

impl Chain {
pub async fn get_rpc(&self, need_pub_sub: bool) -> eyre::Result<Box<dyn Provider<Ethereum>>> {
pub async fn get_rpc<N>(&self, need_pub_sub: bool) -> eyre::Result<ChainListProvider<N>>
where
N: Network,
{
for rpc in self.rpc.iter() {
if need_pub_sub && !rpc.starts_with("ws") {
continue;
Expand Down Expand Up @@ -128,6 +144,8 @@ pub struct Bridge {

#[cfg(test)]
mod test {
use alloy::network::Ethereum;

use super::*;

#[test]
Expand All @@ -145,7 +163,7 @@ mod test {
#[tokio::test]
async fn test_chain_connect() {
let chain = CHAINS.get_chain_by_id(7777777).unwrap();
let provider = connect(&chain.rpc[0]).await.unwrap();
let provider = connect::<Ethereum>(&chain.rpc[0]).await.unwrap();

// quick integration test here
let number = provider.get_block_number().await.unwrap();
Expand All @@ -155,7 +173,7 @@ mod test {
#[tokio::test]
async fn test_chain_connect_variable() {
let url = "https://mainnet.infura.io/v3/${INFURA_API_KEY}".to_string();
let provider = connect(&url).await;
let provider = connect::<Ethereum>(&url).await;

assert!(provider.is_err());
match provider {
Expand Down
10 changes: 1 addition & 9 deletions src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,7 @@ impl Controller {
}

async fn validate_and_insert(&self, premint: PremintTypes) -> eyre::Result<()> {
let evaluation = self
.rules
.evaluate(
premint.clone(),
RuleContext {
store: self.store.clone(),
},
)
.await;
let evaluation = self.rules.evaluate(premint.clone(), RuleContext {}).await;

if evaluation.is_accept() {
self.store.store(premint).await
Expand Down
49 changes: 44 additions & 5 deletions src/premints/zora_premint_v2/rules.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,54 @@
use std::str::FromStr;

use alloy_primitives::Signature;
use alloy_sol_types::SolStruct;
use alloy_provider::Provider;
use alloy_sol_macro::sol;
use alloy_sol_types::{SolCall, SolInterface, SolStruct};

use crate::chain::contract_call;
use crate::chain_list::CHAINS;
use crate::premints::zora_premint_v2::types::ZoraPremintV2;
use crate::rules::Evaluation::{Accept, Reject};
use crate::rules::{Evaluation, Rule, RuleContext};
use crate::typed_rule;
use crate::types::{Premint, PremintTypes};

sol! {
contract PremintExecutor {
function isAuthorizedToCreatePremint(
address signer,
address premintContractConfigContractAdmin,
address contractAddress
) external view returns (bool isAuthorized);
}
}

// create premint v2 rule implementations here

pub async fn is_authorized_to_create_premint(
premint: ZoraPremintV2,
context: RuleContext,
) -> eyre::Result<Evaluation> {
// * if contract exists, check if the signer is the contract admin
// * if contract does not exist, check if the signer is the proposed contract admin
// * this logic exists as a function on the premint executor contract
Ok(Accept)
let call = PremintExecutor::isAuthorizedToCreatePremintCall {
contractAddress: premint.collection_address,
signer: premint.collection.contractAdmin,
premintContractConfigContractAdmin: premint.collection.contractAdmin,
};

let chain = CHAINS.get_chain_by_id(premint.chain_id.to());

match chain {
Some(chain) => {
let provider = chain.get_rpc(false).await?;
let result = contract_call(call, &provider).await?;

match result.isAuthorized {
true => Ok(Accept),
false => Ok(Reject("Unauthorized to create premint".to_string())),
}
}
None => Ok(Reject("Chain not supported".to_string())),
}
}

// * signatureIsValid ( this can be performed entirely offline )
Expand Down Expand Up @@ -101,4 +131,13 @@ mod test {
Ok(Accept)
));
}

#[tokio::test]
async fn test_is_authorized_to_create_premint() {
let premint: ZoraPremintV2 = serde_json::from_str(PREMINT_JSON).unwrap();
assert!(matches!(
is_authorized_to_create_premint(premint, RuleContext {}).await,
Ok(Accept)
));
}
}
4 changes: 1 addition & 3 deletions src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ impl Results {
}

#[derive(Clone)]
pub struct RuleContext {
pub store: PremintStorage,
}
pub struct RuleContext {}

#[async_trait]
pub trait Rule: Send + Sync {
Expand Down

0 comments on commit 34fba54

Please sign in to comment.