Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sweep per single subaccount id #27

Merged
merged 11 commits into from
Aug 12, 2024
1 change: 1 addition & 0 deletions src/icp_subaccount_indexer/icp_subaccount_indexer.did
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,5 @@ service : (Network, nat64, nat32, text, text) -> {
set_webhook_url : (text) -> (Result);
single_sweep : (text) -> (Result_9);
sweep : () -> (Result_9);
sweep_subaccount : (text, float64) -> (Result_8);
}
45 changes: 45 additions & 0 deletions src/icp_subaccount_indexer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,51 @@ async fn single_sweep(tx_hash_arg: String) -> Result<Vec<String>, Error> {
Ok(results)
}

#[update]
async fn sweep_subaccount(subaccountid_hex: String, amount: f64) -> Result<u64, Error> {
authenticate().map_err(|e| Error { message: e })?;

let custodian_id = get_custodian_id().map_err(|e| Error { message: e })?;

let matching_subaccount = LIST_OF_SUBACCOUNTS.with(|subaccounts| {
subaccounts
.borrow()
.iter()
.find(|(_, subaccount)| {
let subaccountid = to_subaccount_id(**subaccount);
subaccountid.to_hex() == subaccountid_hex
})
.map(|(_, &subaccount)| subaccount)
});

let subaccount = matching_subaccount.ok_or_else(|| Error {
message: "Subaccount not found".to_string(),
})?;

// Convert amount to e8s, handling potential precision issues
let amount_e8s = (amount * 100_000_000.0).round() as u64;

// Check for potential overflow or underflow
if amount_e8s == u64::MAX || amount < 0.0 {
return Err(Error {
message: "Invalid amount: overflow or negative value".to_string(),
});
}

let transfer_args = TransferArgs {
memo: Memo(0),
amount: Tokens::from_e8s(amount_e8s),
fee: Tokens::from_e8s(10_000),
from_subaccount: Some(subaccount),
to: custodian_id,
created_at_time: None,
};

InterCanisterCallManager::transfer(transfer_args)
.await
.map_err(|e| Error { message: e })
}

#[update]
async fn set_sweep_failed(tx_hash_arg: String) -> Result<Vec<String>, Error> {
authenticate().map_err(|e| Error { message: e })?;
Expand Down
104 changes: 104 additions & 0 deletions src/icp_subaccount_indexer/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,24 @@ mod tests {
"The function should return the correct URL"
);
}

#[tokio::test]
async fn test_sweep_subaccount_decimal_amount() {
// Setup
let (_, to_subaccountid, _) = setup_principals();
let subaccountid_hex = to_subaccountid.to_hex();
let amount = 1.25; // 1.25 ICP

// Execute
let result = sweep_subaccount(subaccountid_hex, amount).await;

// Assert
assert!(
result.is_ok(),
"Sweeping subaccount with decimal amount should succeed"
);
assert_eq!(result.unwrap(), 1, "BlockIndex should be 1");
}
}

#[cfg(feature = "sad_path")]
Expand Down Expand Up @@ -698,5 +716,91 @@ mod tests {
"The function should return the default String"
);
}

#[tokio::test]
async fn test_sweep_subaccount_nonexistent() {
setup_sweep_environment();
let (_, to_subaccountid, _) = setup_principals();

// Setup
let nonexistent_subaccountid =
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
let amount = 1.25;

// Execute
let result = sweep_subaccount(nonexistent_subaccountid.to_string(), amount).await;

// Assert
assert!(
result.is_err(),
"Sweeping nonexistent subaccount should fail"
);
assert_eq!(
result.unwrap_err().message,
"Subaccount not found",
"Error message should indicate subaccount not found"
);
teardown_sweep_environment();
}

#[tokio::test]
async fn test_sweep_subaccount_transfer_failure() {
// Setup
let (_, to_subaccountid, _) = setup_principals();
let subaccountid_hex = to_subaccountid.to_hex();
let amount = 1.25;

// Execute
let result = sweep_subaccount(subaccountid_hex, amount).await;

// Assert
assert!(
result.is_err(),
"Sweeping should fail due to transfer failure"
);
assert_eq!(
result.unwrap_err().message,
"transfer failed",
"Error message should indicate transfer failure"
);
}

#[tokio::test]
async fn test_sweep_subaccount_negative_amount() {
// Setup
let (_, to_subaccountid, _) = setup_principals();
let subaccountid_hex = to_subaccountid.to_hex();
let amount = -1.0;

// Execute
let result = sweep_subaccount(subaccountid_hex, amount).await;

// Assert
assert!(result.is_err(), "Sweeping with negative amount should fail");
assert_eq!(
result.unwrap_err().message,
"Invalid amount: overflow or negative value",
"Error message should indicate invalid amount"
);
}

#[tokio::test]
async fn test_sweep_subaccount_overflow_amount() {
// Setup
let (_, to_subaccountid, _) = setup_principals();
let subaccountid_hex = to_subaccountid.to_hex();
let amount = f64::MAX;

// Execute
let result = sweep_subaccount(subaccountid_hex, amount).await;

// Assert
assert!(result.is_err(), "Sweeping with overflow amount should fail");
assert_eq!(
result.unwrap_err().message,
"Invalid amount: overflow or negative value",
"Error message should indicate invalid amount"
);
}
}
}
Loading