diff --git a/cosmwasm/contracts/ntt-global-accountant/src/contract.rs b/cosmwasm/contracts/ntt-global-accountant/src/contract.rs index 7a9463160a..ef2f6ed15b 100644 --- a/cosmwasm/contracts/ntt-global-accountant/src/contract.rs +++ b/cosmwasm/contracts/ntt-global-accountant/src/contract.rs @@ -31,15 +31,16 @@ use crate::{ bail, error::{AnyError, ContractError}, msg::{ - AllAccountsResponse, AllModificationsResponse, AllPendingTransfersResponse, - AllTransfersResponse, BatchTransferStatusResponse, ChainRegistrationResponse, ExecuteMsg, - MigrateMsg, MissingObservation, MissingObservationsResponse, Observation, ObservationError, - ObservationStatus, QueryMsg, SubmitObservationResponse, TransferDetails, TransferStatus, - SUBMITTED_OBSERVATIONS_PREFIX, + AllAccountsResponse, AllEndpointHubsResponse, AllEndpointPeersResponse, + AllModificationsResponse, AllPendingTransfersResponse, AllTransfersResponse, + BatchTransferStatusResponse, ExecuteMsg, MigrateMsg, MissingObservation, + MissingObservationsResponse, Observation, ObservationError, ObservationStatus, QueryMsg, + RelayerChainRegistrationResponse, SubmitObservationResponse, TransferDetails, + TransferStatus, SUBMITTED_OBSERVATIONS_PREFIX, }, state::{ - Data, PendingTransfer, DIGESTS, ENDPOINT_PEER, ENDPOINT_TO_HUB, PENDING_TRANSFERS, - RELAYER_CHAIN_REGISTRATIONS, + Data, EndpointHub, EndpointPeer, PendingTransfer, DIGESTS, ENDPOINT_PEER, ENDPOINT_TO_HUB, + PENDING_TRANSFERS, RELAYER_CHAIN_REGISTRATIONS, }, structs::{DeliveryInstruction, EndpointInit, EndpointRegister, EndpointTransfer, ManagerMode}, }; @@ -174,15 +175,15 @@ fn handle_observation( // if the emitter is a known standard relayer, parse the sender and payload from the delivery instruction let delivery_instruction = DeliveryInstruction::deserialize(&o.payload.0)?; ( - delivery_instruction.sender_address, + delivery_instruction.sender_address.into(), delivery_instruction.payload, ) } else { // otherwise, the sender and payload is the same as the VAA - (o.emitter_address, o.payload.0) + (o.emitter_address.into(), o.payload.0) }; - let hub_key = ENDPOINT_TO_HUB.key((o.emitter_chain, sender.to_vec())); + let hub_key = ENDPOINT_TO_HUB.key((o.emitter_chain, sender)); let hub = hub_key .may_load(deps.storage) @@ -194,7 +195,7 @@ fn handle_observation( .context("failed to parse observation payload")?; let destination_chain = message.manager_payload.payload.to_chain.id; - let source_peer_key = ENDPOINT_PEER.key((o.emitter_chain, sender.to_vec(), destination_chain)); + let source_peer_key = ENDPOINT_PEER.key((o.emitter_chain, sender, destination_chain)); let source_peer = source_peer_key .may_load(deps.storage) .context("failed to load source peer")? @@ -263,7 +264,7 @@ fn handle_observation( // and another that uses 8... this is maxed at 8, but should be actually normalized to 8 for accounting purposes. let tx_data = transfer::Data { amount: Uint256::from(message.manager_payload.payload.amount.denormalize(8)), - token_address: TokenAddress::from_vec(hub.1)?, + token_address: hub.1, token_chain: hub.0, recipient_chain: message.manager_payload.payload.to_chain.id, }; @@ -478,12 +479,15 @@ fn handle_ntt_vaa( let delivery_instruction = DeliveryInstruction::deserialize(&Vec::from_slice(body.payload)?)?; ( - delivery_instruction.sender_address, + delivery_instruction.sender_address.into(), delivery_instruction.payload, ) } else { // otherwise, the sender and payload is the same as the VAA - (body.emitter_address.0, Vec::from_slice(body.payload)?) + ( + body.emitter_address.0.into(), + Vec::from_slice(body.payload)?, + ) }; if payload.len() < 4 { @@ -493,7 +497,7 @@ fn handle_ntt_vaa( if prefix == EndpointTransfer::PREFIX { let source_chain = body.emitter_chain.into(); - let hub_key = ENDPOINT_TO_HUB.key((source_chain, sender.to_vec())); + let hub_key = ENDPOINT_TO_HUB.key((source_chain, sender)); let hub = hub_key .may_load(deps.storage) @@ -505,7 +509,7 @@ fn handle_ntt_vaa( .context("failed to parse NTT transfer payload")?; let destination_chain = message.manager_payload.payload.to_chain.id; - let source_peer_key = ENDPOINT_PEER.key((source_chain, sender.to_vec(), destination_chain)); + let source_peer_key = ENDPOINT_PEER.key((source_chain, sender, destination_chain)); let source_peer = source_peer_key .may_load(deps.storage) .context("failed to load source peer")? @@ -558,7 +562,7 @@ fn handle_ntt_vaa( let message = EndpointInit::deserialize(&payload)?; if message.manager_mode == (ManagerMode::LOCKING as u8) { let chain = body.emitter_chain.into(); - let hub_key = ENDPOINT_TO_HUB.key((chain, sender.to_vec())); + let hub_key = ENDPOINT_TO_HUB.key((chain, sender)); if hub_key .may_load(deps.storage) @@ -568,7 +572,7 @@ fn handle_ntt_vaa( bail!("hub entry already exists") } hub_key - .save(deps.storage, &(chain, sender.to_vec())) + .save(deps.storage, &(chain, sender)) .context("failed to save hub")?; Ok(Event::new("RegisterHub") .add_attribute("chain", chain.to_string()) @@ -582,7 +586,7 @@ fn handle_ntt_vaa( let message = EndpointRegister::deserialize(&payload)?; let peer_hub_key = - ENDPOINT_TO_HUB.key((message.endpoint_chain_id, message.endpoint_address.to_vec())); + ENDPOINT_TO_HUB.key((message.endpoint_chain_id, message.endpoint_address.into())); let peer_hub = peer_hub_key .may_load(deps.storage) @@ -590,7 +594,7 @@ fn handle_ntt_vaa( .ok_or(ContractError::MissingHubRegistration)?; let chain = body.emitter_chain.into(); - let peer_key = ENDPOINT_PEER.key((chain, sender.to_vec(), message.endpoint_chain_id)); + let peer_key = ENDPOINT_PEER.key((chain, sender, message.endpoint_chain_id)); if peer_key .may_load(deps.storage) @@ -600,7 +604,7 @@ fn handle_ntt_vaa( bail!("peer entry for this chain already exists") } - let hub_key = ENDPOINT_TO_HUB.key((chain, sender.to_vec())); + let hub_key = ENDPOINT_TO_HUB.key((chain, sender)); if let Some(endpoint_hub) = hub_key .may_load(deps.storage) @@ -612,7 +616,9 @@ fn handle_ntt_vaa( } } else { // this endpoint does not have a known hub, check if this peer is a hub themselves - if peer_hub.0 == message.endpoint_chain_id && peer_hub.1 == message.endpoint_address { + if peer_hub.0 == message.endpoint_chain_id + && peer_hub.1 == message.endpoint_address.into() + { // this peer is a hub, so set it as this endpoint's hub hub_key .save(deps.storage, &peer_hub.clone()) @@ -624,7 +630,7 @@ fn handle_ntt_vaa( } peer_key - .save(deps.storage, &(message.endpoint_address.to_vec())) + .save(deps.storage, &(message.endpoint_address.into())) .context("failed to save hub")?; Ok(Event::new("RegisterPeer") .add_attribute("chain", chain.to_string()) @@ -662,8 +668,14 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - query_chain_registration(deps, chain).and_then(|resp| to_binary(&resp)) + QueryMsg::RelayerChainRegistration { chain } => { + query_relayer_chain_registration(deps, chain).and_then(|resp| to_binary(&resp)) + } + QueryMsg::AllEndpointHubs { start_after, limit } => { + query_all_endpoint_hubs(deps, start_after, limit).and_then(|resp| to_binary(&resp)) + } + QueryMsg::AllEndpointPeers { start_after, limit } => { + query_all_endpoint_peers(deps, start_after, limit).and_then(|resp| to_binary(&resp)) } QueryMsg::MissingObservations { guardian_set, @@ -805,13 +817,61 @@ fn query_all_modifications( } } -fn query_chain_registration( +fn query_relayer_chain_registration( deps: Deps, chain: u16, -) -> StdResult { +) -> StdResult { RELAYER_CHAIN_REGISTRATIONS .load(deps.storage, chain) - .map(|address| ChainRegistrationResponse { address }) + .map(|address| RelayerChainRegistrationResponse { address }) +} + +fn query_all_endpoint_hubs( + deps: Deps, + start_after: Option<(u16, TokenAddress)>, + limit: Option, +) -> StdResult { + let start = start_after.map(|key| Bound::Exclusive((key, PhantomData))); + + let iter = ENDPOINT_TO_HUB + .range(deps.storage, start, None, Order::Ascending) + .map(|item| item.map(|(key, data)| EndpointHub { key, data })); + + if let Some(lim) = limit { + let l = lim + .try_into() + .map_err(|_| ConversionOverflowError::new("u32", "usize", lim.to_string()))?; + iter.take(l) + .collect::>>() + .map(|hubs| AllEndpointHubsResponse { hubs }) + } else { + iter.collect::>>() + .map(|hubs| AllEndpointHubsResponse { hubs }) + } +} + +fn query_all_endpoint_peers( + deps: Deps, + start_after: Option<(u16, TokenAddress, u16)>, + limit: Option, +) -> StdResult { + let start = start_after.map(|key| Bound::Exclusive((key, PhantomData))); + + let iter = ENDPOINT_PEER + .range(deps.storage, start, None, Order::Ascending) + .map(|item| item.map(|(key, data)| EndpointPeer { key, data })); + + if let Some(lim) = limit { + let l = lim + .try_into() + .map_err(|_| ConversionOverflowError::new("u32", "usize", lim.to_string()))?; + iter.take(l) + .collect::>>() + .map(|peers| AllEndpointPeersResponse { peers }) + } else { + iter.collect::>>() + .map(|peers| AllEndpointPeersResponse { peers }) + } } fn query_missing_observations( diff --git a/cosmwasm/contracts/ntt-global-accountant/src/msg.rs b/cosmwasm/contracts/ntt-global-accountant/src/msg.rs index 1888bd3187..5ee5a53d38 100644 --- a/cosmwasm/contracts/ntt-global-accountant/src/msg.rs +++ b/cosmwasm/contracts/ntt-global-accountant/src/msg.rs @@ -1,4 +1,4 @@ -use accountant::state::{account, transfer, Account, Modification, Transfer}; +use accountant::state::{account, transfer, Account, Modification, TokenAddress, Transfer}; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Binary; use serde_wormhole::RawMessage; @@ -7,7 +7,7 @@ use wormhole_sdk::{ Address, }; -use crate::state::{self, PendingTransfer}; +use crate::state::{self, EndpointHub, EndpointPeer, PendingTransfer}; pub const SUBMITTED_OBSERVATIONS_PREFIX: &[u8; 35] = b"ntt_acct_sub_obsfig_00000000000000|"; @@ -142,8 +142,18 @@ pub enum QueryMsg { }, #[returns(cosmwasm_std::Empty)] ValidateTransfer { transfer: Transfer }, - #[returns(ChainRegistrationResponse)] - ChainRegistration { chain: u16 }, + #[returns(RelayerChainRegistrationResponse)] + RelayerChainRegistration { chain: u16 }, + #[returns(AllEndpointHubsResponse)] + AllEndpointHubs { + start_after: Option<(u16, TokenAddress)>, + limit: Option, + }, + #[returns(AllEndpointPeersResponse)] + AllEndpointPeers { + start_after: Option<(u16, TokenAddress, u16)>, + limit: Option, + }, #[returns(MissingObservationsResponse)] MissingObservations { guardian_set: u32, index: u8 }, #[returns(TransferStatus)] @@ -174,10 +184,20 @@ pub struct AllModificationsResponse { } #[cw_serde] -pub struct ChainRegistrationResponse { +pub struct RelayerChainRegistrationResponse { pub address: Binary, } +#[cw_serde] +pub struct AllEndpointHubsResponse { + pub hubs: Vec, +} + +#[cw_serde] +pub struct AllEndpointPeersResponse { + pub peers: Vec, +} + #[cw_serde] pub struct MissingObservationsResponse { pub missing: Vec, diff --git a/cosmwasm/contracts/ntt-global-accountant/src/state.rs b/cosmwasm/contracts/ntt-global-accountant/src/state.rs index 2745c6417f..e294dee644 100644 --- a/cosmwasm/contracts/ntt-global-accountant/src/state.rs +++ b/cosmwasm/contracts/ntt-global-accountant/src/state.rs @@ -1,4 +1,4 @@ -use accountant::state::transfer; +use accountant::state::{transfer, TokenAddress}; use cosmwasm_schema::cw_serde; use cosmwasm_std::Binary; use cw_storage_plus::Map; @@ -6,10 +6,23 @@ use tinyvec::TinyVec; pub const PENDING_TRANSFERS: Map> = Map::new("pending_transfers"); pub const RELAYER_CHAIN_REGISTRATIONS: Map = Map::new("relayer_chain_registrations"); -pub const ENDPOINT_TO_HUB: Map<(u16, Vec), (u16, Vec)> = Map::new("endpoint_to_hub"); -pub const ENDPOINT_PEER: Map<(u16, Vec, u16), Vec> = Map::new("endpoint_peers"); +pub const ENDPOINT_TO_HUB: Map<(u16, TokenAddress), (u16, TokenAddress)> = + Map::new("endpoint_to_hub"); +pub const ENDPOINT_PEER: Map<(u16, TokenAddress, u16), TokenAddress> = Map::new("endpoint_peers"); pub const DIGESTS: Map<(u16, Vec, u64), Binary> = Map::new("digests"); +#[cw_serde] +pub struct EndpointHub { + pub key: (u16, TokenAddress), + pub data: (u16, TokenAddress), +} + +#[cw_serde] +pub struct EndpointPeer { + pub key: (u16, TokenAddress, u16), + pub data: TokenAddress, +} + #[cw_serde] pub struct PendingTransfer { pub key: transfer::Key, diff --git a/cosmwasm/contracts/ntt-global-accountant/tests/chain_registration.rs b/cosmwasm/contracts/ntt-global-accountant/tests/chain_registration.rs index 076a8949b3..d64b84e51a 100644 --- a/cosmwasm/contracts/ntt-global-accountant/tests/chain_registration.rs +++ b/cosmwasm/contracts/ntt-global-accountant/tests/chain_registration.rs @@ -2,7 +2,7 @@ mod helpers; use cosmwasm_std::{to_binary, Event}; use helpers::*; -use ntt_global_accountant::msg::ChainRegistrationResponse; +use ntt_global_accountant::msg::RelayerChainRegistrationResponse; use wormhole_sdk::{ relayer::{Action, GovernancePacket}, vaa::Body, @@ -56,8 +56,9 @@ fn any_target() { .add_attribute("emitter_address", emitter_address.to_string()), ); - let ChainRegistrationResponse { address } = - contract.query_chain_registration(chain.into()).unwrap(); + let RelayerChainRegistrationResponse { address } = contract + .query_relayer_chain_registration(chain.into()) + .unwrap(); assert_eq!(&*address, &emitter_address.0); } @@ -87,8 +88,9 @@ fn wormchain_target() { .add_attribute("emitter_address", emitter_address.to_string()), ); - let ChainRegistrationResponse { address } = - contract.query_chain_registration(chain.into()).unwrap(); + let RelayerChainRegistrationResponse { address } = contract + .query_relayer_chain_registration(chain.into()) + .unwrap(); assert_eq!(&*address, &emitter_address.0); } diff --git a/cosmwasm/contracts/ntt-global-accountant/tests/helpers/mod.rs b/cosmwasm/contracts/ntt-global-accountant/tests/helpers/mod.rs index fa026f7f39..04f3c96784 100644 --- a/cosmwasm/contracts/ntt-global-accountant/tests/helpers/mod.rs +++ b/cosmwasm/contracts/ntt-global-accountant/tests/helpers/mod.rs @@ -12,8 +12,8 @@ use cw_multi_test::{ use ntt_global_accountant::{ msg::{ AllAccountsResponse, AllModificationsResponse, AllPendingTransfersResponse, - AllTransfersResponse, BatchTransferStatusResponse, ChainRegistrationResponse, ExecuteMsg, - MissingObservationsResponse, QueryMsg, TransferStatus, SUBMITTED_OBSERVATIONS_PREFIX, + AllTransfersResponse, BatchTransferStatusResponse, ExecuteMsg, MissingObservationsResponse, + QueryMsg, RelayerChainRegistrationResponse, TransferStatus, SUBMITTED_OBSERVATIONS_PREFIX, }, state, }; @@ -234,10 +234,13 @@ impl Contract { ) } - pub fn query_chain_registration(&self, chain: u16) -> StdResult { + pub fn query_relayer_chain_registration( + &self, + chain: u16, + ) -> StdResult { self.app .wrap() - .query_wasm_smart(self.addr(), &QueryMsg::ChainRegistration { chain }) + .query_wasm_smart(self.addr(), &QueryMsg::RelayerChainRegistration { chain }) } pub fn query_missing_observations( diff --git a/wormchain/contracts/tools/__tests__/test_ntt_accountant.ts b/wormchain/contracts/tools/__tests__/test_ntt_accountant.ts index 07bd60272c..b465dae2f4 100644 --- a/wormchain/contracts/tools/__tests__/test_ntt_accountant.ts +++ b/wormchain/contracts/tools/__tests__/test_ntt_accountant.ts @@ -80,9 +80,9 @@ const NTT_GA_ADDRESS = const HUB_CHAIN = 2; const HUB_ENDPOINT = `0000000000000000000000000000000000000000000000000000000000000042`; const SPOKE_CHAIN_A = 4; -const SPOKE_ENDPOINT_A = HUB_ENDPOINT; +const SPOKE_ENDPOINT_A = `0000000000000000000000000000000000000000000000000000000000000043`; const SPOKE_CHAIN_B = 5; -const SPOKE_ENDPOINT_B = HUB_ENDPOINT; +const SPOKE_ENDPOINT_B = `0000000000000000000000000000000000000000000000000000000000000044`; const FAUX_HUB_CHAIN = 420; const FAUX_HUB_ENDPOINT = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; @@ -266,6 +266,14 @@ describe("Global Accountant Tests", () => { ); const result = await submitVAA(vaa); expect(result.code).toEqual(0); + const response = await cosmWasmClient.queryContractSmart(NTT_GA_ADDRESS, { + all_endpoint_hubs: {}, + }); + const hub = response.hubs.find( + (entry) => entry.key[0] === HUB_CHAIN && entry.key[1] === HUB_ENDPOINT + ); + expect(hub).toBeDefined(); + expect(hub.data).toStrictEqual([HUB_CHAIN, HUB_ENDPOINT]); // check replay protection { const result = await submitVAA(vaa); @@ -313,6 +321,17 @@ describe("Global Accountant Tests", () => { ); const result = await submitVAA(vaa); expect(result.code).toEqual(0); + const response = await cosmWasmClient.queryContractSmart(NTT_GA_ADDRESS, { + all_endpoint_peers: {}, + }); + const peer = response.peers.find( + (entry) => + entry.key[0] === SPOKE_CHAIN_A && + entry.key[1] === SPOKE_ENDPOINT_A && + entry.key[2] === HUB_CHAIN + ); + expect(peer).toBeDefined(); + expect(peer.data).toStrictEqual(HUB_ENDPOINT); // check replay protection { const result = await submitVAA(vaa); @@ -328,6 +347,17 @@ describe("Global Accountant Tests", () => { ); const result = await submitVAA(vaa); expect(result.code).toEqual(0); + const response = await cosmWasmClient.queryContractSmart(NTT_GA_ADDRESS, { + all_endpoint_peers: {}, + }); + const peer = response.peers.find( + (entry) => + entry.key[0] === HUB_CHAIN && + entry.key[1] === HUB_ENDPOINT && + entry.key[2] === SPOKE_CHAIN_A + ); + expect(peer).toBeDefined(); + expect(peer.data).toStrictEqual(SPOKE_ENDPOINT_A); }); test("d. Ensure an endpoint registration to another endpoint without a known hub is rejected", async () => { const vaa = makeVAA( @@ -360,6 +390,20 @@ describe("Global Accountant Tests", () => { ); const result = await submitVAA(vaa); expect(result.code).toEqual(0); + const response = await cosmWasmClient.queryContractSmart( + NTT_GA_ADDRESS, + { + all_endpoint_peers: {}, + } + ); + const peer = response.peers.find( + (entry) => + entry.key[0] === SPOKE_CHAIN_B && + entry.key[1] === SPOKE_ENDPOINT_B && + entry.key[2] === HUB_CHAIN + ); + expect(peer).toBeDefined(); + expect(peer.data).toStrictEqual(HUB_ENDPOINT); } { const vaa = makeVAA( @@ -369,6 +413,20 @@ describe("Global Accountant Tests", () => { ); const result = await submitVAA(vaa); expect(result.code).toEqual(0); + const response = await cosmWasmClient.queryContractSmart( + NTT_GA_ADDRESS, + { + all_endpoint_peers: {}, + } + ); + const peer = response.peers.find( + (entry) => + entry.key[0] === SPOKE_CHAIN_A && + entry.key[1] === SPOKE_ENDPOINT_A && + entry.key[2] === SPOKE_CHAIN_B + ); + expect(peer).toBeDefined(); + expect(peer.data).toStrictEqual(SPOKE_ENDPOINT_B); } }); test("g. Ensure a duplicate registration is rejected", async () => { @@ -547,6 +605,10 @@ describe("Global Accountant Tests", () => { }); describe("5. Relayers", () => { test("a. Ensure a relayer registration is saved", async () => { + const relayerEmitterAsBase64 = Buffer.from( + RELAYER_EMITTER, + "hex" + ).toString("base64"); // register eth const vaa = makeVAA( GOVERNANCE_CHAIN, @@ -555,6 +617,12 @@ describe("Global Accountant Tests", () => { ); const result = await submitVAA(vaa); expect(result.code).toEqual(0); + const response = await cosmWasmClient.queryContractSmart(NTT_GA_ADDRESS, { + relayer_chain_registration: { + chain: 2, + }, + }); + expect(response.address).toEqual(relayerEmitterAsBase64); // check replay protection { const result = await submitVAA(vaa); @@ -570,6 +638,15 @@ describe("Global Accountant Tests", () => { ); const result = await submitVAA(vaa); expect(result.code).toEqual(0); + const response = await cosmWasmClient.queryContractSmart( + NTT_GA_ADDRESS, + { + relayer_chain_registration: { + chain: 4, + }, + } + ); + expect(response.address).toEqual(relayerEmitterAsBase64); } }); test("b. Ensure a valid NTT transfer works", async () => {