Fuel Validators (#1474)

* Fuel ABI gen

* WIP

* Progress

* Use HyperlaneDomain

* Progress on fuel init

* Support unknown domains

* Work on handling wallets and signers for fuel

* Move signer output test

* Work on signers

* Fixing up compilation

* Update to use HyperlaneDomain

* Fixes

* WIP

* Update Mailbox ABI

* Move signer to be in chain conf and change rpc style to implementation

* Better loader debugging

* WIP

* Merge fixes and refactor

* Fix "rpcStyle" after merge

* Simplify chain mappings

* Fix tests

* Remove excess runtime deps from validator

* Use agent core in scraper and remove DB assumptions

* More core cleanup

* Remove duplication for checkpoint syncers

* Better error messages for debugging

* Debugging

* Cleanup

* remove signers

* fmt

* Fix after merge

* Fixes after merge

* Fixes after merge

* remove random inclusion

* Remove unused files

* Requested changes from PR

* Simplify deps and update sha3

* Fix after merge
pull/1587/head
Mattie Conover 2 years ago committed by GitHub
parent 1748b60f4b
commit e5e794eda4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1088
      rust/Cargo.lock
  2. 8
      rust/agents/validator/src/submit.rs
  3. 5
      rust/agents/validator/src/validator.rs
  4. 2
      rust/chains/hyperlane-ethereum/Cargo.toml
  5. 4
      rust/chains/hyperlane-ethereum/build.rs
  6. 1
      rust/chains/hyperlane-fuel/.gitignore
  7. 9
      rust/chains/hyperlane-fuel/Cargo.toml
  8. 530
      rust/chains/hyperlane-fuel/abis/Mailbox.abi.json
  9. 3
      rust/chains/hyperlane-fuel/build.rs
  10. 56
      rust/chains/hyperlane-fuel/src/conversions.rs
  11. 9
      rust/chains/hyperlane-fuel/src/lib.rs
  12. 83
      rust/chains/hyperlane-fuel/src/mailbox.rs
  13. 30
      rust/chains/hyperlane-fuel/src/trait_builder.rs
  14. 2
      rust/ethers-prometheus/Cargo.toml
  15. 2
      rust/ethers-prometheus/build.rs
  16. 2
      rust/hyperlane-base/Cargo.toml
  17. 36
      rust/hyperlane-base/src/settings/chains.rs
  18. 16
      rust/hyperlane-base/src/settings/loader.rs
  19. 1
      rust/hyperlane-base/src/settings/mod.rs
  20. 14
      rust/hyperlane-base/src/settings/signers.rs
  21. 21
      rust/hyperlane-base/src/types/local_storage.rs
  22. 4
      rust/hyperlane-core/Cargo.toml
  23. 6
      rust/hyperlane-core/src/accumulator/mod.rs
  24. 6
      rust/hyperlane-core/src/chain.rs
  25. 4
      rust/hyperlane-core/src/error.rs
  26. 0
      rust/hyperlane-core/src/test_output.rs
  27. 23
      rust/hyperlane-core/src/test_utils.rs
  28. 6
      rust/hyperlane-core/src/traits/indexer.rs
  29. 2
      rust/hyperlane-core/src/traits/provider.rs
  30. 4
      rust/hyperlane-core/src/types/announcement.rs
  31. 2
      rust/hyperlane-core/src/types/checkpoint.rs
  32. 2
      rust/hyperlane-core/src/types/message.rs
  33. 6
      rust/hyperlane-core/src/utils.rs
  34. 10
      rust/utils/abigen/Cargo.toml
  35. 106
      rust/utils/abigen/src/lib.rs

1088
rust/Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -11,7 +11,7 @@ use hyperlane_base::{CheckpointSyncer, CoreMetrics};
use hyperlane_core::{Announcement, HyperlaneDomain, HyperlaneSigner, HyperlaneSignerExt, Mailbox};
pub(crate) struct ValidatorSubmitter {
interval: u64,
interval: Duration,
reorg_period: Option<NonZeroU64>,
signer: Arc<dyn HyperlaneSigner>,
mailbox: Arc<dyn Mailbox>,
@ -21,7 +21,7 @@ pub(crate) struct ValidatorSubmitter {
impl ValidatorSubmitter {
pub(crate) fn new(
interval: u64,
interval: Duration,
reorg_period: u64,
mailbox: Arc<dyn Mailbox>,
signer: Arc<dyn HyperlaneSigner>,
@ -64,7 +64,7 @@ impl ValidatorSubmitter {
// more details.
while self.mailbox.count().await? == 0 {
info!("Waiting for non-zero mailbox size");
sleep(Duration::from_secs(self.interval)).await;
sleep(self.interval).await;
}
// current_index will be None if the validator cannot find
@ -140,7 +140,7 @@ impl ValidatorSubmitter {
.set(signed_checkpoint.value.index as i64);
}
sleep(Duration::from_secs(self.interval)).await;
sleep(self.interval).await;
}
}
}

@ -1,4 +1,5 @@
use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use eyre::{Context, Result};
@ -20,7 +21,7 @@ pub struct Validator {
mailbox: Arc<dyn Mailbox>,
signer: Arc<dyn HyperlaneSigner>,
reorg_period: u64,
interval: u64,
interval: Duration,
checkpoint_syncer: Arc<dyn CheckpointSyncer>,
}
@ -47,7 +48,7 @@ impl BaseAgent for Validator {
.await
.map(|validator| Arc::new(validator) as Arc<dyn HyperlaneSigner>)?;
let reorg_period = settings.reorgperiod.parse().expect("invalid uint");
let interval = settings.interval.parse().expect("invalid uint");
let interval = Duration::from_secs(settings.interval.parse().expect("invalid uint"));
let core = settings.build_hyperlane_core(metrics.clone());
let checkpoint_syncer = settings.checkpointsyncer.build(None)?.into();

@ -26,4 +26,4 @@ hyperlane-core = { path = "../../hyperlane-core" }
ethers-prometheus = { path = "../../ethers-prometheus", features = ["serde"] }
[build-dependencies]
abigen = { path = "../../utils/abigen" }
abigen = { path = "../../utils/abigen", features = ["ethers"] }

@ -1,5 +1,3 @@
use abigen::generate_bindings_for_dir;
fn main() {
generate_bindings_for_dir("./abis", "./src/contracts");
abigen::generate_bindings_for_dir("./abis", "./src/contracts", abigen::BuildType::Ethers);
}

@ -0,0 +1 @@
src/contracts

@ -6,7 +6,14 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
fuels = "0.31"
fuels = "0.33"
hyperlane-core = { path = "../../hyperlane-core" }
async-trait = { version = "0.1", default-features = false }
thiserror = "1.0"
serde = "1.0"
anyhow = "1.0"
tracing-futures = "0.2"
tracing = "0.1"
[build-dependencies]
abigen = { path = "../../utils/abigen", features = ["fuels"] }

@ -0,0 +1,530 @@
{
"types": [
{
"typeId": 0,
"type": "()",
"components": [],
"typeParameters": null
},
{
"typeId": 1,
"type": "(_, _)",
"components": [
{
"name": "__tuple_element",
"type": 2,
"typeArguments": null
},
{
"name": "__tuple_element",
"type": 20,
"typeArguments": null
}
],
"typeParameters": null
},
{
"typeId": 2,
"type": "b256",
"components": null,
"typeParameters": null
},
{
"typeId": 3,
"type": "bool",
"components": null,
"typeParameters": null
},
{
"typeId": 4,
"type": "enum Identity",
"components": [
{
"name": "Address",
"type": 14,
"typeArguments": null
},
{
"name": "ContractId",
"type": 15,
"typeArguments": null
}
],
"typeParameters": null
},
{
"typeId": 5,
"type": "enum Option",
"components": [
{
"name": "None",
"type": 0,
"typeArguments": null
},
{
"name": "Some",
"type": 6,
"typeArguments": null
}
],
"typeParameters": [
6
]
},
{
"typeId": 6,
"type": "generic T",
"components": null,
"typeParameters": null
},
{
"typeId": 7,
"type": "raw untyped ptr",
"components": null,
"typeParameters": null
},
{
"typeId": 8,
"type": "str[12]",
"components": null,
"typeParameters": null
},
{
"typeId": 9,
"type": "str[16]",
"components": null,
"typeParameters": null
},
{
"typeId": 10,
"type": "str[6]",
"components": null,
"typeParameters": null
},
{
"typeId": 11,
"type": "str[7]",
"components": null,
"typeParameters": null
},
{
"typeId": 12,
"type": "str[8]",
"components": null,
"typeParameters": null
},
{
"typeId": 13,
"type": "str[9]",
"components": null,
"typeParameters": null
},
{
"typeId": 14,
"type": "struct Address",
"components": [
{
"name": "value",
"type": 2,
"typeArguments": null
}
],
"typeParameters": null
},
{
"typeId": 15,
"type": "struct ContractId",
"components": [
{
"name": "value",
"type": 2,
"typeArguments": null
}
],
"typeParameters": null
},
{
"typeId": 16,
"type": "struct Message",
"components": [
{
"name": "version",
"type": 22,
"typeArguments": null
},
{
"name": "nonce",
"type": 20,
"typeArguments": null
},
{
"name": "origin",
"type": 20,
"typeArguments": null
},
{
"name": "sender",
"type": 2,
"typeArguments": null
},
{
"name": "destination",
"type": 20,
"typeArguments": null
},
{
"name": "recipient",
"type": 2,
"typeArguments": null
},
{
"name": "body",
"type": 19,
"typeArguments": [
{
"name": "",
"type": 22,
"typeArguments": null
}
]
}
],
"typeParameters": null
},
{
"typeId": 17,
"type": "struct OwnershipTransferredEvent",
"components": [
{
"name": "previous_owner",
"type": 5,
"typeArguments": [
{
"name": "",
"type": 4,
"typeArguments": null
}
]
},
{
"name": "new_owner",
"type": 5,
"typeArguments": [
{
"name": "",
"type": 4,
"typeArguments": null
}
]
}
],
"typeParameters": null
},
{
"typeId": 18,
"type": "struct RawVec",
"components": [
{
"name": "ptr",
"type": 7,
"typeArguments": null
},
{
"name": "cap",
"type": 21,
"typeArguments": null
}
],
"typeParameters": [
6
]
},
{
"typeId": 19,
"type": "struct Vec",
"components": [
{
"name": "buf",
"type": 18,
"typeArguments": [
{
"name": "",
"type": 6,
"typeArguments": null
}
]
},
{
"name": "len",
"type": 21,
"typeArguments": null
}
],
"typeParameters": [
6
]
},
{
"typeId": 20,
"type": "u32",
"components": null,
"typeParameters": null
},
{
"typeId": 21,
"type": "u64",
"components": null,
"typeParameters": null
},
{
"typeId": 22,
"type": "u8",
"components": null,
"typeParameters": null
}
],
"functions": [
{
"inputs": [],
"name": "count",
"output": {
"name": "",
"type": 20,
"typeArguments": null
}
},
{
"inputs": [
{
"name": "message_id",
"type": 2,
"typeArguments": null
}
],
"name": "delivered",
"output": {
"name": "",
"type": 3,
"typeArguments": null
}
},
{
"inputs": [
{
"name": "destination_domain",
"type": 20,
"typeArguments": null
},
{
"name": "recipient",
"type": 2,
"typeArguments": null
},
{
"name": "message_body",
"type": 19,
"typeArguments": [
{
"name": "",
"type": 22,
"typeArguments": null
}
]
}
],
"name": "dispatch",
"output": {
"name": "",
"type": 2,
"typeArguments": null
}
},
{
"inputs": [],
"name": "get_default_ism",
"output": {
"name": "",
"type": 15,
"typeArguments": null
}
},
{
"inputs": [],
"name": "latest_checkpoint",
"output": {
"name": "",
"type": 1,
"typeArguments": null
}
},
{
"inputs": [
{
"name": "metadata",
"type": 19,
"typeArguments": [
{
"name": "",
"type": 22,
"typeArguments": null
}
]
},
{
"name": "_message",
"type": 16,
"typeArguments": null
}
],
"name": "process",
"output": {
"name": "",
"type": 0,
"typeArguments": null
}
},
{
"inputs": [],
"name": "root",
"output": {
"name": "",
"type": 2,
"typeArguments": null
}
},
{
"inputs": [
{
"name": "module",
"type": 15,
"typeArguments": null
}
],
"name": "set_default_ism",
"output": {
"name": "",
"type": 0,
"typeArguments": null
}
},
{
"inputs": [],
"name": "owner",
"output": {
"name": "",
"type": 5,
"typeArguments": [
{
"name": "",
"type": 4,
"typeArguments": null
}
]
}
},
{
"inputs": [
{
"name": "new_owner",
"type": 5,
"typeArguments": [
{
"name": "",
"type": 4,
"typeArguments": null
}
]
}
],
"name": "transfer_ownership",
"output": {
"name": "",
"type": 0,
"typeArguments": null
}
}
],
"loggedTypes": [
{
"logId": 0,
"loggedType": {
"name": "",
"type": 8,
"typeArguments": null
}
},
{
"logId": 1,
"loggedType": {
"name": "",
"type": 9,
"typeArguments": null
}
},
{
"logId": 2,
"loggedType": {
"name": "",
"type": 12,
"typeArguments": null
}
},
{
"logId": 3,
"loggedType": {
"name": "",
"type": 8,
"typeArguments": null
}
},
{
"logId": 4,
"loggedType": {
"name": "",
"type": 13,
"typeArguments": null
}
},
{
"logId": 5,
"loggedType": {
"name": "",
"type": 11,
"typeArguments": null
}
},
{
"logId": 6,
"loggedType": {
"name": "",
"type": 2,
"typeArguments": null
}
},
{
"logId": 7,
"loggedType": {
"name": "",
"type": 10,
"typeArguments": null
}
},
{
"logId": 8,
"loggedType": {
"name": "",
"type": 10,
"typeArguments": null
}
},
{
"logId": 9,
"loggedType": {
"name": "",
"type": 17,
"typeArguments": []
}
}
],
"messagesTypes": []
}

@ -0,0 +1,3 @@
fn main() {
abigen::generate_bindings_for_dir("./abis", "./src/contracts", abigen::BuildType::Fuels);
}

@ -0,0 +1,56 @@
use hyperlane_core::H256;
/// Conversion from a fuel type to H256 primitive.
pub trait FuelIntoH256 {
/// Covert to an H256 primitive.
fn into_h256(self) -> H256;
}
/// Conversion from an H256 primitive to a type to an H256 primitive
pub trait FuelFromH256 {
/// Convert an H256 primitive to this type.
fn from_h256(v: &H256) -> Self;
}
macro_rules! impl_h256 {
($type:ty, $from_method:expr, $into_method:expr) => {
impl FuelIntoH256 for $type {
fn into_h256(self) -> H256 {
let method: fn($type) -> H256 = $into_method;
method(self)
}
}
impl FuelIntoH256 for &$type {
fn into_h256(self) -> H256 {
let method: fn($type) -> H256 = $into_method;
method(self.clone())
}
}
impl FuelFromH256 for $type {
fn from_h256(v: &H256) -> Self {
let method: fn(&H256) -> $type = $from_method;
method(v)
}
}
};
}
impl_h256!(
fuels::prelude::Bech32ContractId,
|v| fuels::prelude::Bech32ContractId::from(fuels::prelude::ContractId::new(v.0)),
|v| H256::from(*v.hash)
);
impl_h256!(
fuels::prelude::Bits256,
|v| fuels::prelude::Bits256(v.0),
|v| H256::from(v.0)
);
impl_h256!(
fuels::prelude::ContractId,
|v| fuels::prelude::ContractId::new(v.0),
|v| H256::from(<[u8; 32]>::from(v))
);

@ -9,8 +9,17 @@ pub use interchain_gas::*;
pub use mailbox::*;
pub use multisig_ism::*;
pub use provider::*;
pub use trait_builder::*;
mod contracts;
mod conversions;
mod interchain_gas;
mod mailbox;
mod multisig_ism;
mod provider;
mod trait_builder;
/// Safe default imports of commonly used traits/types.
pub mod prelude {
pub use crate::conversions::*;
}

@ -1,53 +1,115 @@
use std::collections::HashMap;
use std::fmt::Debug;
use std::fmt::{Debug, Formatter};
use std::num::NonZeroU64;
use async_trait::async_trait;
use fuels::prelude::{Bech32ContractId, WalletUnlocked};
use tracing::instrument;
use hyperlane_core::{
ChainResult, Checkpoint, HyperlaneAbi, HyperlaneChain, HyperlaneContract, HyperlaneDomain,
HyperlaneMessage, Indexer, LogMeta, Mailbox, MailboxIndexer, TxCostEstimate, TxOutcome, H256,
U256,
ChainCommunicationError, ChainResult, Checkpoint, ContractLocator, HyperlaneAbi,
HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, Indexer, LogMeta,
Mailbox, MailboxIndexer, TxCostEstimate, TxOutcome, H256, U256,
};
use crate::{
contracts::mailbox::Mailbox as FuelMailboxInner, conversions::*, make_provider, ConnectionConf,
};
/// A reference to a Mailbox contract on some Fuel chain
#[derive(Debug)]
pub struct FuelMailbox {}
pub struct FuelMailbox {
contract: FuelMailboxInner,
domain: HyperlaneDomain,
}
impl FuelMailbox {
/// Create a new fuel mailbox
pub fn new(
conf: &ConnectionConf,
locator: ContractLocator,
mut wallet: WalletUnlocked,
) -> ChainResult<Self> {
let provider = make_provider(conf)?;
wallet.set_provider(provider);
let address = Bech32ContractId::from_h256(&locator.address);
Ok(FuelMailbox {
contract: FuelMailboxInner::new(address, wallet),
domain: locator.domain,
})
}
}
impl HyperlaneContract for FuelMailbox {
fn address(&self) -> H256 {
todo!()
self.contract.get_contract_id().into_h256()
}
}
impl HyperlaneChain for FuelMailbox {
fn domain(&self) -> &HyperlaneDomain {
todo!()
&self.domain
}
}
impl Debug for FuelMailbox {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self as &dyn HyperlaneContract)
}
}
#[async_trait]
impl Mailbox for FuelMailbox {
#[instrument(err, ret, skip(self))]
async fn count(&self) -> ChainResult<u32> {
todo!()
self.contract
.methods()
.count()
.simulate()
.await
.map(|r| r.value)
.map_err(ChainCommunicationError::from_other)
}
#[instrument(err, ret, skip(self))]
async fn delivered(&self, id: H256) -> ChainResult<bool> {
todo!()
}
#[instrument(err, ret, skip(self))]
async fn latest_checkpoint(&self, lag: Option<NonZeroU64>) -> ChainResult<Checkpoint> {
todo!()
assert!(
lag.is_none(),
"Fuel does not support querying point-in-time"
);
let (root, index) = self
.contract
.methods()
.latest_checkpoint()
.simulate()
.await
.map_err(ChainCommunicationError::from_other)?
.value;
Ok(Checkpoint {
mailbox_address: self.address(),
mailbox_domain: self.domain.id(),
root: root.into_h256(),
index,
})
}
#[instrument(err, ret, skip(self))]
async fn default_ism(&self) -> ChainResult<H256> {
todo!()
}
#[instrument(err, ret, skip(self))]
async fn recipient_ism(&self, recipient: H256) -> ChainResult<H256> {
todo!()
}
#[instrument(err, ret, skip(self))]
async fn process(
&self,
message: &HyperlaneMessage,
@ -57,6 +119,7 @@ impl Mailbox for FuelMailbox {
todo!()
}
#[instrument(err, ret, skip(self))]
async fn process_estimate_costs(
&self,
message: &HyperlaneMessage,

@ -0,0 +1,30 @@
use fuels::client::FuelClient;
use fuels::prelude::Provider;
use hyperlane_core::{ChainCommunicationError, ChainResult};
/// Fuel connection configuration
#[derive(Debug, serde::Deserialize, Clone)]
pub struct ConnectionConf {
/// Fully qualified string to connect to
url: String,
}
#[derive(thiserror::Error, Debug)]
#[error(transparent)]
struct FuelNewConnectionError(#[from] anyhow::Error);
impl From<FuelNewConnectionError> for ChainCommunicationError {
fn from(err: FuelNewConnectionError) -> Self {
ChainCommunicationError::from_other(err)
}
}
fn make_client(conf: &ConnectionConf) -> ChainResult<FuelClient> {
FuelClient::new(&conf.url).map_err(|e| FuelNewConnectionError(e).into())
}
/// Create a new fuel provider and connection
pub fn make_provider(conf: &ConnectionConf) -> ChainResult<Provider> {
Ok(Provider::new(make_client(conf)?))
}

@ -23,7 +23,7 @@ serde_json = { version = "1.0", default-features = false }
primitive-types = { version = "*", features = ["fp-conversion"] }
[build-dependencies]
abigen = { path = "../utils/abigen" }
abigen = { path = "../utils/abigen", features = ["ethers"] }
[features]
default = []

@ -1,3 +1,3 @@
fn main() {
abigen::generate_bindings_for_dir("./abis", "./src/contracts")
abigen::generate_bindings_for_dir("./abis", "./src/contracts", abigen::BuildType::Ethers)
}

@ -12,6 +12,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", default-features = false }
serde_path_to_error = "0.1"
ethers = { git = "https://github.com/hyperlane-xyz/ethers-rs", tag = "2023-01-10-04" }
fuels = "0.33"
thiserror = "1.0"
async-trait = { version = "0.1", default-features = false }
futures-util = "0.3"
@ -29,6 +30,7 @@ backtrace-oneline = { path = "../utils/backtrace-oneline", optional = true }
ethers-prometheus = { path = "../ethers-prometheus", features = ["serde"] }
hyperlane-core = { path = "../hyperlane-core" }
hyperlane-ethereum = { path = "../chains/hyperlane-ethereum" }
hyperlane-fuel = { path = "../chains/hyperlane-fuel" }
hyperlane-test = { path = "../hyperlane-test" }
paste = "1.0"
tracing-error = "0.2"

@ -1,7 +1,8 @@
use std::collections::HashMap;
use ethers::prelude::Selector;
use eyre::{eyre, Context, Result};
use serde::Deserialize;
use std::collections::HashMap;
use ethers_prometheus::middleware::{
ChainInfo, ContractInfo, PrometheusMiddlewareConf, WalletInfo,
@ -14,6 +15,7 @@ use hyperlane_core::{
use hyperlane_ethereum::{
self as h_eth, BuildableWithProvider, EthereumInterchainGasPaymasterAbi, EthereumMailboxAbi,
};
use hyperlane_fuel::{self as h_fuel, prelude::*};
use crate::settings::signers::BuildableWithSignerConf;
use crate::{CoreMetrics, SignerConf};
@ -27,14 +29,14 @@ pub enum ChainConf {
/// Ethereum configuration
Ethereum(h_eth::ConnectionConf),
/// Fuel configuration
Fuel,
Fuel(h_fuel::ConnectionConf),
}
impl ChainConf {
fn protocol(&self) -> HyperlaneDomainProtocol {
match self {
ChainConf::Ethereum(_) => HyperlaneDomainProtocol::Ethereum,
ChainConf::Fuel => HyperlaneDomainProtocol::Fuel,
ChainConf::Fuel(_) => HyperlaneDomainProtocol::Fuel,
}
}
}
@ -148,7 +150,7 @@ impl ChainSetup {
.await
}
ChainConf::Fuel => todo!(),
ChainConf::Fuel(_) => todo!(),
}
.context("Building provider")
}
@ -163,7 +165,12 @@ impl ChainSetup {
.await
}
ChainConf::Fuel => todo!(),
ChainConf::Fuel(conf) => {
let wallet = self.fuel_signer().await?;
hyperlane_fuel::FuelMailbox::new(conf, locator, wallet)
.map(|m| Box::new(m) as Box<dyn Mailbox>)
.map_err(Into::into)
}
}
.context("Building mailbox")
}
@ -188,7 +195,7 @@ impl ChainSetup {
.await
}
ChainConf::Fuel => todo!(),
ChainConf::Fuel(_) => todo!(),
}
.context("Building mailbox indexer")
}
@ -212,7 +219,7 @@ impl ChainSetup {
.await
}
ChainConf::Fuel => todo!(),
ChainConf::Fuel(_) => todo!(),
}
.context("Building IGP")
}
@ -238,7 +245,7 @@ impl ChainSetup {
.await
}
ChainConf::Fuel => todo!(),
ChainConf::Fuel(_) => todo!(),
}
.context("Building IGP indexer")
}
@ -260,7 +267,7 @@ impl ChainSetup {
.await
}
ChainConf::Fuel => todo!(),
ChainConf::Fuel(_) => todo!(),
}
.context("Building multisig ISM")
}
@ -290,6 +297,12 @@ impl ChainSetup {
self.signer().await
}
async fn fuel_signer(&self) -> Result<fuels::prelude::WalletUnlocked> {
self.signer().await.and_then(|opt| {
opt.ok_or_else(|| eyre!("Fuel requires a signer to construct contract instances"))
})
}
/// Get a clone of the ethereum metrics conf with correctly configured
/// contract information.
fn metrics_conf(
@ -341,7 +354,10 @@ impl ChainSetup {
.parse::<ethers::types::Address>()
.context("Invalid ethereum address")?
.into(),
ChainConf::Fuel => todo!(),
ChainConf::Fuel(_) => address
.parse::<fuels::tx::ContractId>()
.map_err(|e| eyre!("Invalid fuel contract id: {e}"))?
.into_h256(),
};
Ok(ContractLocator { domain, address })

@ -1,8 +1,10 @@
use config::{Config, Environment, File};
use serde::Deserialize;
use std::collections::HashMap;
use std::env;
use config::{Config, Environment, File};
use eyre::{Context, Result};
use serde::Deserialize;
/// Load a settings object from the config locations.
///
/// Read settings from the config files and/or env
@ -27,7 +29,7 @@ pub(crate) fn load_settings_object<'de, T: Deserialize<'de>, S: AsRef<str>>(
agent_prefix: &str,
config_file_name: Option<&str>,
ignore_prefixes: &[S],
) -> eyre::Result<T> {
) -> Result<T> {
let env = env::var("RUN_ENV").unwrap_or_else(|_| "default".into());
// Derive additional prefix from agent name
@ -69,5 +71,11 @@ pub(crate) fn load_settings_object<'de, T: Deserialize<'de>, S: AsRef<str>>(
)
.build()?;
Ok(serde_path_to_error::deserialize(config_deserializer)?)
match serde_path_to_error::deserialize(config_deserializer) {
Ok(cfg) => Ok(cfg),
Err(err) => {
let ctx = format!("Invalid config at `{}`", err.path());
Err(err).context(ctx)
}
}
}

@ -150,7 +150,6 @@ impl Settings {
settings: self.clone(),
}
}
/// Try to get a map of chain name -> mailbox contract
pub async fn build_all_mailboxes(
&self,

@ -75,3 +75,17 @@ impl BuildableWithSignerConf for hyperlane_ethereum::Signers {
})
}
}
#[async_trait]
impl BuildableWithSignerConf for fuels::prelude::WalletUnlocked {
async fn build(conf: &SignerConf) -> Result<Self, Report> {
Ok(match conf {
SignerConf::HexKey { key } => {
let key = key.as_ref().parse()?;
fuels::prelude::WalletUnlocked::new_from_private_key(key, None)
}
SignerConf::Aws { .. } => bail!("Aws signer is not supported by fuel"),
SignerConf::Node => bail!("Node signer is not supported by fuel"),
})
}
}

