Skip to content

Commit

Permalink
auth-server: handle_external_match: mutate calldata for gas sponsorsh…
Browse files Browse the repository at this point in the history
…ip (#96)
  • Loading branch information
akirillo authored Feb 3, 2025
1 parent 3a1016c commit 83e5bcb
Show file tree
Hide file tree
Showing 11 changed files with 335 additions and 24 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ target/

.vscode/
/.gitattributes

.env
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions auth/auth-server-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,12 @@ pub struct CreateApiKeyRequest {
/// A description of the API key's purpose
pub description: String,
}

/// The query parameters accepted by the external quote assembly endpoint
#[derive(Debug, Serialize, Deserialize)]
pub struct ExternalQuoteAssemblyQueryParams {
/// Whether to use gas sponsorship for the assembled quote
pub use_gas_sponsorship: Option<bool>,
/// The address to refund gas to
pub refund_address: Option<String>,
}
2 changes: 2 additions & 0 deletions auth/auth-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ native-tls = "0.2"
# === Cryptography === #
aes-gcm = "0.10.1"
alloy-sol-types = "=0.7.7"
alloy-primitives = { version = "=0.7.7", features = ["serde", "k256"] }
ethers = "2"
rand = "0.8.5"

Expand All @@ -48,6 +49,7 @@ futures-util = "0.3"
metrics = "=0.22.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_urlencoded = "0.7"
thiserror = "1.0"
tracing = "0.1"
uuid = { version = "1.0", features = ["serde", "v4"] }
18 changes: 18 additions & 0 deletions auth/auth-server/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ pub enum AuthServerError {
/// An error executing an HTTP request
#[error("Http: {0}")]
Http(String),
/// An error mutating calldata for gas sponsorship
#[error("Calldata mutation error: {0}")]
CalldataMutation(String),
/// An error signing a message
#[error("Signing error: {0}")]
Signing(String),
/// A miscellaneous error
#[error("Error: {0}")]
Custom(String),
Expand Down Expand Up @@ -72,6 +78,18 @@ impl AuthServerError {
pub fn unauthorized<T: ToString>(msg: T) -> Self {
Self::Unauthorized(msg.to_string())
}

/// Create a new calldata mutation error
#[allow(clippy::needless_pass_by_value)]
pub fn calldata_mutation<T: ToString>(msg: T) -> Self {
Self::CalldataMutation(msg.to_string())
}

/// Create a new signing error
#[allow(clippy::needless_pass_by_value)]
pub fn signing<T: ToString>(msg: T) -> Self {
Self::Signing(msg.to_string())
}
}

impl warp::reject::Reject for AuthServerError {}
Expand Down
19 changes: 15 additions & 4 deletions auth/auth-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub(crate) mod schema;
mod server;
mod telemetry;

use auth_server_api::API_KEYS_PATH;
use auth_server_api::{ExternalQuoteAssemblyQueryParams, API_KEYS_PATH};
use clap::Parser;
use ethers::signers::LocalWallet;
use renegade_arbitrum_client::{
Expand Down Expand Up @@ -99,12 +99,22 @@ pub struct Cli {
#[clap(short, long, env = "RPC_URL")]
rpc_url: String,
/// The address of the darkpool contract
#[clap(short = 'a', long, env = "DARKPOOL_ADDRESS")]
#[clap(short, long, env = "DARKPOOL_ADDRESS")]
darkpool_address: String,
/// The URL of the price reporter
#[arg(long, env = "PRICE_REPORTER_URL")]
pub price_reporter_url: String,

// -------------------
// | Gas Sponsorship |
// -------------------
/// The address of the gas sponsor contract
#[clap(long, env = "GAS_SPONSOR_ADDRESS")]
gas_sponsor_address: String,
/// The auth private key used for gas sponsorship, encoded as a hex string
#[clap(long, env = "GAS_SPONSOR_AUTH_KEY")]
gas_sponsor_auth_key: String,

// -------------
// | Telemetry |
// -------------
Expand Down Expand Up @@ -267,9 +277,10 @@ async fn main() {
.and(warp::path::full())
.and(warp::header::headers_cloned())
.and(warp::body::bytes())
.and(warp::query::<ExternalQuoteAssemblyQueryParams>())
.and(with_server(server.clone()))
.and_then(|path, headers, body, server: Arc<Server>| async move {
server.handle_external_quote_assembly_request(path, headers, body).await
.and_then(|path, headers, body, query_params, server: Arc<Server>| async move {
server.handle_external_quote_assembly_request(path, headers, body, query_params).await
});

let atomic_match_path = warp::path("v0")
Expand Down
170 changes: 167 additions & 3 deletions auth/auth-server/src/server/handle_external_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@
//! At a high level the server must first authenticate the request, then forward
//! it to the relayer with admin authentication
use alloy_primitives::Address;
use alloy_sol_types::{sol, SolCall};
use auth_server_api::ExternalQuoteAssemblyQueryParams;
use bytes::Bytes;
use http::Method;
use http::header::CONTENT_LENGTH;
use http::{Method, Response};
use renegade_arbitrum_client::abi::{
processAtomicMatchSettleCall, processAtomicMatchSettleWithReceiverCall,
};
use tracing::{info, instrument, warn};
use warp::{reject::Rejection, reply::Reply};

Expand All @@ -15,6 +22,7 @@ use renegade_api::http::external_match::{
use renegade_circuit_types::fixed_point::FixedPoint;
use renegade_common::types::{token::Token, TimestampedPrice};

use super::helpers::{gen_signed_sponsorship_nonce, get_selector};
use super::Server;
use crate::error::AuthServerError;
use crate::telemetry::helpers::calculate_implied_price;
Expand All @@ -28,6 +36,28 @@ use crate::telemetry::{
},
};

// -------------
// | Constants |
// -------------

/// The gas estimation to use if fetching a gas estimation fails
/// From https://github.com/renegade-fi/renegade/blob/main/workers/api-server/src/http/external_match.rs/#L62
pub const DEFAULT_GAS_ESTIMATION: u64 = 4_000_000; // 4m

// -------
// | ABI |
// -------

// The ABI for gas sponsorship functions
sol! {
function sponsorAtomicMatchSettle(bytes memory internal_party_match_payload, bytes memory valid_match_settle_atomic_statement, bytes memory match_proofs, bytes memory match_linking_proofs, address memory refund_address, uint256 memory nonce, bytes memory signature) external payable;
function sponsorAtomicMatchSettleWithReceiver(address receiver, bytes memory internal_party_match_payload, bytes memory valid_match_settle_atomic_statement, bytes memory match_proofs, bytes memory match_linking_proofs, address memory refund_address, uint256 memory nonce, bytes memory signature) external payable;
}

// ---------------
// | Server Impl |
// ---------------

/// Handle a proxied request
impl Server {
/// Handle an external quote request
Expand Down Expand Up @@ -64,15 +94,39 @@ impl Server {
path: warp::path::FullPath,
headers: warp::hyper::HeaderMap,
body: Bytes,
query_params: ExternalQuoteAssemblyQueryParams,
) -> Result<impl Reply, Rejection> {
// Serialize the path + query params for auth
let query_str = serde_urlencoded::to_string(&query_params).unwrap();
let auth_path = if query_str.is_empty() {
path.as_str().to_string()
} else {
format!("{}?{}", path.as_str(), query_str)
};

// Authorize the request
let key_desc = self.authorize_request(path.as_str(), &headers, &body).await?;
let key_desc = self.authorize_request(&auth_path, &headers, &body).await?;
self.check_bundle_rate_limit(key_desc.clone()).await?;

// Send the request to the relayer
let resp =
let mut resp =
self.send_admin_request(Method::POST, path.as_str(), headers, body.clone()).await?;

if query_params.use_gas_sponsorship.unwrap_or(false) {
// If gas sponsorship is requested, mutate the calldata in the response
// to invoke the gas sponsor contract

info!("Redirecting match bundle through gas sponsor");
let refund_address = query_params
.refund_address
.map(|s| s.parse())
.transpose()
.map_err(AuthServerError::serde)?
.unwrap_or(Address::ZERO);

self.mutate_response_for_gas_sponsorship(&mut resp, refund_address)?;
}

let resp_clone = resp.body().to_vec();
let server_clone = self.clone();
tokio::spawn(async move {
Expand Down Expand Up @@ -330,4 +384,114 @@ impl Server {

Ok(())
}

/// Mutate a quote assembly response to invoke gas sponsorship
fn mutate_response_for_gas_sponsorship(
&self,
resp: &mut Response<Bytes>,
refund_address: Address,
) -> Result<(), AuthServerError> {
let mut external_match_resp: ExternalMatchResponse =
serde_json::from_slice(resp.body()).map_err(AuthServerError::serde)?;

let gas_sponsor_calldata =
self.generate_gas_sponsor_calldata(&external_match_resp, refund_address)?.into();

external_match_resp.match_bundle.settlement_tx.set_to(self.gas_sponsor_address);
external_match_resp.match_bundle.settlement_tx.set_data(gas_sponsor_calldata);

let body =
Bytes::from(serde_json::to_vec(&external_match_resp).map_err(AuthServerError::serde)?);

resp.headers_mut().insert(CONTENT_LENGTH, body.len().into());
*resp.body_mut() = body;

Ok(())
}

/// Generate the calldata for sponsoring the given match via the gas sponsor
fn generate_gas_sponsor_calldata(
&self,
external_match_resp: &ExternalMatchResponse,
refund_address: Address,
) -> Result<Bytes, AuthServerError> {
let calldata = external_match_resp
.match_bundle
.settlement_tx
.data()
.ok_or(AuthServerError::calldata_mutation("expected calldata"))?;

let selector = get_selector(calldata)?;

let gas_sponsor_calldata = match selector {
processAtomicMatchSettleCall::SELECTOR => {
self.sponsor_atomic_match_settle_call(calldata, refund_address)
},
processAtomicMatchSettleWithReceiverCall::SELECTOR => {
self.sponsor_atomic_match_settle_with_receiver_call(calldata, refund_address)
},
_ => {
return Err(AuthServerError::calldata_mutation("invalid selector"));
},
}?;

Ok(gas_sponsor_calldata)
}

/// Create a `sponsorAtomicMatchSettle` call from `processAtomicMatchSettle`
/// calldata
fn sponsor_atomic_match_settle_call(
&self,
calldata: &[u8],
refund_address: Address,
) -> Result<Bytes, AuthServerError> {
let call = processAtomicMatchSettleCall::abi_decode(
calldata, true, // validate
)
.map_err(AuthServerError::calldata_mutation)?;

let (nonce, signature) =
gen_signed_sponsorship_nonce(refund_address, &self.gas_sponsor_auth_key)?;

let sponsored_call = sponsorAtomicMatchSettleCall {
internal_party_match_payload: call.internal_party_match_payload,
valid_match_settle_atomic_statement: call.valid_match_settle_atomic_statement,
match_proofs: call.match_proofs,
match_linking_proofs: call.match_linking_proofs,
refund_address,
nonce,
signature,
};

Ok(sponsored_call.abi_encode().into())
}

/// Create a `sponsorAtomicMatchSettleWithReceiver` call from
/// `processAtomicMatchSettleWithReceiver` calldata
fn sponsor_atomic_match_settle_with_receiver_call(
&self,
calldata: &[u8],
refund_address: Address,
) -> Result<Bytes, AuthServerError> {
let call = processAtomicMatchSettleWithReceiverCall::abi_decode(
calldata, true, // validate
)
.map_err(AuthServerError::calldata_mutation)?;

let (nonce, signature) =
gen_signed_sponsorship_nonce(refund_address, &self.gas_sponsor_auth_key)?;

let sponsored_call = sponsorAtomicMatchSettleWithReceiverCall {
receiver: call.receiver,
internal_party_match_payload: call.internal_party_match_payload,
valid_match_settle_atomic_statement: call.valid_match_settle_atomic_statement,
match_proofs: call.match_proofs,
match_linking_proofs: call.match_linking_proofs,
refund_address,
nonce,
signature,
};

Ok(sponsored_call.abi_encode().into())
}
}
Loading

0 comments on commit 83e5bcb

Please sign in to comment.