Skip to content

Commit b9c6325

Browse files
committed
Add create-tap-with-borrowed-liquidity convenience function
Combines create-tap and borrow-liquidity into a single transaction so new participants can set up both their outgoing capacity (deposit) and incoming capacity (borrowed liquidity) atomically, reducing the on-chain footprint for the common mailbox setup case. Also adds two tests covering the happy path and invalid-signature error path for the new function.
1 parent 6789442 commit b9c6325

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed

contracts/reservoir.clar

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,55 @@
439439
)
440440
)
441441

442+
;;; Create a new tap and immediately borrow liquidity from the reservoir in a
443+
;;; single transaction. This is a convenience function combining `create-tap`
444+
;;; and `borrow-liquidity` for the common setup case where a new participant
445+
;;; wants both outgoing capacity (their own deposit) and incoming capacity
446+
;;; (borrowed liquidity) at once.
447+
;;;
448+
;;; The caller must first obtain `reservoir-signature` from the reservoir
449+
;;; operator off-chain, confirming the post-borrow balances.
450+
;;;
451+
;;; Parameters:
452+
;;; - `stackflow`: the StackFlow token contract
453+
;;; - `token`: optional SIP-010 token (none for STX)
454+
;;; - `tap-amount`: amount the caller deposits to fund their sending side
455+
;;; - `tap-nonce`: nonce for the initial tap creation (typically u0)
456+
;;; - `borrow-amount`: amount of liquidity to borrow from the reservoir
457+
;;; - `borrow-fee`: fee paid to the reservoir for the borrow
458+
;;; (must be >= get-borrow-fee(borrow-amount))
459+
;;; - `my-balance`: caller's balance after the borrow deposit
460+
;;; (should equal tap-amount since no transfers have occurred yet)
461+
;;; - `reservoir-balance`: reservoir's balance after the borrow deposit
462+
;;; (should equal borrow-amount)
463+
;;; - `my-signature`: caller's SIP-018 signature over the post-borrow state
464+
;;; - `reservoir-signature`: reservoir's SIP-018 signature over the post-borrow state
465+
;;; - `borrow-nonce`: nonce for the borrow deposit (must be > tap-nonce)
466+
;;;
467+
;;; Returns:
468+
;;; - `(ok expire-block)` on success, where expire-block is the burn block
469+
;;; height at which the borrowed liquidity expires
470+
;;; - Any error from `create-tap` or `borrow-liquidity`
471+
(define-public (create-tap-with-borrowed-liquidity
472+
(stackflow <stackflow-token>)
473+
(token (optional <sip-010>))
474+
(tap-amount uint)
475+
(tap-nonce uint)
476+
(borrow-amount uint)
477+
(borrow-fee uint)
478+
(my-balance uint)
479+
(reservoir-balance uint)
480+
(my-signature (buff 65))
481+
(reservoir-signature (buff 65))
482+
(borrow-nonce uint)
483+
)
484+
(begin
485+
(try! (create-tap stackflow token tap-amount tap-nonce))
486+
(borrow-liquidity stackflow borrow-amount borrow-fee token my-balance
487+
reservoir-balance my-signature reservoir-signature borrow-nonce)
488+
)
489+
)
490+
442491
;; ----- Read-only functions -----
443492

444493
;;; Calculate the fee for borrowing a given amount.

tests/reservoir.test.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,133 @@ describe("reservoir", () => {
12171217
});
12181218
});
12191219

