Skip to content

Commit cea9f0c

Browse files
tomip01avilagaston9ManuelBilbaoCopilotilitteri
authored
feat(l2): allow paying fees with custom token (#5024)
**Motivation** This pull request introduces support for a new transaction type, `CustomFeeTransaction`, we want to be able to pay fees with an ERC20 token instead of ETH. **Description** * Added `FeeTokenTransaction` as a new variant to the `Transaction` enum, and updated the `TxType` enum and its associated methods to recognize the new type. * Modified `l2_hook` to deduct and refund fees according to the ERC20 tokens desired as the user * Added new flag for deployer `initial_fee_token` that will register one address in advance. **How to test** You can see the `test_fee_token` in `tests.rs` to see how to build a tx. Some useful commands may be: Register a new fee token: ```bash rex send <BRIDGE_L1> "registerNewFeeToken(address)" <FEE_TOKEN> --rpc-url http://localhost:8545 --private-key 0x941e103320615d394a55708be13e45994c7d93b932b064dbcb2b511fe3254e2e ``` Check if fee token is allowed ```bash rex call 0x000000000000000000000000000000000000fffc "isFeeToken(address)" <FEE_TOKEN> --rpc-url http://localhost:1729 ``` --------- Co-authored-by: Avila Gastón <[email protected]> Co-authored-by: Manuel Iñaki Bilbao <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Ivan Litteri <[email protected]>
1 parent e7f1385 commit cea9f0c

File tree

45 files changed

+2643
-1091
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2643
-1091
lines changed

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/ethrex/build_l2.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ pub fn download_script() {
125125
&Path::new("../../crates/l2/contracts/src/l2/L2Upgradeable.sol"),
126126
"UpgradeableSystemContract",
127127
),
128+
(
129+
&Path::new("../../crates/l2/contracts/src/l2/FeeTokenRegistry.sol"),
130+
"FeeTokenRegistry",
131+
),
128132
];
129133
for (path, name) in l2_contracts {
130134
compile_contract_to_bytecode(
@@ -273,7 +277,8 @@ fn decode_to_bytecode(input_file_path: &Path, output_file_path: &Path) {
273277

274278
use ethrex_l2_sdk::{
275279
COMMON_BRIDGE_L2_ADDRESS, CREATE2DEPLOYER_ADDRESS, DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS,
276-
L2_TO_L1_MESSENGER_ADDRESS, SAFE_SINGLETON_FACTORY_ADDRESS, address_to_word, get_erc1967_slot,
280+
FEE_TOKEN_REGISTRY_ADDRESS, L2_TO_L1_MESSENGER_ADDRESS, SAFE_SINGLETON_FACTORY_ADDRESS,
281+
address_to_word, get_erc1967_slot,
277282
};
278283

279284
#[allow(clippy::enum_variant_names)]
@@ -330,6 +335,12 @@ fn l2_to_l1_messenger_runtime(out_dir: &Path) -> Vec<u8> {
330335
fs::read(path).expect("Failed to read bytecode file")
331336
}
332337

338+
/// Bytecode of the FeeTokenRegistry contract.
339+
fn fee_token_registry_runtime(out_dir: &Path) -> Vec<u8> {
340+
let path = out_dir.join("contracts/solc_out/FeeTokenRegistry.bytecode");
341+
fs::read(path).expect("Failed to read bytecode file")
342+
}
343+
333344
/// Bytecode of the Create2Deployer contract.
334345
fn create2deployer_runtime(out_dir: &Path) -> Vec<u8> {
335346
let path = out_dir.join("contracts/solc_out/Create2Deployer.bytecode");
@@ -433,7 +444,14 @@ pub fn update_genesis_file(
433444
out_dir,
434445
)?;
435446

436-
for address in 0xff00..0xfffd {
447+
add_with_proxy(
448+
&mut genesis,
449+
FEE_TOKEN_REGISTRY_ADDRESS,
450+
fee_token_registry_runtime(out_dir),
451+
out_dir,
452+
)?;
453+
454+
for address in 0xff00..0xfffc {
437455
add_placeholder_proxy(&mut genesis, Address::from_low_u64_be(address), out_dir)?;
438456
}
439457

cmd/ethrex/l2/deployer.rs

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use ethrex_common::{
1616
use ethrex_l2::utils::test_data_io::read_genesis_file;
1717
use ethrex_l2_common::{calldata::Value, prover::ProverType, utils::get_address_from_secret_key};
1818
use ethrex_l2_rpc::signer::{LocalSigner, Signer};
19+
use ethrex_l2_sdk::register_fee_token;
1920
use ethrex_l2_sdk::{
2021
build_generic_tx, calldata::encode_calldata, create2_deploy_from_bytecode,
2122
deploy_with_proxy_from_bytecode, initialize_contract, send_generic_transaction,
@@ -275,6 +276,16 @@ pub struct DeployerOptions {
275276
help = "Address of the owner of the CommonBridge contract, who can upgrade the contract."
276277
)]
277278
pub bridge_owner: Address,
279+
#[arg(
280+
long,
281+
value_name = "PRIVATE_KEY",
282+
value_parser = parse_private_key,
283+
env = "ETHREX_BRIDGE_OWNER_PK",
284+
help_heading = "Deployer options",
285+
help = "Private key of the owner of the CommonBridge contract. If set, the deployer will send a transaction to accept the ownership.",
286+
requires = "bridge_owner"
287+
)]
288+
pub bridge_owner_pk: Option<SecretKey>,
278289
#[arg(
279290
long,
280291
value_name = "PRIVATE_KEY",
@@ -336,6 +347,14 @@ pub struct DeployerOptions {
336347
help = "Genesis data is extracted at compile time, used for development"
337348
)]
338349
pub use_compiled_genesis: bool,
350+
#[arg(
351+
long,
352+
value_name = "ADDRESS",
353+
env = "ETHREX_DEPLOYER_INITIAL_FEE_TOKEN",
354+
help_heading = "Deployer options",
355+
help = "This address will be registered as an initial fee token"
356+
)]
357+
pub initial_fee_token: Option<Address>,
339358
}
340359

