Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: integrate Gelato and refactor transaction submission configuration #140

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
fcf7fd7
prog(gelato): starting gelato relay sdk
luketchang Apr 27, 2022
f8fb39b
feat(gelato): finish gelato bindings for sending relay, getting suppo…
luketchang Apr 28, 2022
5f78657
chore(maintenance): add changelog and readme
luketchang Apr 28, 2022
088e20e
refactor(gelato): make client object
luketchang Apr 28, 2022
4db1ce5
prog(contract/gelato): starts draft ChainSubmitter
luketchang Apr 28, 2022
6995cf9
prog(contract/gelato): completes local half of ChainSubmitter
luketchang Apr 29, 2022
5ff361b
prog(gelato): adds get fee estimate hook to gelato bindings
luketchang Apr 29, 2022
6c0e1fd
prog(chain-submitter): adds MetaTx type and function to spawn receivi…
luketchang Apr 30, 2022
194d6d6
prog(chain-submitter): add gelato variant skeleton
luketchang Apr 30, 2022
af1f542
prog(chain-submitter): impl From traits
luketchang Apr 30, 2022
5b214a5
refactor(gelato): move client to nomad-ethereum
luketchang Apr 30, 2022
5d07c82
prog(tx-submission-config): struggling
luketchang May 1, 2022
f191e7c
prog(configuration): working TransactionSubmitterConf in configuration
luketchang May 1, 2022
0900892
prog(transaction-submitter): macro starts to take Option<TransactionS…
luketchang May 1, 2022
c6a60b2
prog(chain-submitter): can build chain submitter for contract in macro
luketchang May 1, 2022
d1987ce
prog(chain-submitter): compiles with contract macro creating but not …
luketchang May 1, 2022
5a77690
prog(chain-submitter): adds gelato branch to contract creation macro …
luketchang May 1, 2022
18acf82
feat(contract-interfaces): replace report_tx! with chain-submitter calls
luketchang May 2, 2022
ecb0144
chore(nits): nits
luketchang May 2, 2022
660c5b8
chore(nit): chain submitter -> tx submitter
luketchang May 2, 2022
f088519
chore(clippy): fix clippy warnings
luketchang May 2, 2022
45aaf78
fix(gelato-client): add ethers middleware to gelato client for gas es…
luketchang May 2, 2022
ab9a69b
fix(rebase): rebase on deser changes
luketchang May 3, 2022
dd06a04
refactor(configuration): simplify from env namespacing
luketchang May 3, 2022
77eced7
chore(nits): remove printlns and fix comments
luketchang May 4, 2022
b5ba03c
prog(gelato): add utils stub for sponsor signing
luketchang May 4, 2022
9136f00
feat(gelato): tentative sponsor signing logic
luketchang May 4, 2022
f065d7d
prog(gelato): more work on gelato forward request
luketchang May 4, 2022
b0c33b0
fix(gelato): fix domain separator calculation
luketchang May 7, 2022
8b0ddf7
feat(gelato): send forward request lib function
luketchang May 7, 2022
fdde959
prog(gelato): smooth out formatting after testing forward request end…
luketchang May 8, 2022
81a7899
prog(gelato): successfully computes digest but signing not eip 712
luketchang May 8, 2022
daf6a29
feat(gelato): successfully signs forward request eip 712
luketchang May 8, 2022
600de9a
prog(gelato): properly handles hex formatting for request.data, minor…
luketchang May 8, 2022
2281e0a
fix(gelato): deserialize check or date
luketchang May 9, 2022
2ebb1e4
refactor(tx-submitter): move gelato complexity to gelato module
luketchang May 9, 2022
f481235
fix(gelato): polling for gelato tx takes self
luketchang May 11, 2022
816fe51
fix(rebase): fix env files and secrets implementation after rebase on…
luketchang May 13, 2022
e2431ab
feat(gelato): add utils map for chain id to forwarder proxy
luketchang May 13, 2022
6d36e3a
fix(gelato): update forward request interface after gelato audit
luketchang May 19, 2022
021938d
fix(gelato-bindings): add new API fields to web reqs
luketchang May 19, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ members = [
"tools/kms-cli",
"tools/nomad-cli",
"tools/balance-exporter",
"gelato-relay",
]
11 changes: 9 additions & 2 deletions agents/watcher/src/watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,14 +524,21 @@ impl NomadAgent for Watcher {
.values()
{
let name = &chain_setup.name;
let signer = settings.base.get_signer(name).await.transpose()?;
let submitter_conf = settings.base.get_submitter_conf(name);

if submitter_conf.is_none() {
panic!("Cannot configure watcher connection manager without transaction submission config!");
}

let gas = settings
.as_ref()
.gas
.get(name)
.map(|c| c.core.connection_manager);

let manager = chain_setup.try_into_connection_manager(signer, gas).await;
let manager = chain_setup
.try_into_connection_manager(submitter_conf, gas)
.await;
connection_managers.push(manager);
}

Expand Down
12 changes: 8 additions & 4 deletions chains/nomad-ethereum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ tracing = "0.1.22"
color-eyre = "0.5.0"
anyhow = "1"
num = "0.4"

nomad-xyz-configuration = { path = "../../configuration" }
nomad-types = { path = "../../nomad-types" }
nomad-core = { path = "../../nomad-core" }
tokio = "1.7.1"
hex = "0.4.3"
prometheus = "0.12"
Expand All @@ -32,6 +28,14 @@ futures-util = "0.3.12"
tracing-futures = "0.2.5"
url = "2.2.2"
thiserror = "1.0.30"
reqwest = { version = "0.11.10", features = ["json"]}
lazy_static = "1.4.0"
sha3 = "0.9.1"

nomad-xyz-configuration = { path = "../../configuration" }
nomad-types = { path = "../../nomad-types" }
nomad-core = { path = "../../nomad-core" }
gelato-relay = { path = "../../gelato-relay" }

[build-dependencies]
ethers = {git = "https://github.com/gakonst/ethers-rs", branch = "master", features = ["abigen"]}
209 changes: 209 additions & 0 deletions chains/nomad-ethereum/src/gelato/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
use ethers::signers::Signer;
use ethers::types::transaction::eip2718::TypedTransaction;
use ethers::types::{Address, H256};
use ethers::{prelude::Bytes, providers::Middleware};
use gelato_relay::{GelatoClient, RelayResponse, TaskState};
use nomad_core::{ChainCommunicationError, Signers, TxOutcome};
use std::{str::FromStr, sync::Arc};
use tokio::task::JoinHandle;
use tokio::time::{sleep, Duration};
use tracing::{debug, info};

/// EIP-712 forward request structure
mod types;
pub use types::*;

pub mod utils;

pub(crate) const ACCEPTABLE_STATES: [TaskState; 4] = [
TaskState::CheckPending,
TaskState::ExecPending,
TaskState::ExecSuccess,
TaskState::WaitingForConfirmation,
];

/// Gelato client for submitting txs to single chain
#[derive(Debug, Clone)]
pub struct SingleChainGelatoClient<M> {
/// Gelato client
pub gelato: Arc<GelatoClient>,
/// Ethers client (for estimating gas)
pub eth_client: Arc<M>,
/// Sponsor signer
pub sponsor: Signers,
/// Gelato relay forwarder address
pub forwarder: Address,
/// Chain id
pub chain_id: usize,
/// Fee token
pub fee_token: String,
/// Transactions are of high priority
pub is_high_priority: bool,
}

impl<M> SingleChainGelatoClient<M>
where
M: Middleware + 'static,
{
/// Get reference to base client
pub fn gelato(&self) -> Arc<GelatoClient> {
self.gelato.clone()
}

/// Instantiate single chain client with default Gelato url
pub fn with_default_url(
eth_client: Arc<M>,
sponsor: Signers,
forwarder: Address,
chain_id: usize,
fee_token: String,
is_high_priority: bool,
) -> Self {
Self {
gelato: GelatoClient::default().into(),
eth_client,
sponsor,
forwarder,
chain_id,
fee_token,
is_high_priority,
}
}

/// Submit a transaction to Gelato and poll until completion or failure.
pub async fn submit_blocking(
&self,
domain: u32,
contract_address: Address,
tx: &TypedTransaction,
) -> Result<TxOutcome, ChainCommunicationError> {
let RelayResponse { task_id } = self.dispatch_tx(domain, contract_address, tx).await?;

info!(task_id = ?&task_id, "Submitted tx to Gelato relay.");

info!(task_id = ?&task_id, "Polling Gelato task...");
self.poll_task_id(task_id)
.await
.map_err(|e| ChainCommunicationError::TxSubmissionError(e.into()))?
}

/// Dispatch tx to Gelato and return task id.
pub async fn dispatch_tx(
&self,
domain: u32,
contract_address: Address,
tx: &TypedTransaction,
) -> Result<RelayResponse, ChainCommunicationError> {
// If gas limit not hardcoded in tx, eth_estimateGas
let gas_limit = tx
.gas()
.unwrap_or(
&self
.eth_client
.estimate_gas(tx)
.await
.map_err(|e| ChainCommunicationError::MiddlewareError(e.into()))?,
)
.as_usize();
let data = tx.data().expect("!tx data");

info!(
domain = domain,
contract_address = ?contract_address,
"Dispatching tx to Gelato relay."
);

self.send_forward_request(contract_address, data, gas_limit)
.await
.map_err(|e| ChainCommunicationError::TxSubmissionError(e.into()))
}

/// Poll task id and return tx hash of transaction if successful, error if
/// otherwise.
pub fn poll_task_id(
&self,
task_id: String,
) -> JoinHandle<Result<TxOutcome, ChainCommunicationError>> {
let gelato = self.gelato();

tokio::spawn(async move {
loop {
let status = gelato
.get_task_status(&task_id)
.await
.map_err(|e| ChainCommunicationError::TxSubmissionError(e.into()))?
.expect("!task status");

if !ACCEPTABLE_STATES.contains(&status.task_state) {
return Err(ChainCommunicationError::TxSubmissionError(
format!("Gelato task failed: {:?}", status).into(),
));
}

if let Some(execution) = &status.execution {
info!(
chain = ?status.chain,
task_id = ?status.task_id,
execution = ?execution,
"Gelato relay executed tx."
);

let tx_hash = &execution.transaction_hash;
let txid = H256::from_str(tx_hash)
.unwrap_or_else(|_| panic!("Malformed tx hash from Gelato"));

return Ok(TxOutcome { txid });
}

debug!(task_id = ?task_id, "Polling Gelato task.");
sleep(Duration::from_millis(500)).await;
}
})
}

/// Send relay transaction
pub async fn send_forward_request(
&self,
target: Address,
data: &Bytes,
gas_limit: usize,
) -> Result<RelayResponse, ChainCommunicationError> {
let max_fee = self
.gelato()
.get_estimated_fee(self.chain_id, &self.fee_token, gas_limit + 100_000, false)
.await
.map_err(|e| ChainCommunicationError::CustomError(e.into()))?; // add 100k gas padding for Gelato contract ops

let target = format!("{:#x}", target);
let sponsor = format!("{:#x}", self.sponsor.address());
let data = data.to_string().strip_prefix("0x").unwrap().to_owned();

let unfilled_request = UnfilledFowardRequest {
chain_id: self.chain_id,
target,
data,
fee_token: self.fee_token.to_owned(),
payment_type: 1, // gas tank
max_fee,
gas: gas_limit,
sponsor,
sponsor_chain_id: self.chain_id,
nonce: 0, // default, not needed
enforce_sponsor_nonce: false, // replay safety builtin to contracts
enforce_sponsor_nonce_ordering: false,
};

let sponsor_signature = self
.sponsor
.sign_typed_data(&unfilled_request)
.await
.unwrap();

let filled_request = unfilled_request.into_filled(sponsor_signature.to_vec());

self.gelato()
.send_forward_request(&filled_request)
.await
.map_err(|e| ChainCommunicationError::TxSubmissionError(e.into()))
}
}
Loading