Skip to content

Commit f82b026

Browse files
authored
Merge branch 'main' into randygrok/typed-tx-adr
2 parents de884b9 + fd3cc2a commit f82b026

File tree

17 files changed

+1871
-10
lines changed

17 files changed

+1871
-10
lines changed

Cargo.lock

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

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,31 @@ Implementation details:
274274
- No runtime environment variables are required; the chainspec carries the policy alongside other fork settings
275275
- When not configured, the EVM operates normally with standard fee burning
276276

277+
### Custom EIP-1559 Parameters (Custom Networks Only)
278+
279+
ev-reth also lets you override EIP-1559 base fee parameters through the same `evolve` stanza in
280+
your chainspec. This is consensus-critical: all nodes must use the same values.
281+
282+
```json
283+
"config": {
284+
...,
285+
"evolve": {
286+
"baseFeeMaxChangeDenominator": 8,
287+
"baseFeeElasticityMultiplier": 2,
288+
"initialBaseFeePerGas": 1000000000
289+
}
290+
}
291+
```
292+
293+
Notes:
294+
295+
- `baseFeeMaxChangeDenominator` and `baseFeeElasticityMultiplier` override the EIP-1559 formula.
296+
- `initialBaseFeePerGas` only applies when `londonBlock` is `0` (London at genesis). It updates the
297+
genesis `baseFeePerGas` value; if London is activated later, the initial base fee remains
298+
hardcoded to the EIP-1559 constant.
299+
- The node will fail fast if these values are invalid or inconsistent.
300+
- See `docs/eip1559-configuration.md` for recommended values at 100ms block times.
301+
277302
### Custom Contract Size Limit
278303

