Skip to content
This repository has been archived by the owner on Jan 11, 2024. It is now read-only.

FM-99: Ethereum API methods (Part 7) #136

Merged
merged 4 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ arbitrary = { version = "1", features = ["derive"] }
arbtest = "0.2"
async-stm = "0.2"
async-trait = "0.1"
axum = "0.6"
axum = { version = "0.6", features = ["ws"] }
base64 = "0.21"
blake2b_simd = "1.0"
bytes = "1.4"
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ The following command runs unit and integration tests:
make test
```

while the next command builds docker images and runs an end-to-end test using the [SimpleCoin](./fendermint/rpc/examples/simplecoin.rs) example:
while the next command builds docker images and runs an end-to-end test using the
[SimpleCoin](./fendermint/rpc/examples/simplecoin.rs) and the
[ethers](./fendermint/eth/api/examples/ethers.rs) examples:

```bash
make e2e
Expand Down
4 changes: 3 additions & 1 deletion fendermint/eth/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ license.workspace = true
anyhow = { workspace = true }
axum = { workspace = true }
ethers-core = { workspace = true }
futures = { workspace = true }
hex = { workspace = true }
jsonrpc-v2 = { workspace = true }
lazy_static = { workspace = true }
libsecp256k1 = { workspace = true }
paste = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tracing = { workspace = true }
tendermint = { workspace = true }
Expand All @@ -35,12 +37,12 @@ ethers = { workspace = true, features = ["abigen"] }
hex = { workspace = true }
lazy_static = { workspace = true }
rand = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
quickcheck = { workspace = true }
quickcheck_macros = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }

