Skip to content

Commit

Permalink
feat: add filtering by l1 recepient (#423)
Browse files Browse the repository at this point in the history
# What ❔

<!-- What are the changes this PR brings about? -->
<!-- Example: This PR adds a PR template to the repo. -->
<!-- (For bigger PRs adding more context is appreciated) -->

## Why ❔

<!-- Why are these changes done? What goal do they contribute to? What
are the principles behind them? -->
<!-- Example: PR templates ensure PR reviewers, observers, and future
iterators are in context about the evolution of repos. -->

## Checklist

<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->

- [ ] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] Code has been formatted via `cargo fmt`.
  • Loading branch information
montekki authored Mar 27, 2024
1 parent 32c70ca commit cef3f12
Show file tree
Hide file tree
Showing 22 changed files with 484 additions and 673 deletions.
1 change: 1 addition & 0 deletions .github/spellcheck/finalizer.dic
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
42
args
ABI
vlog
L2
Expand Down
2 changes: 0 additions & 2 deletions Cargo.lock

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

15 changes: 0 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ Deployment is done by deploying a dockerized image of the service.
| `BATCH_FINALIZATION_GAS_LIMIT` | The gas limit of the finalization of the whole batch in a call to `finalizeWithdrawals` in Withdrawal Finalizer Contract |
| `WITHDRAWAL_FINALIZER_ACCOUNT_PRIVATE_KEY` | The private key of the account that is going to be submit finalization transactions |
| `TX_RETRY_TIMEOUT_SECS` | Number of seconds to wait for a potentially stuck finalization transaction before readjusting its fees |
| `TOKENS_TO_FINALIZE` | Configures the sets of tokens this instance of finalizer will finalize. It may be configured as a whitelist, a blacklist, a wildcard or completely disable any finalization. For more info see below. |
| `FINALIZE_ETH_TOKEN` | (Optional) Configure, whether the Ethereum withdrawal events should be monitored. Useful to turn off for custom bridges that are only interested in a particular ERC20 token and have nothing to do with main Ethereum withdrawals |
| `CUSTOM_TOKEN_DEPLOYER_ADDRESSES` | (Optional) Normally ERC20 tokens are deployed by the bridge contract. However, in custom cases it may be necessary to override that behavior with a custom set of addresses that have deployed tokens |
| `CUSTOM_TOKEN_ADDRESSES` | (Optional) Adds a predefined list of tokens to finalize. May be useful in case of custom bridge setups when the regular technique of finding token deployments does not work. |
Expand All @@ -62,20 +61,6 @@ The configuration structure describing the service config can be found in [`conf

** more about zkSync contracts can be found [here](https://github.com/matter-labs/era-contracts/blob/main/docs/Overview.md)

## Configuring Tokens to finalize.

It may be handy to limit a set of tokens the Finalizer is finalizing. This
configuration may be specified by setting a rule in the `TOKENS_TO_FINALIZE` value.
If this environment variable is not set then by default Finalizer will only finalize
ETH token (`0x000...0800a`).

You may specify `All`, `None`, `BlackList` or `WhiteList` as json documents:

1. `TOKENS_TO_FINALIZE = '"All"'` - Finalize everything
1. `TOKENS_TO_FINALIZE = '"None"'` - Finalize nothing
1. `TOKENS_TO_FINALIZE = '{ "WhiteList":[ "0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4" ] }'` - Finalize only these tokens
1. `TOKENS_TO_FINALIZE = '{ "BlackList":[ "0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4" ] }'` - Finalize all tokens but these

## Deploying the finalizer smart contract

The finalizer smart contract needs to reference the addresses of the diamond proxy contract and l1 erc20 proxy contract.
Expand Down
8 changes: 4 additions & 4 deletions bin/withdrawal-finalizer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::str::FromStr;

use envconfig::Envconfig;
use ethers::types::Address;
use finalizer::{AddrList, TokenList};
use finalizer::AddrList;
use serde::{Deserialize, Serialize};
use url::Url;

Expand Down Expand Up @@ -66,9 +66,6 @@ pub struct Config {
#[envconfig(from = "TX_RETRY_TIMEOUT_SECS")]
pub tx_retry_timeout: usize,

#[envconfig(from = "TOKENS_TO_FINALIZE")]
pub tokens_to_finalize: Option<TokenList>,

#[envconfig(from = "FINALIZE_ETH_TOKEN")]
pub finalize_eth_token: Option<bool>,

Expand All @@ -86,6 +83,9 @@ pub struct Config {

#[envconfig(from = "ETH_FINALIZATION_THRESHOLD")]
pub eth_finalization_threshold: Option<String>,

#[envconfig(from = "ONLY_L1_RECIPIENTS")]
pub only_l1_recipients: Option<AddrList>,
}

#[derive(Deserialize, Serialize, Debug, Eq, PartialEq)]
Expand Down
2 changes: 1 addition & 1 deletion bin/withdrawal-finalizer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,9 @@ async fn main() -> Result<()> {
l1_bridge,
config.tx_retry_timeout,
finalizer_account_address,
config.tokens_to_finalize.unwrap_or_default(),
meter_withdrawals,
eth_finalization_threshold,
config.only_l1_recipients.map(|v| v.0.into_iter().collect()),
);
let finalizer_handle = tokio::spawn(finalizer.run(client_l2));

Expand Down
25 changes: 23 additions & 2 deletions chain-events/src/l2_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,15 +463,36 @@ impl L2EventsListener {
{
if let (Some(tx_hash), Some(block_number)) = (log.transaction_hash, log.block_number) {
match l2_event {
L2Events::BridgeBurn(BridgeBurnFilter { amount, .. })
| L2Events::Withdrawal(WithdrawalFilter { amount, .. }) => {
L2Events::BridgeBurn(BridgeBurnFilter { amount, .. }) => {
CHAIN_EVENTS_METRICS.withdrawal_events.inc();

let we = WithdrawalEvent {
tx_hash,
block_number: block_number.as_u64(),
token: log.address,
amount: *amount,
l1_receiver: None,
};
let event = we.into();
tracing::info!("sending withdrawal event {event:?}");
sender
.send(event)
.await
.map_err(|_| Error::ChannelClosing)?;
}
L2Events::Withdrawal(WithdrawalFilter {
amount,
l_1_receiver,
..
}) => {
CHAIN_EVENTS_METRICS.withdrawal_events.inc();

let we = WithdrawalEvent {
tx_hash,
block_number: block_number.as_u64(),
token: log.address,
amount: *amount,
l1_receiver: Some(*l_1_receiver),
};
let event = we.into();
tracing::info!("sending withdrawal event {event:?}");
Expand Down
3 changes: 3 additions & 0 deletions client/src/zksync_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ pub struct WithdrawalEvent {

/// The amount transferred.
pub amount: U256,

/// Address on L1 that will receive this withdrawal.
pub l1_receiver: Option<Address>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down
4 changes: 0 additions & 4 deletions finalizer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,9 @@ sqlx = { workspace = true, features = ["postgres", "runtime-tokio-rustls"] }
tokio = { workspace = true, features = ["macros"] }
tracing = { workspace = true }
vise = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }

client = { workspace = true }
storage = { workspace = true }
tx-sender = { workspace = true }
withdrawals-meterer = { workspace = true }

[dev-dependencies]
pretty_assertions = { workspace = true }
142 changes: 10 additions & 132 deletions finalizer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use ethers::{
types::{H256, U256},
};
use futures::TryFutureExt;
use serde::Deserialize;
use sqlx::PgPool;

use client::{
Expand Down Expand Up @@ -45,34 +44,6 @@ const OUT_OF_FUNDS_BACKOFF: Duration = Duration::from_secs(10);
/// Backoff period if one of the loop iterations has failed.
const LOOP_ITERATION_ERROR_BACKOFF: Duration = Duration::from_secs(5);

/// An `enum` that defines a set of tokens that Finalizer finalizes.
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub enum TokenList {
/// Finalize all known tokens
All,
/// Finalize nothing
None,
/// Finalize everything but these tokens, this is a blacklist.
BlackList(Vec<Address>),
/// Finalize nothing but these tokens, this is a whitelist.
WhiteList(Vec<Address>),
}

impl Default for TokenList {
fn default() -> Self {
Self::All
}
}

impl FromStr for TokenList {
type Err = serde_json::Error;

fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let res = serde_json::from_str(s)?;
Ok(res)
}
}

/// A newtype that represents a set of addresses in JSON format.
#[derive(Debug, Eq, PartialEq)]
pub struct AddrList(pub Vec<Address>);
Expand Down Expand Up @@ -102,8 +73,8 @@ pub struct Finalizer<M1, M2> {
tx_retry_timeout: Duration,
account_address: Address,
withdrawals_meterer: Option<WithdrawalsMeter>,
token_list: TokenList,
eth_threshold: Option<U256>,
only_l1_recipients: Option<Vec<Address>>,
}

const NO_NEW_WITHDRAWALS_BACKOFF: Duration = Duration::from_secs(5);
Expand Down Expand Up @@ -131,9 +102,9 @@ where
l1_bridge: IL1Bridge<M>,
tx_retry_timeout: usize,
account_address: Address,
token_list: TokenList,
meter_withdrawals: bool,
eth_threshold: Option<U256>,
only_l1_recipients: Option<Vec<Address>>,
) -> Self {
let withdrawals_meterer = meter_withdrawals.then_some(WithdrawalsMeter::new(
pgpool.clone(),
Expand All @@ -142,8 +113,6 @@ where
let tx_fee_limit = ethers::utils::parse_ether(TX_FEE_LIMIT)
.expect("{TX_FEE_LIMIT} ether is a parsable amount; qed");

tracing::info!("finalizing tokens {token_list:?}");

Self {
pgpool,
one_withdrawal_gas_limit,
Expand All @@ -158,8 +127,8 @@ where
tx_retry_timeout: Duration::from_secs(tx_retry_timeout as u64),
account_address,
withdrawals_meterer,
token_list,
eth_threshold,
only_l1_recipients,
}
}

Expand Down Expand Up @@ -352,35 +321,13 @@ where
async fn loop_iteration(&mut self) -> Result<()> {
tracing::debug!("begin iteration of the finalizer loop");

let try_finalize_these = match &self.token_list {
TokenList::All => {
storage::withdrawals_to_finalize(
&self.pgpool,
self.query_db_pagination_limit,
self.eth_threshold,
)
.await?
}
TokenList::WhiteList(w) => {
storage::withdrawals_to_finalize_with_whitelist(
&self.pgpool,
self.query_db_pagination_limit,
w,
self.eth_threshold,
)
.await?
}
TokenList::BlackList(b) => {
storage::withdrawals_to_finalize_with_blacklist(
&self.pgpool,
self.query_db_pagination_limit,
b,
self.eth_threshold,
)
.await?
}
TokenList::None => return Ok(()),
};
let try_finalize_these = storage::withdrawals_to_finalize(
&self.pgpool,
self.query_db_pagination_limit,
self.eth_threshold,
self.only_l1_recipients.as_deref(),
)
.await?;

tracing::debug!("trying to finalize these {try_finalize_these:?}");

Expand Down Expand Up @@ -649,72 +596,3 @@ where

Ok(())
}

#[cfg(test)]
mod tests {
use std::str::FromStr;

use crate::AddrList;

use super::TokenList;
use ethers::abi::Address;
use pretty_assertions::assert_eq;

#[test]
fn tokens_list_de() {
let all = "\"All\"";

let none = "\"None\"";

let all: TokenList = serde_json::from_str(all).unwrap();
assert_eq!(all, TokenList::All);

let none: TokenList = serde_json::from_str(none).unwrap();
assert_eq!(none, TokenList::None);

let black = r#"
{
"BlackList":[
"0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4"
]
}
"#;

let usdc_addr: Address = "0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4"
.parse()
.unwrap();

let blocked_usdc: TokenList = serde_json::from_str(black).unwrap();
assert_eq!(blocked_usdc, TokenList::BlackList(vec![usdc_addr]));

let white = r#"
{
"WhiteList":[
"0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4"
]
}
"#;

let allowed_usdc: TokenList = serde_json::from_str(white).unwrap();
assert_eq!(allowed_usdc, TokenList::WhiteList(vec![usdc_addr]));
}

#[test]
fn addr_list_de() {
let addr_1: Address = "0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4"
.parse()
.unwrap();
let addr_2: Address = "0x1820495E7d1B8BA82B706FB972d2A2B8282023d0"
.parse()
.unwrap();

let addr_list = r#"[
"0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4",
"0x1820495E7d1B8BA82B706FB972d2A2B8282023d0"
]"#;

let list: AddrList = AddrList::from_str(addr_list).unwrap();

assert_eq!(list, AddrList(vec![addr_1, addr_2]));
}
}

This file was deleted.

Loading

0 comments on commit cef3f12

Please sign in to comment.