Skip to content
Open
26 changes: 11 additions & 15 deletions Cargo.lock

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

102 changes: 58 additions & 44 deletions bundler_tests/DummyBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import {
IERC7786GatewaySource,
IERC7786Recipient
} from "@openzeppelin/contracts/interfaces/draft-IERC7786.sol";
import {CAIP2, CAIP10} from "@openzeppelin/contracts/utils/CAIP10.sol";
import {NoncesKeyed} from "@openzeppelin/contracts/utils/NoncesKeyed.sol";
import {InteroperableAddress} from "@openzeppelin/contracts/utils/draft-InteroperableAddress.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

contract DummyBridge is
Pausable,
Expand All @@ -33,15 +34,17 @@ contract DummyBridge is
bytes32 private immutable LOCAL_CHAIN_K256;
address private EP_ADDRESS;

mapping(string => uint128[6]) private destinationFees;
mapping(uint64 => uint128[6]) private destinationFees;

constructor(address _ep) payable {
entryPoint = IEntryPoint(_ep);
LOCAL_CHAIN_K256 = keccak256(bytes(CAIP2.local()));
LOCAL_CHAIN_K256 = keccak256(
InteroperableAddress.formatEvmV1(block.chainid)
);
EP_ADDRESS = _ep;

// pre-populate
destinationFees[CAIP2.local()] = [
destinationFees[uint64(block.chainid)] = [
uint128(0x100001),
uint128(0x100002),
uint128(0x100003),
Expand All @@ -60,28 +63,43 @@ contract DummyBridge is
_;
}

/// IAccountExecute::executeUserOp()
/// Execution calls
function execute(
address target,
uint256 value,
bytes calldata data
) external onlyEntryPointOrOwner {
_execute(target, value, data);
}

/// Called in the execution phase of UserOp handling.
// function executeBatch(
// address[] calldata targets,
// uint256[] calldata values,
// bytes[] calldata datas
// ) external onlyEntryPointOrOwner {
// uint256 len = targets.length;
// require(len == values.length && len == datas.length);
// for (uint256 i; i < len; ++i) {
// _execute(targets[i], values[i], datas[i]);
// }
// }

function _execute(
address target,
uint256 value,
bytes memory data
) internal {
Address.functionCallWithValue(target, data, value);
}

/// IAccountExecute::executeUserOp()
/// Configuration calls
function executeUserOp(
PackedUserOperation calldata userOp,
bytes32 _userOpHash
) external onlyEntryPointOrOwner {
// 1. Validate the userOp

// 2. Extract the call arguments
bytes32 sendId = keccak256(userOp.callData);
address gateway = address(uint160(userOp.nonce >> 96)); // byte20 prefix with gateway address
address relayer = address(bytes20(userOp.signature[:20])); // byte20 prefix with signer wallet

// Call the gateway
require(
IERC7786Recipient(gateway).receiveMessage(
sendId,
abi.encodePacked(relayer),
userOp.callData
) == IERC7786Recipient.receiveMessage.selector,
"Gateway.receiveMessage() failed"
);
// return success
}

/// IAccount::validateUserOp()
Expand Down Expand Up @@ -136,7 +154,7 @@ contract DummyBridge is
}

function getFees(
string calldata chain_id
uint64 chain_id
) public view virtual returns (uint128[6] memory) {
return destinationFees[chain_id];
}
Expand Down Expand Up @@ -167,19 +185,20 @@ contract DummyBridge is
bytes memory recipient,
bytes memory payload,
uint256 _nonce
) = abi.decode(_payload[4:], (bytes, bytes, bytes, uint256));
) = abi.decode(_payload, (bytes, bytes, bytes, uint256));
// 4. Nonce replay check