@ -1,5 +1,5 @@
use async_trait::async_trait;
use eyre::Result;
use eyre::{Context, Result};
use prometheus::IntGauge;
use hyperlane_core::{SignedAnnouncement, SignedCheckpoint};
@ -31,7 +31,10 @@ impl LocalStorage {
}
async fn write_index(&self, index: u32) -> Result<()> {
tokio::fs::write(self.latest_index_file_path(), index.to_string()).await?;
let path = self.latest_index_file_path();
tokio::fs::write(&path, index.to_string())
.await
.with_context(|| format!("Writing index to {path}"))?;
Ok(())
}
@ -72,11 +75,10 @@ impl CheckpointSyncer for LocalStorage {
async fn write_checkpoint(&self, signed_checkpoint: &SignedCheckpoint) -> Result<()> {
let serialized_checkpoint = serde_json::to_string_pretty(signed_checkpoint)?;
tokio::fs::write(
self.checkpoint_file_path(signed_checkpoint.value.index),
&serialized_checkpoint,
)
.await?;
let path = self.checkpoint_file_path(signed_checkpoint.value.index);
tokio::fs::write(&path, &serialized_checkpoint)
.await
.with_context(|| format!("Writing checkpoint to {path}"))?;
match self.latest_index().await? {
Some(current_latest_index) => {
@ -92,7 +94,10 @@ impl CheckpointSyncer for LocalStorage {
async fn write_announcement(&self, signed_announcement: &SignedAnnouncement) -> Result<()> {
let serialized_announcement = serde_json::to_string_pretty(signed_announcement)?;
tokio::fs::write(self.announcement_file_path(), &serialized_announcement).await?;
let path = self.announcement_file_path();
tokio::fs::write(&path, &serialized_announcement)
.await
.with_context(|| format!("Writing announcement to {path}"))?;
Ok(())
}

@ -7,12 +7,12 @@ edition = "2021"
[dependencies]
auto_impl = "1.0"
ethers = { git = "https://github.com/hyperlane-xyz/ethers-rs", tag = "2023-01-10-04", default-features = false, features = ['legacy'] }
ethers = { git = "https://github.com/hyperlane-xyz/ethers-rs", tag = "2023-01-10-04" }
ethers-signers = { git = "https://github.com/hyperlane-xyz/ethers-rs", tag = "2023-01-10-04", features=["aws"] }
ethers-providers = { git = "https://github.com/hyperlane-xyz/ethers-rs", tag = "2023-01-10-04", features=["ws", "rustls"] }
config = "0.13"
hex = "0.4.3"
sha3 = "0.9.1"
sha3 = "0.10"
lazy_static = "*"
thiserror = "1.0"
async-trait = { version = "0.1", default-features = false }

@ -1,5 +1,5 @@
use lazy_static::lazy_static;
use sha3::{Digest, Keccak256};
use sha3::{digest::Update, Digest, Keccak256};
use crate::H256;
@ -18,8 +18,8 @@ const EMPTY_SLICE: &[H256] = &[];
pub(super) fn hash_concat(left: impl AsRef<[u8]>, right: impl AsRef<[u8]>) -> H256 {
H256::from_slice(
Keccak256::new()
.chain(left.as_ref())
.chain(right.as_ref())
.chain(left)
.chain(right)
.finalize()
.as_slice(),
)

@ -108,6 +108,9 @@ pub enum KnownHyperlaneDomain {
Test2 = 13372,
/// Test3 local chain
Test3 = 13373,
/// Fuel1 local chain
FuelTest1 = 13374,
}
#[derive(Clone)]
@ -166,7 +169,7 @@ impl KnownHyperlaneDomain {
Goerli, Mumbai, Fuji, ArbitrumGoerli, OptimismGoerli, BinanceSmartChainTestnet,
Alfajores, MoonbaseAlpha, Zksync2Testnet
],
LocalTestChain: [Test1, Test2, Test3],
LocalTestChain: [Test1, Test2, Test3, FuelTest1],
})
}
@ -179,6 +182,7 @@ impl KnownHyperlaneDomain {
Optimism, OptimismGoerli, BinanceSmartChain, BinanceSmartChainTestnet, Celo,
Alfajores, Moonbeam, MoonbaseAlpha, Zksync2Testnet, Test1, Test2, Test3
],
HyperlaneDomainProtocol::Fuel: [FuelTest1],
})
}
}

