use crate::{BundleSubmitter, FillSubmitter, OrdersAndFills, TxBuilder};
use alloy::primitives::Address;
use alloy::{
eips::eip2718::Encodable2718,
network::{Ethereum, Network, TransactionBuilder},
primitives::Bytes,
providers::{fillers::FillerControlFlow, SendableTx},
rpc::types::mev::EthSendBundle,
transports::{RpcError, TransportErrorKind},
};
use futures_util::{future::try_join_all, stream, StreamExt, TryStreamExt};
use signet_bundle::SignetEthBundle;
use signet_constants::SignetSystemConstants;
#[cfg(doc)]
use signet_types::SignedFill;
use tracing::{debug, error, instrument};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum FeePolicyError {
#[error("no fills provided for submission")]
NoFills,
#[error("RPC error: {0}")]
Rpc(#[source] RpcError<TransportErrorKind>),
#[error("transaction missing required properties: {0:?}")]
IncompleteTransaction(Vec<(&'static str, Vec<&'static str>)>),
#[error("failed to submit bundle: {0}")]
Submission(#[source] Box<dyn core::error::Error + Send + Sync>),
}
impl From<FillerControlFlow> for FeePolicyError {
fn from(filler_control_flow: FillerControlFlow) -> Self {
match filler_control_flow {
FillerControlFlow::Missing(missing) => Self::IncompleteTransaction(missing),
FillerControlFlow::Finished | FillerControlFlow::Ready => {
error!("fill returned Builder but status is {filler_control_flow:?}");
Self::IncompleteTransaction(Vec::new())
}
}
}
}
#[derive(Debug, Clone)]
pub struct FeePolicySubmitter<RuP, HostP, B> {
ru_provider: RuP,
host_provider: HostP,
submitter: B,
constants: SignetSystemConstants,
}
impl<RuP, HostP, B> FeePolicySubmitter<RuP, HostP, B> {
pub const fn new(
ru_provider: RuP,
host_provider: HostP,
submitter: B,
constants: SignetSystemConstants,
) -> Self {
Self { ru_provider, host_provider, submitter, constants }
}
pub const fn ru_provider(&self) -> &RuP {
&self.ru_provider
}
pub const fn host_provider(&self) -> &HostP {
&self.host_provider
}
pub const fn submitter(&self) -> &B {
&self.submitter
}
pub const fn constants(&self) -> &SignetSystemConstants {
&self.constants
}
}
impl<RuP, HostP, B> FillSubmitter for FeePolicySubmitter<RuP, HostP, B>
where
RuP: TxBuilder<Ethereum>,
HostP: TxBuilder<Ethereum>,
B: BundleSubmitter + Send + Sync,
B::Response: Send,
{
type Response = Vec<B::Response>;
type Error = FeePolicyError;
#[instrument(
skip_all,
fields(order_count = orders.len(), fill_count = fills.len(), target_block_count)
)]
async fn submit_fills(
&self,
OrdersAndFills { orders, fills, signer_address }: OrdersAndFills,
target_block_count: u8,
) -> Result<Self::Response, Self::Error> {
if fills.is_empty() {
return Err(FeePolicyError::NoFills);
}
let fill_iter = fills
.get(&self.constants.ru_chain_id())
.map(|fill| fill.to_fill_tx(self.constants.ru_orders()))
.into_iter();
let order_iter = orders
.iter()
.map(|order| order.to_initiate_tx(signer_address, self.constants.ru_orders()));
let rollup_txs: Vec<Bytes> = stream::iter(fill_iter.chain(order_iter))
.then(|tx_request| sign_and_encode_tx(&self.ru_provider, tx_request, signer_address))
.try_collect()
.await?;
let host_txs = match fills.get(&self.constants.host_chain_id()) {
Some(fill) => {
let tx_request = fill.to_fill_tx(self.constants.host_orders());
vec![sign_and_encode_tx(&self.host_provider, tx_request, signer_address).await?]
}
None => vec![],
};
let target_block =
self.ru_provider.get_block_number().await.map_err(FeePolicyError::Rpc)? + 1;
let base_bundle = SignetEthBundle::new(
EthSendBundle { txs: rollup_txs, block_number: target_block, ..Default::default() },
host_txs,
);
let targets = target_block..target_block + u64::from(target_block_count);
debug!(target_blocks = %format!("[{targets:?})"), "submitting fills");
try_join_all(targets.map(|target| {
let mut bundle = base_bundle.clone();
bundle.bundle.block_number = target;
self.submitter.submit_bundle(bundle)
}))
.await
.map_err(|error| FeePolicyError::Submission(Box::new(error)))
}
}
#[instrument(skip_all)]
async fn sign_and_encode_tx<N, P>(
provider: &P,
mut tx_request: N::TransactionRequest,
signer_address: Address,
) -> Result<Bytes, FeePolicyError>
where
N: Network,
P: TxBuilder<N>,
N::TxEnvelope: Encodable2718,
{
tx_request = tx_request.with_from(signer_address);
let sendable = provider.fill(tx_request).await.map_err(FeePolicyError::Rpc)?;
let envelope = match sendable {
SendableTx::Envelope(envelope) => envelope,
SendableTx::Builder(tx) => {
return Err(FeePolicyError::from(provider.status(&tx)));
}
};
Ok(Bytes::from(envelope.encoded_2718()))
}