// 5. Send to destination
(string memory dst_chain, string memory dst_addr) = CAIP10.parse(
string(recipient)
(uint256 dst_chain, address dst_addr) = InteroperableAddress.parseEvmV1(
recipient
);
require(
keccak256(bytes(dst_chain)) == LOCAL_CHAIN_K256,
keccak256(InteroperableAddress.formatEvmV1(dst_chain)) ==
LOCAL_CHAIN_K256,
"Foreign destination"
);
(string memory src_chain, string memory src_addr) = CAIP10.parse(
string(sender)
(uint256 src_chain, address src_addr) = InteroperableAddress.parseEvmV1(
sender
);

// require(
Expand All @@ -196,26 +215,18 @@ contract DummyBridge is
/// IERC7786GatewaySource::sendMessage()
/// Constructs the cross-chain quad-tuple payload to be relayed.
function sendMessage(
bytes calldata recipient, // CAIP10/EIP155 full address
bytes calldata recipient, // ERC7930
bytes calldata payload,
bytes[] calldata // Stick pricing in here?
bytes[] calldata attributes // Stick pricing in here?
) public payable virtual whenNotPaused returns (bytes32 sendId) {
require(msg.value == 0, "received value");

// retrieve destination fee structure
bytes[] memory attributes = new bytes[](1);
bytes memory feeAttribute = abi.encodeWithSignature(
"feeParams(uint128[6])",
destinationFees[CAIP2.local()]
);
attributes[0] = feeAttribute;

// wrapping the payload
bytes memory sender = bytes(CAIP10.local(msg.sender));
bytes memory sender = InteroperableAddress.formatEvmV1(
block.chainid,
msg.sender
);
uint256 nonce = _useNonce(address(this), uint192(0));

bytes memory wrappedPayload = abi.encodeWithSelector(
IAccountExecute.executeUserOp.selector, // needed to trigger executeUserOp() later
bytes memory wrappedPayload = abi.encode(
sender,
recipient,
payload,
Expand All @@ -225,7 +236,10 @@ contract DummyBridge is
// compute sendId
sendId = keccak256(wrappedPayload);

bytes memory gateway = bytes(CAIP10.local(address(this)));
bytes memory gateway = InteroperableAddress.formatEvmV1(
block.chainid,
address(this)
);

emit MessageSent(
sendId,
Expand Down
4 changes: 2 additions & 2 deletions bundler_tests/config-bundler-spec-tests.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ consensus.genesis_accounts = [
["2B5AD5c4795c026514f8317c7a215E218DcCD6cF", "5000000000000000000000"],
["6813Eb9362372EEF6200f3b1dbC3f819671cBA69", "5000000000000000000000"],
["1efF47bc3a10a45D4B230B5d10E37751FE6AA718", "5000000000000000000000"],
# geth account
# ["71562b71999873db5b286df957af199ec94617f7", "5000000000000000000000"],
# own account
["99F7f7C00526426b8dCA99302e96d85A0e5fd400", "10000000000000000000000"],
# hardhat addresses
["f39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "10000000000000000000000"], # 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
["70997970c51812dc3a010c7d01b50e0d17dc79c8", "10000000000000000000000"], # 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
Expand Down
2 changes: 1 addition & 1 deletion zilliqa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ revm-inspectors = { version = "0.39.0", features = ["js-tracer"] }
revm-precompile = "34.0.0"
revm-inspector = "19.0.0"
revm-context = "16.0.1"
tap-caip = "0.7.0"
ensip25 = "0.4.4"

[dev-dependencies]
alloy = { version = "2.0.4", default-features = false, features = ["consensus", "dyn-abi", "eips", "json-abi", "k256", "rlp", "rpc-types", "rpc-types-trace", "serde", "sol-types", "pubsub", "json-rpc", "contract", "signer-local", "providers", "provider-debug-api"] }
Expand Down
61 changes: 59 additions & 2 deletions zilliqa/src/api/bundler.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
use std::{sync::Arc, time::Duration};

use alloy::{
consensus::TxLegacy,
eips::BlockId,
network::TxSignerSync as _,
primitives::{Address, B256, TxKind},
rpc::types::{
BlockOverrides, TransactionRequest,
state::{AccountOverride, StateOverride},
trace::geth::{GethDebugTracingCallOptions, GethTrace},
},
};
use alloy_rpc_types_trace::geth::{GethDebugTracingCallOptions, GethTrace};
use anyhow::{Result, anyhow};
use anyhow::{Context, Result, anyhow};
use eth_trie::{EthTrie, Trie as _};
use jsonrpsee::{
RpcModule,
Expand All @@ -22,6 +25,7 @@ use crate::{
to_hex::ToHex as _,
},
cfg::EnabledApi,
crypto::Hash,
error::ensure_success,
node::Node,
state::Code,
Expand All @@ -32,6 +36,12 @@ use crate::{
/// Provides bundler-specific API alternatives and implementations.
pub fn rpc_module(node: Arc<Node>, enabled_apis: &[EnabledApi]) -> RpcModule<Arc<Node>> {
let mut module = RpcModule::new(node.clone());
module
.merge(super::web3::rpc_module(node.clone(), enabled_apis))
.unwrap();
module
.merge(super::net::rpc_module(node.clone(), enabled_apis))
.unwrap();
module
.merge(super::eth::rpc_module(node.clone(), enabled_apis))
.unwrap();
Expand All @@ -45,6 +55,12 @@ pub fn rpc_module(node: Arc<Node>, enabled_apis: &[EnabledApi]) -> RpcModule<Arc
enabled_apis,
[
("eth_call", eth_call, HandlerType::Fast),
("eth_accounts", eth_accounts, HandlerType::Fast),
(
"eth_sendTransaction",
eth_send_transaction,
HandlerType::Fast
),
("debug_traceCall", debug_trace_call, HandlerType::Slow),
],
);
Expand All @@ -56,6 +72,47 @@ pub fn rpc_module(node: Arc<Node>, enabled_apis: &[EnabledApi]) -> RpcModule<Arc
module
}

fn eth_accounts(_params: Params, node: &Arc<Node>) -> Result<Vec<Address>> {
let address = node.secret_key.to_evm_address();
Ok(vec![address])
}

// FIXME: DO NOT EXPOSE THIS TO THE PUBLIC
#[cfg(not(debug_assertions))]
fn eth_send_transaction(_params: Params, _node: &Arc<Node>) -> Result<String> {
Err(anyhow!("API method eth_sendTransaction is not available"))
}
// This is only for local development use.
#[cfg(debug_assertions)]
fn eth_send_transaction(params: Params, node: &Arc<Node>) -> Result<String> {
let txn = params.one::<alloy::rpc::types::TransactionRequest>()?;

let address = node.secret_key.to_evm_address();
let block = node.get_block(BlockId::latest())?.context("must exist")?;
let nonce = node.get_state(&block)?.get_account(address)?.nonce;

let mut tx_legacy = TxLegacy {
chain_id: Some(node.chain_id.eth),
gas_price: node.config.consensus.gas_price.0,
gas_limit: txn.gas.unwrap_or_default(),
to: txn.to.unwrap_or(TxKind::Create),
value: txn.value.unwrap_or_default(),
input: txn.input.into_input().unwrap_or_default(),
nonce,
};

let signer = alloy::signers::local::PrivateKeySigner::from_bytes(&B256::from_slice(
node.secret_key.as_bytes().as_slice(),
))?;
let sig = signer.sign_transaction_sync(&mut tx_legacy)?;

let stx = crate::transaction::SignedTransaction::Legacy { tx: tx_legacy, sig };
let vtx = stx.verify_bypass(Hash::ZERO)?;

let (txn_hash, _result) = node.create_transaction(vtx)?;
Ok(txn_hash.0.to_hex())
}

pub fn debug_trace_call(params: Params, node: &Arc<Node>) -> Result<GethTrace> {
let mut params = params.sequence();
let call_params: TransactionRequest = params.next()?;
Expand Down
2 changes: 1 addition & 1 deletion zilliqa/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub fn all_enabled() -> Vec<crate::cfg::EnabledApi> {
}

pub fn bundler_enabled() -> Vec<crate::cfg::EnabledApi> {
["debug", "eth"]
["debug", "eth", "net", "web3"]
.into_iter()
.map(|ns| crate::cfg::EnabledApi::EnableAll(ns.to_owned()))
.collect()
Expand Down
Loading
Loading