1220+
describe("create-tap-with-borrowed-liquidity", () => {
1221+
beforeEach(() => {
1222+
// Set rate to 10% and add liquidity
1223+
simnet.callPublicFn(
1224+
"reservoir",
1225+
"set-borrow-rate",
1226+
[Cl.uint(1000)],
1227+
deployer
1228+
);
1229+
simnet.callPublicFn(
1230+
"reservoir",
1231+
"add-liquidity",
1232+
[Cl.none(), Cl.uint(5000000000)],
1233+
deployer
1234+
);
1235+
});
1236+
1237+
it("creates a tap and borrows liquidity in one call", () => {
1238+
const tapAmount = 1000000;
1239+
const tapNonce = 0;
1240+
const borrowAmount = 50000;
1241+
const borrowFee = 5000; // 10%
1242+
const borrowNonce = 1;
1243+
1244+
const mySignature = generateDepositSignature(
1245+
address1PK,
1246+
null,
1247+
address1,
1248+
reservoirContract,
1249+
tapAmount,
1250+
borrowAmount,
1251+
borrowNonce,
1252+
reservoirContract
1253+
);
1254+
1255+
const reservoirSignature = generateDepositSignature(
1256+
deployerPK,
1257+
null,
1258+
reservoirContract,
1259+
address1,
1260+
borrowAmount,
1261+
tapAmount,
1262+
borrowNonce,
1263+
reservoirContract
1264+
);
1265+
1266+
const { result } = simnet.callPublicFn(
1267+
"reservoir",
1268+
"create-tap-with-borrowed-liquidity",
1269+
[
1270+
Cl.principal(stackflowContract),
1271+
Cl.none(),
1272+
Cl.uint(tapAmount),
1273+
Cl.uint(tapNonce),
1274+
Cl.uint(borrowAmount),
1275+
Cl.uint(borrowFee),
1276+
Cl.uint(tapAmount),
1277+
Cl.uint(borrowAmount),
1278+
Cl.buffer(mySignature),
1279+
Cl.buffer(reservoirSignature),
1280+
Cl.uint(borrowNonce),
1281+
],
1282+
address1
1283+
);
1284+
expect(result).toBeOk(
1285+
Cl.uint(simnet.burnBlockHeight + BORROW_TERM_BLOCKS)
1286+
);
1287+
1288+
// Verify balances: reservoir funded tap + borrow, minus borrow amount + fee
1289+
const stxBalances = simnet.getAssetsMap().get("STX")!;
1290+
// reservoir: 5000000000 - borrowAmount + borrowFee
1291+
expect(stxBalances.get(reservoirContract)).toBe(4999955000n);
1292+
// stackflow: tapAmount + borrowAmount
1293+
expect(stxBalances.get(stackflowContract)).toBe(1050000n);
1294+
});
1295+
1296+
it("fails if borrow signature is wrong", () => {
1297+
const tapAmount = 1000000;
1298+
const borrowAmount = 50000;
1299+
const borrowFee = 5000;
1300+
const borrowNonce = 1;
1301+
1302+
// Use a wrong signature (signed with address2's key instead of address1's)
1303+
const wrongMySignature = generateDepositSignature(
1304+
address2PK,
1305+
null,
1306+
address1,
1307+
reservoirContract,
1308+
tapAmount,
1309+
borrowAmount,
1310+
borrowNonce,
1311+
reservoirContract
1312+
);
1313+
1314+
const reservoirSignature = generateDepositSignature(
1315+
deployerPK,
1316+
null,
1317+
reservoirContract,
1318+
address1,
1319+
borrowAmount,
1320+
tapAmount,
1321+
borrowNonce,
1322+
reservoirContract
1323+
);
1324+
1325+
const { result } = simnet.callPublicFn(
1326+
"reservoir",
1327+
"create-tap-with-borrowed-liquidity",
1328+
[
1329+
Cl.principal(stackflowContract),
1330+
Cl.none(),
1331+
Cl.uint(tapAmount),
1332+
Cl.uint(0),
1333+
Cl.uint(borrowAmount),
1334+
Cl.uint(borrowFee),
1335+
Cl.uint(tapAmount),
1336+
Cl.uint(borrowAmount),
1337+
Cl.buffer(wrongMySignature),
1338+
Cl.buffer(reservoirSignature),
1339+
Cl.uint(borrowNonce),
1340+
],
1341+
address1
1342+
);
1343+
expect(result).toBeErr(Cl.uint(StackflowError.InvalidSignature));
1344+
});
1345+
});
1346+
12201347
describe("force-closures", () => {
12211348
beforeEach(() => {
12221349
// Add liquidity to reservoir

0 commit comments

Comments
 (0)