Skip to content

Commit

Permalink
Support cosmos contract byte lengths other than 32 (hyperlane-xyz#3147)
Browse files Browse the repository at this point in the history
### Description

fixes hyperlane-xyz#3143 

Initially went down a path that would use protocol-specific types for
addresses as suggested by hyperlane-xyz#3143. I had made a `HyperlaneConnectionConf`
enum with protocol-specific variants whose values were `addresses:
CoreContractAddresses<ProtocolSpecificAddressType>` and `connection:
ProtocolSpecificConnectionConf`. This worked pretty well until I hit the
ISM logic.

Because hyperlane-core is where the Mailbox trait is defined, the return
type of `recipient_ism` in the trait cannot involve protocol specific
types. It can't be moved to hyperlane-base because hyperlane-base
imports the chain crates, so we'd have a cyclic dependency. I
experimented with moving away from H256 to something like a Vec<u8> or
string, but this felt a bit weird.

In the end we decided to keep H256s as the global representation for
contract addresses for now, with the intent of eventually changing this,
and to support the varying length situation in a cosmos config

### Drive-by changes

- Added some cosmos specific agent configurations into the sdk
- Moved to bech32_prefix in the agents for consistency with what the
SDK's chain metadata already does
- I guess no one's ran cargo test in a while so vectors/message.json got
a new v3 message lol

### Related issues

Fixes hyperlane-xyz#3143 

### Backward compatibility

Changes prefix to bech32_prefix in the agent config, and now requires
`contractAddressBytes`

### Testing

Tested merged with hyperlane-xyz#3144 and all worked

---------

Co-authored-by: Daniel Savu <[email protected]>
  • Loading branch information
2 people authored and ltyu committed Mar 13, 2024
1 parent 30bb62b commit 80fe1d1
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 43 deletions.
39 changes: 31 additions & 8 deletions rust/chains/hyperlane-cosmos/src/libs/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,21 @@ impl CosmosAddress {
///
/// - digest: H256 digest (hex representation of address)
/// - prefix: Bech32 prefix
pub fn from_h256(digest: H256, prefix: &str) -> ChainResult<Self> {
/// - byte_count: Number of bytes to truncate the digest to. Cosmos addresses can sometimes
/// be less than 32 bytes, so this helps to serialize it in bech32 with the appropriate
/// length.
pub fn from_h256(digest: H256, prefix: &str, byte_count: usize) -> ChainResult<Self> {
// This is the hex-encoded version of the address
let bytes = digest.as_bytes();
let untruncated_bytes = digest.as_bytes();

if byte_count > untruncated_bytes.len() {
return Err(Overflow.into());
}

let remainder_bytes_start = untruncated_bytes.len() - byte_count;
// Left-truncate the digest to the desired length
let bytes = &untruncated_bytes[remainder_bytes_start..];

// Bech32 encode it
let account_id =
AccountId::new(prefix, bytes).map_err(Into::<HyperlaneCosmosError>::into)?;
Expand Down Expand Up @@ -132,22 +144,33 @@ pub mod test {
addr.address(),
"neutron1kknekjxg0ear00dky5ykzs8wwp2gz62z9s6aaj"
);
// TODO: watch out for this edge case. This check will fail unless
// the first 12 bytes are removed from the digest.
// let digest = addr.digest();
// let addr2 = CosmosAddress::from_h256(digest, prefix).expect("Cosmos address creation failed");
// assert_eq!(addr.address(), addr2.address());

// Create an address with the same digest & explicitly set the byte count to 20,
// which should have the same result as the above.
let digest = addr.digest();
let addr2 =
CosmosAddress::from_h256(digest, prefix, 20).expect("Cosmos address creation failed");
assert_eq!(addr.address(), addr2.address());
}

#[test]
fn test_bech32_encode_from_h256() {
let hex_key = "0x1b16866227825a5166eb44031cdcf6568b3e80b52f2806e01b89a34dc90ae616";
let key = hex_or_base58_to_h256(hex_key).unwrap();
let prefix = "dual";
let addr = CosmosAddress::from_h256(key, prefix).expect("Cosmos address creation failed");
let addr =
CosmosAddress::from_h256(key, prefix, 32).expect("Cosmos address creation failed");
assert_eq!(
addr.address(),
"dual1rvtgvc38sfd9zehtgsp3eh8k269naq949u5qdcqm3x35mjg2uctqfdn3yq"
);

// Last 20 bytes only, which is 0x1cdcf6568b3e80b52f2806e01b89a34dc90ae616
let addr =
CosmosAddress::from_h256(key, prefix, 20).expect("Cosmos address creation failed");
assert_eq!(
addr.address(),
"dual1rnw0v45t86qt2tegqmsphzdrfhys4esk9ktul7"
);
}
}
15 changes: 12 additions & 3 deletions rust/chains/hyperlane-cosmos/src/mailbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,12 @@ impl CosmosMailbox {
}

/// Prefix used in the bech32 address encoding
pub fn prefix(&self) -> String {
self.config.get_prefix()
pub fn bech32_prefix(&self) -> String {
self.config.get_bech32_prefix()
}

fn contract_address_bytes(&self) -> usize {
self.config.get_contract_address_bytes()
}
}

Expand Down Expand Up @@ -151,7 +155,12 @@ impl Mailbox for CosmosMailbox {

#[instrument(err, ret, skip(self))]
async fn recipient_ism(&self, recipient: H256) -> ChainResult<H256> {
let address = CosmosAddress::from_h256(recipient, &self.prefix())?.address();
let address = CosmosAddress::from_h256(
recipient,
&self.bech32_prefix(),
self.contract_address_bytes(),
)?
.address();

let payload = mailbox::RecipientIsmRequest {
recipient_ism: mailbox::RecipientIsmRequestInner {
Expand Down
8 changes: 7 additions & 1 deletion rust/chains/hyperlane-cosmos/src/providers/grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,13 @@ impl WasmGrpcProvider {
Endpoint::new(conf.get_grpc_url()).map_err(Into::<HyperlaneCosmosError>::into)?;
let channel = endpoint.connect_lazy();
let contract_address = locator
.map(|l| CosmosAddress::from_h256(l.address, &conf.get_prefix()))
.map(|l| {
CosmosAddress::from_h256(
l.address,
&conf.get_bech32_prefix(),
conf.get_contract_address_bytes(),
)
})
.transpose()?;

Ok(Self {
Expand Down
3 changes: 2 additions & 1 deletion rust/chains/hyperlane-cosmos/src/providers/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ impl CosmosWasmIndexer {
provider,
contract_address: CosmosAddress::from_h256(
locator.address,
conf.get_prefix().as_str(),
conf.get_bech32_prefix().as_str(),
conf.get_contract_address_bytes(),
)?,
target_event_kind: format!("{}-{}", Self::WASM_TYPE, event_type),
reorg_period,
Expand Down
25 changes: 18 additions & 7 deletions rust/chains/hyperlane-cosmos/src/trait_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ pub struct ConnectionConf {
rpc_url: String,
/// The chain ID
chain_id: String,
/// The prefix for the account address
prefix: String,
/// The human readable address prefix for the chains using bech32.
bech32_prefix: String,
/// Canoncial Assets Denom
canonical_asset: String,
/// The gas price set by the cosmos-sdk validator. Note that this represents the
/// minimum price set by the validator.
/// More details here: https://docs.cosmos.network/main/learn/beginner/gas-fees#antehandler
gas_price: RawCosmosAmount,
/// The number of bytes used to represent a contract address.
/// Cosmos address lengths are sometimes less than 32 bytes, so this helps to serialize it in
/// bech32 with the appropriate length.
contract_address_bytes: usize,
}

/// Untyped cosmos amount
Expand Down Expand Up @@ -86,9 +90,9 @@ impl ConnectionConf {
self.chain_id.clone()
}

/// Get the prefix
pub fn get_prefix(&self) -> String {
self.prefix.clone()
/// Get the bech32 prefix
pub fn get_bech32_prefix(&self) -> String {
self.bech32_prefix.clone()
}

/// Get the asset
Expand All @@ -101,22 +105,29 @@ impl ConnectionConf {
self.gas_price.clone()
}

/// Get the number of bytes used to represent a contract address
pub fn get_contract_address_bytes(&self) -> usize {
self.contract_address_bytes
}

/// Create a new connection configuration
pub fn new(
grpc_url: String,
rpc_url: String,
chain_id: String,
prefix: String,
bech32_prefix: String,
canonical_asset: String,
minimum_gas_price: RawCosmosAmount,
contract_address_bytes: usize,
) -> Self {
Self {
grpc_url,
rpc_url,
chain_id,
prefix,
bech32_prefix,
canonical_asset,
gas_price: minimum_gas_price,
contract_address_bytes,
}
}
}
3 changes: 2 additions & 1 deletion rust/config/mainnet3_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -431,11 +431,12 @@
],
"grpcUrl": "https://grpc-kralum.neutron-1.neutron.org:80",
"canonicalAsset": "untrn",
"prefix": "neutron",
"bech32Prefix": "neutron",
"gasPrice": {
"amount": "0.57",
"denom": "untrn"
},
"contractAddressBytes": 32,
"index": {
"from": 4000000,
"chunk": 100000
Expand Down
14 changes: 12 additions & 2 deletions rust/hyperlane-base/src/settings/parser/connection_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,14 @@ pub fn build_cosmos_connection_conf(

let prefix = chain
.chain(err)
.get_key("prefix")
.get_key("bech32Prefix")
.parse_string()
.end()
.or_else(|| {
local_err.push(&chain.cwp + "prefix", eyre!("Missing prefix for chain"));
local_err.push(
&chain.cwp + "bech32Prefix",
eyre!("Missing bech32 prefix for chain"),
);
None
});

Expand All @@ -100,6 +103,12 @@ pub fn build_cosmos_connection_conf(
.and_then(parse_cosmos_gas_price)
.end();

let contract_address_bytes = chain
.chain(err)
.get_opt_key("contractAddressBytes")
.parse_u64()
.end();

if !local_err.is_ok() {
err.merge(local_err);
None
Expand All @@ -111,6 +120,7 @@ pub fn build_cosmos_connection_conf(
prefix.unwrap().to_string(),
canonical_asset.unwrap(),
gas_price.unwrap(),
contract_address_bytes.unwrap().try_into().unwrap(),
)))
}
}
Expand Down
10 changes: 5 additions & 5 deletions rust/utils/run-locally/src/cosmos/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub struct IGPOracleInstantiateMsg {
#[cw_serde]
pub struct EmptyMsg {}

const PREFIX: &str = "osmo";
const BECH32_PREFIX: &str = "osmo";

#[apply(as_task)]
pub fn deploy_cw_hyperlane(
Expand All @@ -46,7 +46,7 @@ pub fn deploy_cw_hyperlane(
codes.hpl_mailbox,
core::mailbox::InstantiateMsg {
owner: deployer_addr.to_string(),
hrp: PREFIX.to_string(),
hrp: BECH32_PREFIX.to_string(),
domain,
},
"hpl_mailbox",
Expand All @@ -68,7 +68,7 @@ pub fn deploy_cw_hyperlane(
Some(deployer_addr),
codes.hpl_igp,
GasOracleInitMsg {
hrp: PREFIX.to_string(),
hrp: BECH32_PREFIX.to_string(),
owner: deployer_addr.clone(),
gas_token: "uosmo".to_string(),
beneficiary: deployer_addr.clone(),
Expand Down Expand Up @@ -159,7 +159,7 @@ pub fn deploy_cw_hyperlane(
Some(deployer_addr),
codes.hpl_validator_announce,
core::va::InstantiateMsg {
hrp: PREFIX.to_string(),
hrp: BECH32_PREFIX.to_string(),
mailbox: mailbox.to_string(),
},
"hpl_validator_announce",
Expand All @@ -173,7 +173,7 @@ pub fn deploy_cw_hyperlane(
Some(deployer_addr),
codes.hpl_test_mock_msg_receiver,
TestMockMsgReceiverInstantiateMsg {
hrp: PREFIX.to_string(),
hrp: BECH32_PREFIX.to_string(),
},
"hpl_test_mock_msg_receiver",
);
Expand Down
6 changes: 4 additions & 2 deletions rust/utils/run-locally/src/cosmos/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,11 @@ pub struct AgentConfig {
pub chain_id: String,
pub rpc_urls: Vec<AgentUrl>,
pub grpc_url: String,
pub prefix: String,
pub bech32_prefix: String,
pub signer: AgentConfigSigner,
pub index: AgentConfigIndex,
pub gas_price: RawCosmosAmount,
pub contract_address_bytes: usize,
}

#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
Expand Down Expand Up @@ -156,7 +157,7 @@ impl AgentConfig {
),
}],
grpc_url: format!("http://{}", network.launch_resp.endpoint.grpc_addr),
prefix: "osmo".to_string(),
bech32_prefix: "osmo".to_string(),
signer: AgentConfigSigner {
typ: "cosmosKey".to_string(),
key: format!("0x{}", hex::encode(validator.priv_key.to_bytes())),
Expand All @@ -166,6 +167,7 @@ impl AgentConfig {
denom: "uosmo".to_string(),
amount: "0.05".to_string(),
},
contract_address_bytes: 32,
index: AgentConfigIndex {
from: 1,
chunk: 100,
Expand Down
9 changes: 7 additions & 2 deletions solidity/test/message.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,26 @@ import {
} from '@hyperlane-xyz/utils';

import testCases from '../../vectors/message.json';
import { TestMessage, TestMessage__factory } from '../types';
import { Mailbox__factory, TestMessage, TestMessage__factory } from '../types';

const remoteDomain = 1000;
const localDomain = 2000;
const version = 0;
const nonce = 11;

describe('Message', async () => {
let messageLib: TestMessage;
let version: number;

before(async () => {
const [signer] = await ethers.getSigners();

const Message = new TestMessage__factory(signer);
messageLib = await Message.deploy();

// For consistency with the Mailbox version
const Mailbox = new Mailbox__factory(signer);
const mailbox = await Mailbox.deploy(localDomain);
version = await mailbox.VERSION();
});

it('Returns fields from a message', async () => {
Expand Down
Loading

0 comments on commit 80fe1d1

Please sign in to comment.