forked from circle-Fi/circleFi-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathe2e.mjs
More file actions
132 lines (109 loc) · 4.87 KB
/
Copy pathe2e.mjs
File metadata and controls
132 lines (109 loc) · 4.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/**
* A whole circle, start to finish, against live Stellar testnet.
*
* Generates throwaway keypairs, funds them from friendbot, then drives every
* write the contract has: create, join, contribute, settle, withdraw. The
* assertions are on the *contract's own balance*, which is exact - member
* balances move by transaction fees too, so they prove less.
*
* node test/e2e.mjs
*
* Takes a couple of minutes: every step is a real transaction on a real ledger.
*/
import assert from 'node:assert/strict';
import { Keypair, TransactionBuilder } from '@stellar/stellar-sdk';
import { CircleFi, TESTNET, TESTNET_NATIVE, amount, statusOf } from '../src/index.js';
const CAPACITY = 3;
const CONTRIBUTION = 10_000_000n; // 1 XLM, in stroops
const ROUND_SECONDS = 300;
const POT = CONTRIBUTION * BigInt(CAPACITY);
const cf = new CircleFi(TESTNET);
const log = (...a) => console.log(...a);
const xlm = (v) => amount(v, 7, 'XLM');
/** A signer is just an address and a signTransaction - here, over a Keypair. */
function signerFor(keypair) {
return {
address: keypair.publicKey(),
async signTransaction(xdr, { networkPassphrase }) {
const tx = TransactionBuilder.fromXDR(xdr, networkPassphrase);
tx.sign(keypair);
return { signedTxXdr: tx.toXDR() };
},
};
}
async function fund(publicKey) {
const r = await fetch(`https://friendbot.stellar.org/?addr=${publicKey}`);
if (!r.ok && r.status !== 400) throw new Error(`friendbot said ${r.status}`);
}
const held = () => cf.tokenBalance(TESTNET_NATIVE, circleId);
log('funding three throwaway accounts from friendbot');
const keys = Array.from({ length: CAPACITY }, () => Keypair.random());
const signers = keys.map(signerFor);
await Promise.all(keys.map((k) => fund(k.publicKey())));
for (const s of signers) log(` ${s.address}`);
log('\ncreating the circle');
const circleId = await cf.create(signers[0], {
token: TESTNET_NATIVE,
contribution: CONTRIBUTION,
roundSeconds: ROUND_SECONDS,
capacity: CAPACITY,
});
log(` ${circleId}`);
log(` ${CAPACITY} members, ${xlm(CONTRIBUTION)} a round, pot ${xlm(POT)}`);
const circle = cf.circle(circleId);
assert.equal(statusOf((await circle.state()).status), 'forming');
assert.equal(await held(), 0n, 'a fresh circle should hold nothing');
log('\njoining - each member locks one round as a deposit');
for (const [i, s] of signers.entries()) {
await circle.join(s);
const inside = await held();
log(` ${i + 1}. ${s.address.slice(0, 8)} joined, circle holds ${xlm(inside)}`);
assert.equal(inside, CONTRIBUTION * BigInt(i + 1), 'deposits should be the only thing held');
}
const started = await circle.state();
assert.equal(statusOf(started.status), 'active', 'a full circle should start');
assert.equal(Number(started.round), 1);
const deposits = CONTRIBUTION * BigInt(CAPACITY);
const order = started.members;
for (let round = 1; round <= CAPACITY; round += 1) {
const recipient = order[round - 1];
log(`\nround ${round} of ${CAPACITY} - the pot goes to ${recipient.slice(0, 8)}`);
for (const s of signers) {
await circle.contribute(s);
assert.equal(await circle.hasContributed(round, s.address), true);
}
assert.equal(
await held(), deposits + POT,
'after everyone pays, the circle should hold the deposits plus a whole pot',
);
const before = await cf.tokenBalance(TESTNET_NATIVE, recipient);
await circle.settle(signers[round % CAPACITY]); // anyone may settle
const after = await cf.tokenBalance(TESTNET_NATIVE, recipient);
assert.equal(await held(), deposits, 'settling should pay the pot out in full');
assert.ok(after > before, 'the recipient should be better off');
log(` paid out ${xlm(POT)}; recipient gained ${xlm(after - before)} after fees`);
const rec = await circle.member(recipient);
assert.equal(rec.received, true, 'the recipient should be marked paid out');
assert.equal(Number(rec.defaults), 0, 'nobody defaulted');
}
const finished = await circle.state();
assert.equal(statusOf(finished.status), 'complete', 'every turn taken means complete');
log('\nwithdrawing deposits');
for (const s of signers) {
await circle.withdraw(s);
log(` ${s.address.slice(0, 8)} took their ${xlm(CONTRIBUTION)} deposit back`);
}
assert.equal(await held(), 0n, 'the circle should end holding nothing at all');
const finalRecords = await Promise.all(signers.map((s) => circle.member(s.address)));
for (const [i, r] of finalRecords.entries()) {
assert.equal(r.received, true, `member ${i + 1} never got a turn`);
assert.equal(Number(r.defaults), 0, `member ${i + 1} defaulted`);
assert.equal(BigInt(r.deposit), 0n, `member ${i + 1} left a deposit behind`);
assert.equal(r.delinquent, false);
}
log(`
every member paid ${CAPACITY} rounds and took the pot exactly once,
every deposit came back, and the contract ended holding nothing.
circle ${circleId}
explorer https://stellar.expert/explorer/testnet/contract/${circleId}
end-to-end test passed`);