341360
impl Default for DeployerOptions {
@@ -396,13 +415,25 @@ impl Default for DeployerOptions {
396415
0x44, 0x17, 0x09, 0x2b, 0x70, 0xa3, 0xe5, 0xf1, 0x0d, 0xc5, 0x04, 0xd0, 0x94, 0x7d,
397416
0xd2, 0x56, 0xb9, 0x65, 0xfc, 0x62,
398417
]),
418+
// Private Key: 0x941e103320615d394a55708be13e45994c7d93b932b064dbcb2b511fe3254e2e
419+
bridge_owner_pk: Some(
420+
SecretKey::from_slice(
421+
H256::from_str(
422+
"941e103320615d394a55708be13e45994c7d93b932b064dbcb2b511fe3254e2e",
423+
)
424+
.expect("Bridge owner private key is a valid hex string")
425+
.as_bytes(),
426+
)
427+
.expect("Bridge owner private key is valid"),
428+
),
399429
on_chain_proposer_owner_pk: None,
400430
sp1_vk_path: None,
401431
risc0_vk_path: None,
402432
deploy_based_contracts: false,
403433
sequencer_registry_owner: None,
404434
inclusion_max_wait: 3000,
405435
use_compiled_genesis: true,
436+
initial_fee_token: None,
406437
}
407438
}
408439
}
@@ -977,7 +1008,7 @@ async fn initialize_contracts(
9771008
info!("Initializing CommonBridge");
9781009
let initialize_tx_hash = {
9791010
let calldata_values = vec![
980-
Value::Address(opts.bridge_owner),
1011+
Value::Address(initializer.address()),
9811012
Value::Address(contract_addresses.on_chain_proposer_address),
9821013
Value::Uint(opts.inclusion_max_wait.into()),
9831014
];
@@ -994,6 +1025,56 @@ async fn initialize_contracts(
9941025
};
9951026
info!(tx_hash = %format!("{initialize_tx_hash:#x}"), "CommonBridge initialized");
9961027

1028+
if let Some(fee_token) = opts.initial_fee_token {
1029+
register_fee_token(
1030+
eth_client,
1031+
contract_addresses.bridge_address,
1032+
fee_token,
1033+
initializer,
1034+
)
1035+
.await?;
1036+
info!(?fee_token, "CommonBridge initial fee token registered");
1037+
}
1038+
1039+
if opts.bridge_owner != initializer.address() {
1040+
let transfer_calldata = encode_calldata(
1041+
TRANSFER_OWNERSHIP_SIGNATURE,
1042+
&[Value::Address(opts.bridge_owner)],
1043+
)?;
1044+
let transfer_tx_hash = initialize_contract(
1045+
contract_addresses.bridge_address,
1046+
transfer_calldata,
1047+
initializer,
1048+
eth_client,
1049+
)
1050+
.await?;
1051+
if let Some(owner_pk) = opts.bridge_owner_pk {
1052+
let signer = Signer::Local(LocalSigner::new(owner_pk));
1053+
let accept_calldata = encode_calldata(ACCEPT_OWNERSHIP_SIGNATURE, &[])?;
1054+
let accept_tx = build_generic_tx(
1055+
eth_client,
1056+
TxType::EIP1559,
1057+
contract_addresses.bridge_address,
1058+
opts.bridge_owner,
1059+
accept_calldata.into(),
1060+
Overrides::default(),
1061+
)
1062+
.await?;
1063+
let accept_tx_hash = send_generic_transaction(eth_client, accept_tx, &signer).await?;
1064+
wait_for_transaction_receipt(accept_tx_hash, eth_client, 100).await?;
1065+
info!(
1066+
transfer_tx_hash = %format!("{transfer_tx_hash:#x}"),
1067+
accept_tx_hash = %format!("{accept_tx_hash:#x}"),
1068+
"CommonBridge ownership transferred and accepted"
1069+
);
1070+
} else {
1071+
info!(
1072+
transfer_tx_hash = %format!("{transfer_tx_hash:#x}"),
1073+
"CommonBridge ownership transfer pending acceptance"
1074+
);
1075+
}
1076+
}
1077+
9971078
trace!("Contracts initialized");
9981079
Ok(())
9991080
}
@@ -1031,6 +1112,8 @@ async fn make_deposits(
10311112
.map(|line| line.trim().to_string())
10321113
.collect();
10331114

1115+
let mut last_hash = None;
1116+
10341117
for pk in private_keys.iter() {
10351118
let secret_key = parse_private_key(pk).map_err(|_| {
10361119
DeployerError::DecodingError("Error while parsing private key".to_string())
@@ -1070,6 +1153,7 @@ async fn make_deposits(
10701153

10711154
match send_generic_transaction(eth_client, build, &signer).await {
10721155
Ok(hash) => {
1156+
last_hash = Some(hash);
10731157
info!(
10741158
address =? signer.address(),
10751159
?value_to_deposit,
@@ -1084,6 +1168,9 @@ async fn make_deposits(
10841168
}
10851169
}
10861170
trace!("Deposits finished");
1171+
if let Some(hash) = last_hash {
1172+
wait_for_transaction_receipt(hash, eth_client, 100).await?;
1173+
}
10871174
Ok(())
10881175
}
10891176

crates/blockchain/blockchain.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1382,6 +1382,7 @@ impl Blockchain {
13821382
"Privileged Transactions are not supported in P2P".to_string(),
13831383
));
13841384
}
1385+
Transaction::FeeTokenTransaction(itx) => P2PTransaction::FeeTokenTransaction(itx),
13851386
};
13861387

13871388
Ok(result)

crates/blockchain/metrics/metrics_transactions.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ impl MetricsTxType {
162162
ethrex_common::types::TxType::EIP4844 => "EIP4844",
163163
ethrex_common::types::TxType::EIP7702 => "EIP7702",
164164
ethrex_common::types::TxType::Privileged => "Privileged",
165+
ethrex_common::types::TxType::FeeToken => "FeeTokenTransaction",
165166
}
166167
}
167168
pub fn all() -> Vec<String> {
@@ -172,6 +173,7 @@ impl MetricsTxType {
172173
"EIP4844".to_string(),
173174
"EIP7702".to_string(),
174175
"Privileged".to_string(),
176+
"FeeToken".to_string(),
175177
]
176178
}
177179
}

crates/common/types/receipt.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ impl ReceiptWithBloom {
175175
0x2 => TxType::EIP1559,
176176
0x3 => TxType::EIP4844,
177177
0x4 => TxType::EIP7702,
178+
0x7d => TxType::FeeToken,
178179
0x7e => TxType::Privileged,
179180
ty => {
180181
return Err(RLPDecodeError::Custom(format!(
@@ -237,6 +238,7 @@ impl RLPDecode for ReceiptWithBloom {
237238
0x2 => TxType::EIP1559,
238239
0x3 => TxType::EIP4844,
239240
0x4 => TxType::EIP7702,
241+
0x7d => TxType::FeeToken,
240242
0x7e => TxType::Privileged,
241243
ty => {
242244
return Err(RLPDecodeError::Custom(format!(

0 commit comments

Comments
 (0)