Skip to content
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
70 changes: 70 additions & 0 deletions integration_tests/src/fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use async_trait::async_trait;
use cksol_types::{GetDepositAddressArgs, Signature, UpdateBalanceArgs};
use ic_pocket_canister_runtime::{
ExecuteHttpOutcallMocks, JsonRpcRequestMatcher, JsonRpcResponse, MockHttpOutcalls,
MockHttpOutcallsBuilder,
};
use icrc_ledger_types::{
icrc::generic_value::{ICRC3Value, Value},
Expand Down Expand Up @@ -156,3 +157,72 @@ pub fn get_memo(block: ICRC3Value) -> Vec<u8> {
let memo_blob = memo.clone().as_blob().expect("memo should be a blob");
memo_blob.into_vec()
}

/// Creates HTTP mocks for `getTransaction` RPC calls.
pub fn get_transaction_http_mocks(response: impl Fn() -> JsonRpcResponse) -> MockHttpOutcalls {
MockHttpOutcallsBuilder::new()
.given(get_deposit_transaction_request().with_id(0))
.respond_with(response().with_id(0))
.given(get_deposit_transaction_request().with_id(1))
.respond_with(response().with_id(1))
.given(get_deposit_transaction_request().with_id(2))
.respond_with(response().with_id(2))
.given(get_deposit_transaction_request().with_id(3))
.respond_with(response().with_id(3))
.build()
}

/// JSON-RPC request matcher for `getSlot`.
pub fn get_slot_request() -> JsonRpcRequestMatcher {
JsonRpcRequestMatcher::with_method("getSlot")
}

/// JSON-RPC response for `getSlot`.
pub fn get_slot_response(slot: u64) -> JsonRpcResponse {
JsonRpcResponse::from(json!({
"jsonrpc": "2.0",
"result": slot,
"id": 1
}))
}

/// JSON-RPC request matcher for `getBlock`.
pub fn get_block_request(slot: u64) -> JsonRpcRequestMatcher {
JsonRpcRequestMatcher::with_method("getBlock").with_params(json!([
slot,
{
"transactionDetails": "none",
"rewards": false,
"maxSupportedTransactionVersion": 0
}
]))
}

/// JSON-RPC response for `getBlock`.
pub fn get_block_response(blockhash: &str) -> JsonRpcResponse {
JsonRpcResponse::from(json!({
"jsonrpc": "2.0",
"result": {
"blockhash": blockhash,
"previousBlockhash": "CzBVNFJkh7WkQDfJUiDjLc7kPrJd8kR2yiCvwBUhSe7Y",
"parentSlot": 449819444,
"blockTime": 1700000000_i64,
"blockHeight": 449819444
},
"id": 1
}))
}

/// JSON-RPC request matcher for `sendTransaction`.
pub fn send_transaction_request() -> JsonRpcRequestMatcher {
JsonRpcRequestMatcher::with_method("sendTransaction")
}

/// JSON-RPC response for `sendTransaction`.
pub fn send_transaction_response(signature: &str) -> JsonRpcResponse {
JsonRpcResponse::from(json!({
"jsonrpc": "2.0",
"result": signature,
"id": 1
}))
}
15 changes: 15 additions & 0 deletions integration_tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ use icrc_ledger_types::{
icrc3::blocks::{GetBlocksRequest, GetBlocksResult, ICRC3GenericBlock},
};
use num_traits::cast::ToPrimitive;
pub use pocket_ic::common::rest::{
CanisterHttpReply, CanisterHttpRequest, CanisterHttpResponse, MockCanisterHttpResponse,
};
use pocket_ic::{PocketIcBuilder, RejectResponse, nonblocking::PocketIc};
use serde::de::DeserializeOwned;
use sol_rpc_client::SolRpcClient;
Expand Down Expand Up @@ -297,6 +300,18 @@ impl Setup {
self.env.as_ref().unwrap().advance_time(duration).await
}

pub async fn execute_http_mocks(&self, mut mocks: impl ExecuteHttpOutcallMocks) {
const MAX_ITERATIONS: usize = 20;
let env = self.env.as_ref().unwrap();

for _ in 0..MAX_ITERATIONS {
self.tick().await;
self.advance_time(Duration::from_nanos(1)).await;

mocks.execute_http_outcall_mocks(env).await;
}
}

pub async fn drop(self) {
let mut setup = self;
if let Some(env) = setup.env.take() {
Expand Down
105 changes: 90 additions & 15 deletions integration_tests/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use cksol_int_tests::{
fixtures::{
DEFAULT_CALLER_ACCOUNT, DEFAULT_CALLER_DEPOSIT_ADDRESS, DEPOSIT_AMOUNT,
EXPECTED_MINT_AMOUNT, SharedMockHttpOutcalls, default_update_balance_args,
deposit_transaction_signature, get_deposit_transaction_request,
get_deposit_transaction_response,
deposit_transaction_signature, get_block_request, get_block_response,
get_deposit_transaction_response, get_slot_request, get_slot_response,
get_transaction_http_mocks, send_transaction_request, send_transaction_response,
},
};
use cksol_types::{
Expand All @@ -19,6 +20,7 @@ use ic_pocket_canister_runtime::{JsonRpcResponse, MockHttpOutcalls, MockHttpOutc
use icrc_ledger_types::icrc1::account::Subaccount;
use serde_json::json;
use sol_rpc_types::{CommitmentLevel, ConsensusStrategy, GetTransactionEncoding, RpcConfig};
use std::time::Duration;
use tokio::join;

mod get_deposit_address_tests {
Expand Down Expand Up @@ -767,19 +769,6 @@ mod update_balance_tests {
setup.drop().await;
}

fn get_transaction_http_mocks(response: impl Fn() -> JsonRpcResponse) -> MockHttpOutcalls {
MockHttpOutcallsBuilder::new()
.given(get_deposit_transaction_request().with_id(0))
.respond_with(response().with_id(0))
.given(get_deposit_transaction_request().with_id(1))
.respond_with(response().with_id(1))
.given(get_deposit_transaction_request().with_id(2))
.respond_with(response().with_id(2))
.given(get_deposit_transaction_request().with_id(3))
.respond_with(response().with_id(3))
.build()
}

async fn get_transaction_cycles_cost(setup: &Setup) -> u128 {
setup
.sol_rpc()
Expand Down Expand Up @@ -840,3 +829,89 @@ mod anonymous_caller_tests {
setup.drop().await;
}
}

mod consolidation_tests {
use super::*;

const DEPOSIT_CONSOLIDATION_DELAY: Duration = Duration::from_secs(600);

#[tokio::test]
async fn should_consolidate_deposits_after_timer() {
let setup = SetupBuilder::new().with_proxy_canister().build().await;

let result = setup
.minter()
.with_http_mocks(get_transaction_http_mocks(get_deposit_transaction_response))
.update_balance(default_update_balance_args())
.await;
assert_matches!(result, Ok(DepositStatus::Minted { .. }));

// Advance time past the consolidation delay to trigger the timer
setup.advance_time(DEPOSIT_CONSOLIDATION_DELAY).await;
setup
.execute_http_mocks(http_mocks_for_deposit_consolidation())
.await;

// Verify consolidation events were recorded
let events_after = setup.minter().get_all_events().await;
assert!(
events_after
.iter()
.any(|e| matches!(e.payload, EventType::ConsolidatedDeposits { .. })),
"Expected ConsolidatedDeposits event. Events: {events_after:?}"
);
assert!(
events_after
.iter()
.any(|e| matches!(e.payload, EventType::SubmittedTransaction { .. })),
"Expected SubmittedTransaction event. Events: {events_after:?}"
);

// Verify the consolidated deposits match the deposit amount
for event in &events_after {
if let EventType::ConsolidatedDeposits { deposits } = &event.payload {
let total: Lamport = deposits.iter().map(|(_, amount)| amount).sum();
assert_eq!(
total, DEPOSIT_AMOUNT,
"Consolidated amount should match the deposit amount"
);
}
}

setup.drop().await;
}

// Returns the required HTTP outcall mocks for executing the deposit consolidation task
fn http_mocks_for_deposit_consolidation() -> MockHttpOutcalls {
const SLOT: u64 = 100_000_000;
const BLOCKHASH: &str = "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn";
const TX_SIGNATURE: &str = "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW";

let mut mocks = MockHttpOutcallsBuilder::new();
// getSlot requests for estimate_recent_blockhash (IDs 4-7)
for id in 4..8 {
mocks = mocks
.given(get_slot_request().with_id(id))
.respond_with(get_slot_response(SLOT).with_id(id));
}
// getBlock requests for estimate_recent_blockhash (IDs 8-11)
for id in 8..12 {
mocks = mocks
.given(get_block_request(SLOT).with_id(id))
.respond_with(get_block_response(BLOCKHASH).with_id(id));
}
// getSlot requests for get_slot (IDs 12-15)
for id in 12..16 {
mocks = mocks
.given(get_slot_request().with_id(id))
.respond_with(get_slot_response(SLOT).with_id(id));
}
// sendTransaction requests (IDs 16-19)
for id in 16..20 {
mocks = mocks
.given(send_transaction_request().with_id(id))
.respond_with(send_transaction_response(TX_SIGNATURE).with_id(id));
}
mocks.build()
}
}
Loading