@ -3,9 +3,7 @@ use std::error::Error as StdError;
use std::fmt::{Debug, Display, Formatter};
use std::ops::Deref;
use ethers::contract::ContractError;
use ethers::prelude::SignatureError;
use ethers_providers::{Middleware, ProviderError};
use ethers::prelude::{ContractError, Middleware, ProviderError, SignatureError};
use crate::db::DbError;
use crate::HyperlaneProviderError;

@ -1,7 +1,10 @@
use std::{fs::File, io::Read, path::PathBuf};
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use primitive_types::H256;
use crate::accumulator::merkle::Proof;
use crate::H256;
/// Struct representing a single merkle test case
#[derive(serde::Deserialize, serde::Serialize)]
@ -17,6 +20,14 @@ pub struct MerkleTestCase {
pub expected_root: H256,
}
/// Reads merkle test case json file and returns a vector of `MerkleTestCase`s
pub fn load_merkle_test_json() -> Vec<MerkleTestCase> {
let mut file = File::open(find_vector("merkle.json")).unwrap();
let mut data = String::new();
file.read_to_string(&mut data).unwrap();
serde_json::from_str(&data).unwrap()
}
/// Find a vector file assuming that a git checkout exists
// TODO: look instead for the workspace `Cargo.toml`? use a cargo env var?
pub fn find_vector(final_component: &str) -> PathBuf {
@ -28,11 +39,3 @@ pub fn find_vector(final_component: &str) -> PathBuf {
git_dir.join("vectors").join(final_component)
}
/// Reads merkle test case json file and returns a vector of `MerkleTestCase`s
pub fn load_merkle_test_json() -> Vec<MerkleTestCase> {
let mut file = File::open(find_vector("merkle.json")).unwrap();
let mut data = String::new();
file.read_to_string(&mut data).unwrap();
serde_json::from_str(&data).unwrap()
}

@ -13,7 +13,7 @@ use crate::{ChainResult, HyperlaneMessage, InterchainGasPaymentWithMeta, LogMeta
/// Interface for an indexer.
#[async_trait]
#[auto_impl(Box, Arc)]
#[auto_impl(&, Box, Arc)]
pub trait Indexer: Send + Sync + Debug {
/// Get the chain's latest block number that has reached finality
async fn get_finalized_block_number(&self) -> ChainResult<u32>;
@ -22,7 +22,7 @@ pub trait Indexer: Send + Sync + Debug {
/// Interface for Mailbox contract indexer. Interface for allowing other
/// entities to retrieve chain-specific data from a mailbox.
#[async_trait]
#[auto_impl(Box, Arc)]
#[auto_impl(&, Box, Arc)]
pub trait MailboxIndexer: Indexer {
/// Fetch list of outbound messages between blocks `from` and `to`, sorted
/// by nonce.
@ -42,7 +42,7 @@ pub trait MailboxIndexer: Indexer {
/// Interface for InterchainGasPaymaster contract indexer.
#[async_trait]
#[auto_impl(Box, Arc)]
#[auto_impl(&, Box, Arc)]
pub trait InterchainGasPaymasterIndexer: Indexer {
/// Fetch list of gas payments between `from_block` and `to_block`,
/// inclusive

@ -1,6 +1,7 @@
use std::fmt::Debug;
use async_trait::async_trait;
use auto_impl::auto_impl;
use thiserror::Error;
use crate::{BlockInfo, ChainResult, HyperlaneChain, TxnInfo, H256};
@ -13,6 +14,7 @@ use crate::{BlockInfo, ChainResult, HyperlaneChain, TxnInfo, H256};
/// however, there are some generic calls that we should be able to make outside
/// the context of a contract.
#[async_trait]
#[auto_impl(&, Box, Arc)]
pub trait HyperlaneProvider: HyperlaneChain + Send + Sync + Debug {
/// Get block info for a given block hash
async fn get_block_by_hash(&self, hash: &H256) -> ChainResult<BlockInfo>;

@ -1,6 +1,6 @@
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use sha3::{Digest, Keccak256};
use sha3::{digest::Update, Digest, Keccak256};
use crate::{utils::domain_hash, Signable, SignedType, H256};
@ -33,7 +33,7 @@ impl Signable for Announcement {
H256::from_slice(
Keccak256::new()
.chain(domain_hash(self.mailbox_address, self.mailbox_domain))
.chain(self.storage_metadata.clone())
.chain(&self.storage_metadata)
.finalize()
.as_slice(),
)

@ -1,7 +1,7 @@
use async_trait::async_trait;
use ethers::prelude::{Address, Signature};
use serde::{Deserialize, Serialize};
use sha3::{Digest, Keccak256};
use sha3::{digest::Update, Digest, Keccak256};
use crate::{utils::domain_hash, Signable, SignedType, H256};

@ -1,4 +1,4 @@
use sha3::{Digest, Keccak256};
use sha3::{digest::Update, Digest, Keccak256};
use crate::{Decode, Encode, HyperlaneProtocolError, H256};

@ -1,6 +1,6 @@
use std::str::FromStr;
use sha3::{Digest, Keccak256};
use sha3::{digest::Update, Digest, Keccak256};
use thiserror::Error;
use crate::H256;
@ -23,8 +23,8 @@ pub fn domain_hash(address: H256, domain: impl Into<u32>) -> H256 {
H256::from_slice(
Keccak256::new()
.chain(domain.into().to_be_bytes())
.chain(address.as_ref())
.chain("HYPERLANE".as_bytes())
.chain(address)
.chain("HYPERLANE")
.finalize()
.as_slice(),
)

@ -6,5 +6,13 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ethers = { git = "https://github.com/hyperlane-xyz/ethers-rs", tag = "2023-01-10-04" }
ethers = { git = "https://github.com/hyperlane-xyz/ethers-rs", optional = true, tag = "2023-01-10-04" }
fuels = { version = "0.33", optional = true }
which = {version = "4.3", optional = true }
Inflector = "0.11"
[features]
default = []
ethers = ["dep:ethers"]
fmt = ["dep:which"]
fuels = ["dep:fuels", "fmt"]

@ -4,13 +4,22 @@ use std::fs::{self, File};
use std::io::Write;
use std::path::Path;
use ethers::contract::Abigen;
use inflector::Inflector;
/// A `build.rs` tool for building a directory of ABIs. This will parse the `abi_dir` for ABI files
/// ending in `.json` and write the generated rust code to `output_dir` and create an appropriate
/// `mod.rs` file to.
pub fn generate_bindings_for_dir(abi_dir: impl AsRef<Path>, output_dir: impl AsRef<Path>) {
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum BuildType {
Ethers,
Fuels,
}
/// A `build.rs` tool for building a directory of ABIs. This will parse the
/// `abi_dir` for ABI files ending in `.json` and write the generated rust code
/// to `output_dir` and create an appropriate `mod.rs` file to.
pub fn generate_bindings_for_dir(
abi_dir: impl AsRef<Path>,
output_dir: impl AsRef<Path>,
build_type: BuildType,
) {
println!("cargo:rerun-if-changed={}", abi_dir.as_ref().display());
// clean old bindings
@ -25,13 +34,14 @@ pub fn generate_bindings_for_dir(abi_dir: impl AsRef<Path>, output_dir: impl AsR
.filter_map(Result::ok)
.map(|entry| entry.path())
.filter(|path| {
let ending_str = ".abi.json";
path.to_str()
.map(|p| p.ends_with(".abi.json"))
.map(|p| p.ends_with(ending_str))
.unwrap_or_default()
})
.map(|contract_path| {
println!("Generating bindings for {:?}", &contract_path);
generate_bindings(&contract_path, &output_dir)
generate_bindings(&contract_path, &output_dir, build_type)
})
.collect();
@ -52,9 +62,14 @@ pub fn generate_bindings_for_dir(abi_dir: impl AsRef<Path>, output_dir: impl AsR
drop(mod_file);
}
/// Generate the bindings for a given ABI and return the new module name. Will create a file within
/// the designated path with the correct `{module_name}.rs` format.
pub fn generate_bindings(contract_path: impl AsRef<Path>, output_dir: impl AsRef<Path>) -> String {
/// Generate the bindings for a given ABI and return the new module name. Will
/// create a file within the designated path with the correct `{module_name}.rs`
/// format.
pub fn generate_bindings(
contract_path: impl AsRef<Path>,
output_dir: impl AsRef<Path>,
build_type: BuildType,
) -> String {
println!("path {:?}", contract_path.as_ref().display());
// contract name is the first
let contract_name = contract_path
@ -68,22 +83,71 @@ pub fn generate_bindings(contract_path: impl AsRef<Path>, output_dir: impl AsRef
let module_name = contract_name.to_snake_case();
let bindings = Abigen::new(
contract_name,
contract_path.as_ref().to_str().expect("valid utf8 path"),
)
.expect("could not instantiate Abigen")
.generate()
.expect("could not generate bindings");
let output_file = {
let mut p = output_dir.as_ref().to_path_buf();
p.push(format!("{module_name}.rs"));
p
};
bindings
.write_to_file(&output_file)
.expect("Could not write bidings to file");
let abi_source = contract_path.as_ref().to_str().expect("valid utf8 path");
#[cfg(feature = "ethers")]
if build_type == BuildType::Ethers {
ethers::contract::Abigen::new(contract_name, abi_source)
.expect("could not instantiate Abigen")
.generate()
.expect("could not generate bindings")
.write_to_file(&output_file)
.expect("Could not write bindings to file");
}
#[cfg(feature = "fuels")]
if build_type == BuildType::Fuels {
fuels::core::code_gen::abigen::Abigen::new(contract_name, abi_source)
.expect("could not instantiate Abigen")
.generate()
.expect("could not generate bindings")
.write_to_file(&output_file)
.expect("Could not write bindings to file");
// apparently fuels was too lazy to do this for us
fmt_file(&output_file);
}
module_name
}
#[cfg(feature = "fmt")]
fn fmt_file(path: &Path) {
if let Err(err) = std::process::Command::new(rustfmt_path())
.args(["--edition", "2021"])
.arg(path)
.output()
{
println!("cargo:warning=Failed to run rustfmt for {path:?}, ignoring ({err})");
}
}
/// Get the rustfmt binary path.
#[cfg(feature = "fmt")]
fn rustfmt_path() -> &'static Path {
use std::path::PathBuf;
// lazy static var
static mut PATH: Option<PathBuf> = None;
if let Some(path) = unsafe { PATH.as_ref() } {
return path;
}
if let Ok(path) = std::env::var("RUSTFMT") {
unsafe {
PATH = Some(PathBuf::from(path));
PATH.as_ref().unwrap()
}
} else {
// assume it is in PATH
unsafe {
PATH = Some(which::which("rustmft").unwrap_or_else(|_| "rustfmt".into()));
PATH.as_ref().unwrap()
}
}
}

Loading…
Cancel
Save