Skip to content

Commit 661a44b

Browse files
committed
test: withdraw
1 parent 91b7a39 commit 661a44b

File tree

6 files changed

+193
-56
lines changed

6 files changed

+193
-56
lines changed

src/main.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ mod types {
3030
pub fee_to: Option<Principal>,
3131
pub custodians: Option<HashSet<Principal>>,
3232
pub cap: Option<Principal>,
33+
// TODO: threshold
3334
}
3435
#[derive(CandidType, Deserialize)]
3536
pub enum GenericValue {
@@ -83,6 +84,8 @@ mod types {
8384
ErrorOperationStyle,
8485
AmountTooSmall,
8586
LedgerTrap,
87+
InvalidAccountId,
88+
InvalidE8sAmount,
8689
Other(String),
8790
}
8891
#[derive(CandidType, Deserialize)]
@@ -302,11 +305,14 @@ mod ledger {
302305
self.balances.insert(to, self.balance_of(&to) + amount);
303306
}
304307

305-
pub fn withdraw(&mut self, to: Principal, amount: Nat) {
306-
self.is_enough_balance_to_spend(&to, amount.clone())
308+
pub fn withdraw(&mut self, from: Principal, amount: Nat) {
309+
self.is_enough_balance_to_spend(&from, amount.clone())
307310
.then(|| ())
308311
.expect("TokenError::InsufficientBalance"); // guarding state
309-
self.balances.insert(to, self.balance_of(&to) - amount);
312+
self.balances.insert(from, self.balance_of(&from) - amount);
313+
self.balance_of(&from)
314+
.eq(&0)
315+
.then(|| self.balances.remove(&from));
310316
}
311317

312318
pub fn add_tx(
@@ -718,16 +724,18 @@ async fn withdraw(amount: Nat, to: String) -> Result<Nat, TokenError> {
718724
.ge(&amount)
719725
.then(|| ())
720726
.ok_or(TokenError::InsufficientBalance)?;
727+
// make sure the canister only performs send ICP when it has enough balance.
721728
total_supply()
722729
.ge(&amount)
723730
.then(|| ())
724731
.ok_or(TokenError::InsufficientBalance)?;
725732
let args = SendArgs {
726733
memo: Memo(0),
727-
amount: (Tokens::from_e8s(amount_e8s) - DEFAULT_TRANSFER_FEE).unwrap(),
734+
amount: (Tokens::from_e8s(amount_e8s) - DEFAULT_TRANSFER_FEE)
735+
.map_err(|_| TokenError::InvalidE8sAmount)?,
728736
fee: DEFAULT_TRANSFER_FEE,
729737
from_subaccount: None,
730-
to: AccountIdentifier::from_hex(&to).unwrap(),
738+
to: AccountIdentifier::from_hex(&to).map_err(|_| TokenError::InvalidAccountId)?,
731739
created_at_time: None,
732740
};
733741
ledger::with_mut(|ledger| ledger.withdraw(caller, amount.clone()));
@@ -741,14 +749,19 @@ async fn withdraw(amount: Nat, to: String) -> Result<Nat, TokenError> {
741749
ledger::with_mut(|ledger| ledger.mint(caller, amount.clone()));
742750
TokenError::LedgerTrap
743751
})
744-
.map(|_| {
752+
.map(|(block_height,)| {
745753
ledger::with_mut(|ledger| {
746754
Nat::from(ledger.add_tx(
747755
caller,
748-
"burn".into(),
756+
"withdraw".into(),
749757
vec![
750-
("to".into(), GenericValue::Principal(caller)),
758+
("from".into(), GenericValue::Principal(caller)),
759+
("to".into(), GenericValue::TextContent(to)),
751760
("amount".into(), GenericValue::NatContent(amount)),
761+
(
762+
"block_height".into(),
763+
GenericValue::Nat64Content(block_height),
764+
),
752765
],
753766
))
754767
})

test/factory/wicp_idl.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,12 @@ export type TokenError = { 'InsufficientAllowance' : null } |
5959
{ 'BlockError' : null } |
6060
{ 'InsufficientBalance' : null } |
6161
{ 'TxNotFound' : null } |
62+
{ 'InvalidAccountId' : null } |
6263
{ 'ErrorOperationStyle' : null } |
6364
{ 'Unauthorized' : null } |
6465
{ 'LedgerTrap' : null } |
6566
{ 'ErrorTo' : null } |
67+
{ 'InvalidE8sAmount' : null } |
6668
{ 'Other' : string } |
6769
{ 'BlockUsed' : null } |
6870
{ 'AmountTooSmall' : null };

test/factory/wicp_idl.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ export const idlFactory = ({ IDL }) => {
1616
'BlockError' : IDL.Null,
1717
'InsufficientBalance' : IDL.Null,
1818
'TxNotFound' : IDL.Null,
19+
'InvalidAccountId' : IDL.Null,
1920
'ErrorOperationStyle' : IDL.Null,
2021
'Unauthorized' : IDL.Null,
2122
'LedgerTrap' : IDL.Null,
2223
'ErrorTo' : IDL.Null,
24+
'InvalidE8sAmount' : IDL.Null,
2325
'Other' : IDL.Text,
2426
'BlockUsed' : IDL.Null,
2527
'AmountTooSmall' : IDL.Null,

test/integration/ledger.test.ts

Lines changed: 124 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ test.serial("verify users allowance after `approve` with fee.", async t => {
508508
).forEach(result => t.is(result, BigInt(0)));
509509
});
510510

511-
test.serial("verify transaction after `approve` with fee.", async t => {
511+
test.serial("verify transactions after `approve` with fee.", async t => {
512512
(await Promise.all(allActors.map(actor => actor.transaction(BigInt(3))))).forEach(result =>
513513
t.like(result, {
514514
Ok: {
@@ -679,7 +679,7 @@ test.serial("verify users allowance after `approve` with zero fee.", async t =>
679679
).forEach(result => t.is(result, BigInt(0)));
680680
});
681681

682-
test.serial("verify transaction after `approve` with zero fee.", async t => {
682+
test.serial("verify transactions after `approve` with zero fee.", async t => {
683683
(await Promise.all(allActors.map(actor => actor.transaction(BigInt(6))))).forEach(result =>
684684
t.like(result, {
685685
Ok: {
@@ -932,7 +932,7 @@ test.serial("verify users allowance after `transferFrom` with fee.", async t =>
932932
).forEach(result => t.is(result, BigInt(0)));
933933
});
934934

935-
test.serial("verify transaction after `transferFrom` with fee.", async t => {
935+
test.serial("verify transactions after `transferFrom` with fee.", async t => {
936936
(await Promise.all(allActors.map(actor => actor.transaction(BigInt(10))))).forEach(result =>
937937
t.like(result, {
938938
Ok: {
@@ -1082,7 +1082,7 @@ test.serial("verify users allowance after `transferFrom` with zero fee.", async
10821082
).forEach(result => t.is(result, BigInt(0)));
10831083
});
10841084

1085-
test.serial("verify transaction after `transferFrom` with zero fee.", async t => {
1085+
test.serial("verify transactions after `transferFrom` with zero fee.", async t => {
10861086
(await Promise.all(allActors.map(actor => actor.transaction(BigInt(11))))).forEach(result =>
10871087
t.like(result, {
10881088
Ok: {
@@ -1221,7 +1221,7 @@ test.serial("verify users balance after `transfer` with fee.", async t => {
12211221
);
12221222
});
12231223

1224-
test.serial("verify transaction after `transfer` with fee.", async t => {
1224+
test.serial("verify transactions after `transfer` with fee.", async t => {
12251225
(await Promise.all(allActors.map(actor => actor.transaction(BigInt(15))))).forEach(result =>
12261226
t.like(result, {
12271227
Ok: {
@@ -1326,7 +1326,7 @@ test.serial("verify users balance after `transfer` with zero fee.", async t => {
13261326
);
13271327
});
13281328

1329-
test.serial("verify transaction after `transfer` with zero fee.", async t => {
1329+
test.serial("verify transactions after `transfer` with zero fee.", async t => {
13301330
(await Promise.all(allActors.map(actor => actor.transaction(BigInt(19))))).forEach(result =>
13311331
t.like(result, {
13321332
Ok: {
@@ -1385,3 +1385,121 @@ test.serial("verify transaction after `transfer` with zero fee.", async t => {
13851385
);
13861386
});
13871387

1388+
test.serial("error on `withdraw` with amount exceed u64 range.", async t => {
1389+
t.deepEqual(await aliceWicpActor.withdraw(BigInt("0xffffffffffffffffffff"), aliceAccountId.toHex()), {
1390+
Err: {Other: "failed to cast usize from nat"}
1391+
});
1392+
});
1393+
1394+
test.serial("error on `withdraw` with invalid e8s amount.", async t => {
1395+
t.deepEqual(await johnWicpActor.withdraw(BigInt(0), johnAccountId.toHex()), {
1396+
Err: {InvalidE8sAmount: null}
1397+
});
1398+
});
1399+
1400+
test.serial("error on `withdraw` with invalid account id.", async t => {
1401+
t.deepEqual(await aliceWicpActor.withdraw(BigInt(10_000), `${aliceAccountId.toHex()}asdfasfjdklfjsf"`), {
1402+
Err: {InvalidAccountId: null}
1403+
});
1404+
});
1405+
1406+
test.serial("error on `withdraw` when owner have insufficient balance WICP.", async t => {
1407+
t.deepEqual(await aliceWicpActor.withdraw(BigInt(200_000_001), aliceAccountId.toHex()), {
1408+
Err: {InsufficientBalance: null}
1409+
});
1410+
t.deepEqual(await bobWicpActor.withdraw(BigInt(100_000_001), bobAccountId.toHex()), {
1411+
Err: {InsufficientBalance: null}
1412+
});
1413+
t.deepEqual(await johnWicpActor.withdraw(BigInt(1), johnAccountId.toHex()), {
1414+
Err: {InsufficientBalance: null}
1415+
});
1416+
t.deepEqual(await custodianWicpActor.withdraw(BigInt(69_700_000_001), custodianAccountId.toHex()), {
1417+
Err: {InsufficientBalance: null}
1418+
});
1419+
});
1420+
1421+
test.serial("withdraw WICP.", async t => {
1422+
t.deepEqual(await aliceWicpActor.withdraw(BigInt(200_000_000), aliceAccountId.toHex()), {
1423+
Ok: BigInt(23)
1424+
});
1425+
t.deepEqual(await bobWicpActor.withdraw(BigInt(100_000_000), bobAccountId.toHex()), {
1426+
Ok: BigInt(24)
1427+
});
1428+
t.deepEqual(await custodianWicpActor.withdraw(BigInt(69_700_000_000), custodianAccountId.toHex()), {
1429+
Ok: BigInt(25)
1430+
});
1431+
});
1432+
1433+
test.serial("verify stats after `withdraw`.", async t => {
1434+
const result = await custodianWicpActor.stats();
1435+
t.truthy(result.cycles);
1436+
t.is(result.icps, BigInt(0));
1437+
t.is(result.total_supply, BigInt(0));
1438+
t.is(result.total_transactions, BigInt(25));
1439+
t.is(result.total_unique_holders, BigInt(0));
1440+
t.truthy(await custodianWicpActor.cycles());
1441+
t.is(await custodianWicpActor.icps(), BigInt(0));
1442+
t.is(await custodianWicpActor.totalSupply(), BigInt(0));
1443+
t.is(await custodianWicpActor.totalTransactions(), BigInt(25));
1444+
t.is(await custodianWicpActor.totalUniqueHolders(), BigInt(0));
1445+
});
1446+
1447+
test.serial("verify users balance after `withdraw`.", async t => {
1448+
(await Promise.all(allActors.map(actor => actor.balanceOf(aliceIdentity.getPrincipal())))).forEach(result =>
1449+
t.is(result, BigInt(0))
1450+
);
1451+
(await Promise.all(allActors.map(actor => actor.balanceOf(bobIdentity.getPrincipal())))).forEach(result =>
1452+
t.is(result, BigInt(0))
1453+
);
1454+
(await Promise.all(allActors.map(actor => actor.balanceOf(johnIdentity.getPrincipal())))).forEach(result =>
1455+
t.is(result, BigInt(0))
1456+
);
1457+
(await Promise.all(allActors.map(actor => actor.balanceOf(custodianIdentity.getPrincipal())))).forEach(result =>
1458+
t.is(result, BigInt(0))
1459+
);
1460+
});
1461+
1462+
test.serial("verify transactions after `withdraw`.", async t => {
1463+
(await Promise.all(allActors.map(actor => actor.transaction(BigInt(23))))).forEach(result =>
1464+
t.like(result, {
1465+
Ok: {
1466+
operation: "withdraw",
1467+
details: [
1468+
["from", {Principal: aliceIdentity.getPrincipal()}],
1469+
["to", {TextContent: aliceAccountId.toHex()}],
1470+
["amount", {NatContent: BigInt(200_000_000)}],
1471+
["block_height", {Nat64Content: BigInt(8)}]
1472+
],
1473+
caller: aliceIdentity.getPrincipal()
1474+
}
1475+
})
1476+
);
1477+
(await Promise.all(allActors.map(actor => actor.transaction(BigInt(24))))).forEach(result =>
1478+
t.like(result, {
1479+
Ok: {
1480+
operation: "withdraw",
1481+
details: [
1482+
["from", {Principal: bobIdentity.getPrincipal()}],
1483+
["to", {TextContent: bobAccountId.toHex()}],
1484+
["amount", {NatContent: BigInt(100_000_000)}],
1485+
["block_height", {Nat64Content: BigInt(9)}]
1486+
],
1487+
caller: bobIdentity.getPrincipal()
1488+
}
1489+
})
1490+
);
1491+
(await Promise.all(allActors.map(actor => actor.transaction(BigInt(25))))).forEach(result =>
1492+
t.like(result, {
1493+
Ok: {
1494+
operation: "withdraw",
1495+
details: [
1496+
["from", {Principal: custodianIdentity.getPrincipal()}],
1497+
["to", {TextContent: custodianAccountId.toHex()}],
1498+
["amount", {NatContent: BigInt(69_700_000_000)}],
1499+
["block_height", {Nat64Content: BigInt(10)}]
1500+
],
1501+
caller: custodianIdentity.getPrincipal()
1502+
}
1503+
})
1504+
);
1505+
});

0 commit comments

Comments
 (0)