fendermint_testing = { path = "../../testing", features = ["arb"] }
fendermint_vm_message = { path = "../../vm/message", features = ["arb"] }
78 changes: 59 additions & 19 deletions fendermint/eth/api/examples/ethers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ use ethers_core::{
k256::ecdsa::SigningKey,
types::{
transaction::eip2718::TypedTransaction, Address, BlockId, BlockNumber, Bytes,
Eip1559TransactionRequest, SyncingStatus, TransactionReceipt, H160, H256, U256, U64,
Eip1559TransactionRequest, Filter, SyncingStatus, TransactionReceipt, H160, H256, U256,
U64,
},
};
use fendermint_rpc::message::MessageFactory;
Expand Down Expand Up @@ -208,15 +209,13 @@ impl TestAccount {
// - eth_getCode
// - eth_syncing
// - web3_clientVersion
// - eth_getLogs
//
// DOING:
//
// TODO:
//
// - eth_getLogs
// - eth_newBlockFilter
// - eth_newPendingTransactionFilter
// - eth_newPendingTransactionFilter
// - eth_newFilter
// - eth_uninstallFilter
// - eth_getFilterChanges
Expand Down Expand Up @@ -336,7 +335,7 @@ async fn run(provider: Provider<Http>, opts: Options) -> anyhow::Result<()> {

tracing::info!("sending example transfer");

let transfer = make_transfer(&mw, to)
let transfer = make_transfer(&mw, &to)
.await
.context("failed to make a transfer")?;

Expand All @@ -351,17 +350,23 @@ async fn run(provider: Provider<Http>, opts: Options) -> anyhow::Result<()> {
tracing::info!(height = ?bn, ?tx_hash, "example transfer");

// Get a block with transactions by number.
request(
"eth_getBlockByNumber /w txns",
let block = request(
"eth_getBlockByNumber w/ txns",
provider
.get_block_with_txs(BlockId::Number(BlockNumber::Number(bn)))
.await,
|b| b.is_some() && b.as_ref().map(|b| b.number).flatten() == Some(bn),
)?;

assert_eq!(
block.unwrap().transactions[0].hash,
tx_hash,
"computed hash should match"
);

// Get the block with transactions by hash.
request(
"eth_getBlockByHash /w txns",
"eth_getBlockByHash w/ txns",
provider.get_block_with_txs(BlockId::Hash(bh)).await,
|b| b.is_some() && b.as_ref().map(|b| b.number).flatten() == Some(bn),
)?;
Expand Down Expand Up @@ -413,15 +418,22 @@ async fn run(provider: Provider<Http>, opts: Options) -> anyhow::Result<()> {
)?;

request(
"eth_estimateGas",
"eth_estimateGas w/ height",
provider.estimate_gas(&probe_tx, Some(probe_height)).await,
|gas: &U256| !gas.is_zero(),
)?;

request(
"eth_estimateGas w/o height",
provider.estimate_gas(&probe_tx, None).await,
|gas: &U256| !gas.is_zero(),
)?;

tracing::info!("deploying SimpleCoin");

let bytecode =
Bytes::from(hex::decode(SIMPLECOIN_HEX).context("failed to decode contract hex")?);

let abi = serde_json::from_str::<ethers::core::abi::Abi>(SIMPLECOIN_ABI)?;

let factory = ContractFactory::new(abi, bytecode.clone(), mw.clone());
Expand All @@ -430,7 +442,9 @@ async fn run(provider: Provider<Http>, opts: Options) -> anyhow::Result<()> {
// Fill the fields so we can debug any difference between this and the node.
// Using `Some` block ID because with `None` the eth_estimateGas call would receive invalid parameters.
mw.fill_transaction(&mut deployer.tx, Some(BlockId::Number(BlockNumber::Latest)))
.await?;
.await
.context("failed to fill deploy transaction")?;

tracing::info!(sighash = ?deployer.tx.sighash(), "deployment tx");

// NOTE: This will call eth_estimateGas to figure out how much gas to use, because we don't set it,
Expand All @@ -453,15 +467,10 @@ async fn run(provider: Provider<Http>, opts: Options) -> anyhow::Result<()> {
let _bn = receipt.block_number.unwrap();
let _bh = receipt.block_hash.unwrap();

let mut coin_call: TestContractCall<U256> = contract.get_balance(from.eth_addr);
mw.fill_transaction(
&mut coin_call.tx,
Some(BlockId::Number(BlockNumber::Latest)),
)
.await
.context("failed to fill call transaction")?;
let coin_balance: TestContractCall<U256> =
prepare_call(&mw, contract.get_balance(from.eth_addr)).await?;

request("eth_call", coin_call.call().await, |coin_balance| {
request("eth_call", coin_balance.call().await, |coin_balance| {
*coin_balance == U256::from(10000)
})?;

Expand Down Expand Up @@ -504,10 +513,28 @@ async fn run(provider: Provider<Http>, opts: Options) -> anyhow::Result<()> {
*s == SyncingStatus::IsFalse // There is only one node.
})?;

// Send a SimpleCoin transaction to get an event emitted.
// Not using `prepare_call` here because `send_transaction` will fill the missing fields.
let coin_send: TestContractCall<bool> = contract.send_coin(to.eth_addr, U256::from(100));
// Using `send_transaction` instead of `coin_send.send()` so it gets the receipt.
// Unfortunately the returned `bool` is not available through the Ethereum API.
let receipt = request(
"eth_sendRawTransaction",
send_transaction(&mw, coin_send.tx).await,
|receipt| !receipt.logs.is_empty(),
)?;

request(
"eth_getLogs",
mw.get_logs(&Filter::new().at_block_hash(receipt.block_hash.unwrap()))
.await,
|logs| *logs == receipt.logs,
)?;

Ok(())
}

async fn make_transfer(mw: &TestMiddleware, to: TestAccount) -> anyhow::Result<TypedTransaction> {
async fn make_transfer(mw: &TestMiddleware, to: &TestAccount) -> anyhow::Result<TypedTransaction> {
// Create a transaction to transfer 1000 atto.
let tx = Eip1559TransactionRequest::new().to(to.eth_addr).value(1000);

Expand All @@ -524,6 +551,7 @@ async fn make_transfer(mw: &TestMiddleware, to: TestAccount) -> anyhow::Result<T
Ok(tx)
}

/// Send a transaction and await the receipt.
async fn send_transaction(
mw: &TestMiddleware,
tx: TypedTransaction,
Expand Down Expand Up @@ -556,3 +584,15 @@ fn make_middleware(

Ok(SignerMiddleware::new(provider, wallet))
}

/// Fill the transaction fields such as gas and nonce.
async fn prepare_call<T>(
mw: &TestMiddleware,
mut call: TestContractCall<T>,
) -> anyhow::Result<TestContractCall<T>> {
mw.fill_transaction(&mut call.tx, Some(BlockId::Number(BlockNumber::Latest)))
.await
.context("failed to fill transaction")?;

Ok(call)
}
Loading