Skip to content

Commit

Permalink
feat: sweep per single subaccount id (#27)
Browse files Browse the repository at this point in the history
* feat: added method sweep_subaccount

* added unit tests for sweep_subaccount

* return type adjustments for sweep_subaccount

* adjusted candid definition

* fix indentation

* adjusted return type casting

* multiple amount by 100mio

* adjust amount from u64 to f64

* fix formatting via cargo fmt

* fix test cases

* fix indentation

---------

Co-authored-by: Stephen Antoni <luxeaves@gmail.com>
  • Loading branch information
steve24grd and luxeave authored Aug 12, 2024
1 parent 8a01ebe commit 4f13ff0
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 0 deletions.
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"
);
}
}
}

0 comments on commit 4f13ff0

Please sign in to comment.