279304
By default, Ethereum enforces a 24KB contract size limit per [EIP-170](https://eips.ethereum.org/EIPS/eip-170). If your network requires larger contracts, `ev-reth` supports configuring a custom limit via the chainspec.

bin/ev-reth/src/main.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ use evolve_ev_reth::{
1010
config::EvolveConfig,
1111
rpc::txpool::{EvolveTxpoolApiImpl, EvolveTxpoolApiServer},
1212
};
13-
use reth_ethereum_cli::{chainspec::EthereumChainSpecParser, Cli};
13+
use reth_ethereum_cli::Cli;
1414
use reth_tracing_otlp::layer as otlp_layer;
1515
use tracing::info;
1616
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
1717

18-
use ev_node::{log_startup, EvolveArgs, EvolveNode};
18+
use ev_node::{log_startup, EvolveArgs, EvolveChainSpecParser, EvolveNode};
1919

2020
#[global_allocator]
2121
static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator();
@@ -47,8 +47,8 @@ fn main() {
4747
init_otlp_tracing();
4848
}
4949

50-
if let Err(err) = Cli::<EthereumChainSpecParser, EvolveArgs>::parse().run(
51-
|builder, _evolve_args| async move {
50+
if let Err(err) =
51+
Cli::<EvolveChainSpecParser, EvolveArgs>::parse().run(|builder, _evolve_args| async move {
5252
log_startup();
5353
let handle = builder
5454
.node(EvolveNode::new())
@@ -67,8 +67,8 @@ fn main() {
6767

6868
info!("=== EV-RETH: Node launched successfully with ev-reth payload builder ===");
6969
handle.node_exit_future.await
70-
},
71-
) {
70+
})
71+
{
7272
eprintln!("Error: {err:?}");
7373
std::process::exit(1);
7474
}

contracts/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
Smart contracts for EV-Reth, including the FeeVault for bridging collected fees to Celestia.
44

5+
## AdminProxy
6+
7+
The AdminProxy contract solves the bootstrap problem for admin addresses at genesis. It acts as an intermediary owner/admin for other contracts and precompiles (like the Mint Precompile) when the final admin (e.g., a multisig) is not known at genesis time.
8+
9+
See [AdminProxy documentation](../docs/contracts/admin_proxy.md) for detailed setup and usage instructions.
10+
511
## FeeVault
612

713
The FeeVault contract collects base fees and bridges them to Celestia via Hyperlane. It supports:
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import {Script, console} from "forge-std/Script.sol";
5+
import {AdminProxy} from "../src/AdminProxy.sol";
6+
7+
/// @title GenerateAdminProxyAlloc
8+
/// @notice Generates genesis alloc JSON for deploying AdminProxy at a deterministic address
9+
/// @dev Run with: OWNER=0xYourAddress forge script script/GenerateAdminProxyAlloc.s.sol -vvv
10+
///
11+
/// This script outputs the bytecode and storage layout needed to deploy AdminProxy
12+
/// in the genesis block. The owner is set directly in storage slot 0.
13+
///
14+
/// Usage:
15+
/// 1. Set OWNER env var to your initial admin EOA address
16+
/// 2. Run this script to get the bytecode and storage
17+
/// 3. Add to genesis.json alloc section at desired address (e.g., 0x...Ad00)
18+
/// 4. Set that address as mintAdmin in chainspec config
19+
contract GenerateAdminProxyAlloc is Script {
20+
// Suggested deterministic address for AdminProxy
21+
// Using a memorable address in the precompile-adjacent range
22+
address constant SUGGESTED_ADDRESS = 0x000000000000000000000000000000000000Ad00;
23+
24+
function run() external {
25+
// Get owner from environment, default to zero if not set
26+
address owner = vm.envOr("OWNER", address(0));
27+
28+
// Deploy to get runtime bytecode
29+
AdminProxy proxy = new AdminProxy();
30+
31+
// Get runtime bytecode (not creation code)
32+
bytes memory runtimeCode = address(proxy).code;
33+
34+
// Convert owner to storage slot value (left-padded to 32 bytes)
35+
bytes32 ownerSlotValue = bytes32(uint256(uint160(owner)));
36+
37+
console.log("========== AdminProxy Genesis Alloc ==========");
38+
console.log("");
39+
console.log("Suggested address:", SUGGESTED_ADDRESS);
40+
console.log("Owner (from OWNER env):", owner);
41+
console.log("");
42+
43+
if (owner == address(0)) {
44+
console.log("WARNING: OWNER not set! Set OWNER env var to your admin EOA.");
45+
console.log("Example: OWNER=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 forge script ...");
46+
console.log("");
47+
}
48+
49+
console.log("Add this to your genesis.json 'alloc' section:");
50+
console.log("");
51+
console.log("{");
52+
console.log(' "alloc": {');
53+
console.log(' "000000000000000000000000000000000000Ad00": {');
54+
console.log(' "balance": "0x0",');
55+
console.log(' "code": "%s",', vm.toString(runtimeCode));
56+
console.log(' "storage": {');
57+
console.log(' "0x0": "%s"', vm.toString(ownerSlotValue));
58+
console.log(" }");
59+
console.log(" }");
60+
console.log(" }");
61+
console.log("}");
62+
console.log("");
63+
console.log("Then update chainspec config:");
64+
console.log("");
65+
console.log("{");
66+
console.log(' "config": {');
67+
console.log(' "evolve": {');
68+
console.log(' "mintAdmin": "0x000000000000000000000000000000000000Ad00",');
69+
console.log(' "mintPrecompileActivationHeight": 0');
70+
console.log(" }");
71+
console.log(" }");
72+
console.log("}");
73+
console.log("");
74+
console.log("==============================================");
75+
console.log("");
76+
console.log("Post-genesis steps:");
77+
console.log("1. Owner can immediately use the proxy (no claiming needed)");
78+
console.log("2. Deploy multisig (e.g., Safe)");
79+
console.log("3. Call transferOwnership(multisigAddress)");
80+
console.log("4. From multisig, call acceptOwnership()");
81+
console.log("");
82+
83+
// Also output raw values for programmatic use
84+
console.log("Raw bytecode length:", runtimeCode.length);
85+
console.log("Owner storage slot (0x0):", vm.toString(ownerSlotValue));
86+
}
87+
}
88+
89+
/// @title GenerateAdminProxyAllocJSON
90+
/// @notice Outputs just the JSON snippet for easy copy-paste
91+
/// @dev Run with: OWNER=0xYourAddress forge script script/GenerateAdminProxyAlloc.s.sol:GenerateAdminProxyAllocJSON -vvv
92+
contract GenerateAdminProxyAllocJSON is Script {
93+
function run() external {
94+
address owner = vm.envOr("OWNER", address(0));
95+
96+
AdminProxy proxy = new AdminProxy();
97+
bytes memory runtimeCode = address(proxy).code;
98+
bytes32 ownerSlotValue = bytes32(uint256(uint160(owner)));
99+
100+
// Output minimal JSON that can be merged into genesis
101+
string memory json = string(
102+
abi.encodePacked(
103+
'{"000000000000000000000000000000000000Ad00":{"balance":"0x0","code":"',
104+
vm.toString(runtimeCode),
105+
'","storage":{"0x0":"',
106+
vm.toString(ownerSlotValue),
107+
'"}}}'
108+
)
109+
);
110+
111+
console.log(json);
112+
}
113+
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import {Script, console} from "forge-std/Script.sol";
5+
import {FeeVault} from "../src/FeeVault.sol";
6+
7+
abstract contract FeeVaultAllocBase is Script {
8+
struct Config {
9+
address feeVaultAddress;
10+
address owner;
11+
uint32 destinationDomain;
12+
bytes32 recipientAddress;
13+
uint256 minimumAmount;
14+
uint256 callFee;
15+
uint256 bridgeShareBpsRaw;
16+
uint256 bridgeShareBps;
17+
address otherRecipient;
18+
address hypNativeMinter;
19+
bytes32 salt;
20+
address deployer;
21+
}
22+
23+
function loadConfig() internal view returns (Config memory cfg) {
24+
cfg.owner = vm.envAddress("OWNER");
25+
cfg.destinationDomain = uint32(vm.envOr("DESTINATION_DOMAIN", uint256(0)));
26+
cfg.recipientAddress = vm.envOr("RECIPIENT_ADDRESS", bytes32(0));
27+
cfg.minimumAmount = vm.envOr("MINIMUM_AMOUNT", uint256(0));
28+
cfg.callFee = vm.envOr("CALL_FEE", uint256(0));
29+
cfg.bridgeShareBpsRaw = vm.envOr("BRIDGE_SHARE_BPS", uint256(0));
30+
cfg.otherRecipient = vm.envOr("OTHER_RECIPIENT", address(0));
31+
cfg.hypNativeMinter = vm.envOr("HYP_NATIVE_MINTER", address(0));
32+
cfg.feeVaultAddress = vm.envOr("FEE_VAULT_ADDRESS", address(0));
33+
cfg.deployer = vm.envOr("DEPLOYER", address(0));
34+
cfg.salt = vm.envOr("SALT", bytes32(0));
35+
36+
require(cfg.owner != address(0), "OWNER required");
37+
require(cfg.bridgeShareBpsRaw <= 10000, "BRIDGE_SHARE_BPS > 10000");
38+
39+
cfg.bridgeShareBps = cfg.bridgeShareBpsRaw == 0 ? 10000 : cfg.bridgeShareBpsRaw;
40+
41+
if (cfg.feeVaultAddress == address(0) && cfg.deployer != address(0)) {
42+
bytes32 initCodeHash = keccak256(
43+
abi.encodePacked(
44+
type(FeeVault).creationCode,
45+
abi.encode(
46+
cfg.owner,
47+
cfg.destinationDomain,
48+
cfg.recipientAddress,
49+
cfg.minimumAmount,
50+
cfg.callFee,
51+
cfg.bridgeShareBpsRaw,
52+
cfg.otherRecipient
53+
)
54+
)
55+
);
56+
cfg.feeVaultAddress = address(
57+
uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), cfg.deployer, cfg.salt, initCodeHash))))
58+
);
59+
}
60+
61+
require(cfg.feeVaultAddress != address(0), "FEE_VAULT_ADDRESS or DEPLOYER required");
62+
}
63+
64+
function computeSlots(Config memory cfg)
65+
internal
66+
pure
67+
returns (
68+
bytes32 slot0,
69+
bytes32 slot1,
70+
bytes32 slot2,
71+
bytes32 slot3,
72+
bytes32 slot4,
73+
bytes32 slot5,
74+
bytes32 slot6
75+
)
76+
{
77+
slot0 = bytes32(uint256(uint160(cfg.hypNativeMinter)));
78+
slot1 = bytes32((uint256(cfg.destinationDomain) << 160) | uint256(uint160(cfg.owner)));
79+
slot2 = cfg.recipientAddress;
80+
slot3 = bytes32(cfg.minimumAmount);
81+
slot4 = bytes32(cfg.callFee);
82+
slot5 = bytes32(uint256(uint160(cfg.otherRecipient)));
83+
slot6 = bytes32(cfg.bridgeShareBps);
84+
}
85+
86+
function addressKey(address addr) internal pure returns (string memory) {
87+
bytes memory full = bytes(vm.toString(addr));
88+
bytes memory key = new bytes(40);
89+
// Fixed-length copy for address key without 0x prefix.
90+
for (uint256 i = 0; i < 40; i++) {
91+
key[i] = full[i + 2];
92+
}
93+
return string(key);
94+
}
95+
}
96+
97+
/// @title GenerateFeeVaultAlloc
98+
/// @notice Generates genesis alloc JSON for deploying FeeVault at a deterministic address
99+
/// @dev Run with: OWNER=0x... forge script script/GenerateFeeVaultAlloc.s.sol -vvv
100+
contract GenerateFeeVaultAlloc is FeeVaultAllocBase {
101+
function run() external view {
102+
Config memory cfg = loadConfig();
103+
bytes memory runtimeCode = type(FeeVault).runtimeCode;
104+
105+
(bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5, bytes32 slot6) =
106+
computeSlots(cfg);
107+
108+
console.log("========== FeeVault Genesis Alloc ==========");
109+
console.log("FeeVault address:", cfg.feeVaultAddress);
110+
console.log("Owner:", cfg.owner);
111+
console.log("Destination domain:", cfg.destinationDomain);
112+
console.log("Bridge share bps (raw):", cfg.bridgeShareBpsRaw);
113+
console.log("Bridge share bps (effective):", cfg.bridgeShareBps);
114+
console.log("");
115+
116+
if (cfg.bridgeShareBpsRaw == 0) {
117+
console.log("NOTE: BRIDGE_SHARE_BPS=0 defaults to 10000 (constructor behavior).");
118+
}
119+
if (cfg.bridgeShareBps < 10000 && cfg.otherRecipient == address(0)) {
120+
console.log("WARNING: OTHER_RECIPIENT is zero but bridge share < 10000.");
121+
}
122+
if (cfg.hypNativeMinter == address(0)) {
123+
console.log("NOTE: HYP_NATIVE_MINTER is zero; set it before calling sendToCelestia().");
124+
}
125+
console.log("");
126+
127+
console.log("Add this to your genesis.json 'alloc' section:");
128+
console.log("");
129+
console.log("{");
130+
console.log(' "alloc": {');
131+
console.log(' "%s": {', addressKey(cfg.feeVaultAddress));
132+
console.log(' "balance": "0x0",');
133+
console.log(' "code": "%s",', vm.toString(runtimeCode));
134+
console.log(' "storage": {');
135+
console.log(' "0x0": "%s",', vm.toString(slot0));
136+
console.log(' "0x1": "%s",', vm.toString(slot1));
137+
console.log(' "0x2": "%s",', vm.toString(slot2));
138+
console.log(' "0x3": "%s",', vm.toString(slot3));
139+
console.log(' "0x4": "%s",', vm.toString(slot4));
140+
console.log(' "0x5": "%s",', vm.toString(slot5));
141+
console.log(' "0x6": "%s"', vm.toString(slot6));
142+
console.log(" }");
143+
console.log(" }");
144+
console.log(" }");
145+
console.log("}");
146+
console.log("");
147+
console.log("Raw bytecode length:", runtimeCode.length);
148+
console.log("=============================================");
149+
}
150+
}
151+
152+
/// @title GenerateFeeVaultAllocJSON
153+
/// @notice Outputs just the JSON snippet for easy copy-paste
154+
/// @dev Run with: OWNER=0x... forge script script/GenerateFeeVaultAlloc.s.sol:GenerateFeeVaultAllocJSON -vvv
155+
contract GenerateFeeVaultAllocJSON is FeeVaultAllocBase {
156+
function run() external view {
157+
Config memory cfg = loadConfig();
158+
bytes memory runtimeCode = type(FeeVault).runtimeCode;
159+
160+
(bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5, bytes32 slot6) =
161+
computeSlots(cfg);
162+
163+
string memory json = string(
164+
abi.encodePacked(
165+
'{"',
166+
addressKey(cfg.feeVaultAddress),
167+
'":{"balance":"0x0","code":"',
168+
vm.toString(runtimeCode),
169+
'","storage":{"0x0":"',
170+
vm.toString(slot0),
171+
'","0x1":"',
172+
vm.toString(slot1),
173+
'","0x2":"',
174+
vm.toString(slot2),
175+
'","0x3":"',
176+
vm.toString(slot3),
177+
'","0x4":"',
178+
vm.toString(slot4),
179+
'","0x5":"',
180+
vm.toString(slot5),
181+
'","0x6":"',
182+
vm.toString(slot6),
183+
'"}}}'
184+
)
185+
);
186+
187+
console.log(json);
188+
}
189+
}

0 commit comments

Comments
 (0)