Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions contracts/reservoir.clar
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,55 @@
)
)

;;; Create a new tap and immediately borrow liquidity from the reservoir in a
;;; single transaction. This is a convenience function combining `create-tap`
;;; and `borrow-liquidity` for the common setup case where a new participant
;;; wants both outgoing capacity (their own deposit) and incoming capacity
;;; (borrowed liquidity) at once.
;;;
;;; The caller must first obtain `reservoir-signature` from the reservoir
;;; operator off-chain, confirming the post-borrow balances.
;;;
;;; Parameters:
;;; - `stackflow`: the StackFlow token contract
;;; - `token`: optional SIP-010 token (none for STX)
;;; - `tap-amount`: amount the caller deposits to fund their sending side
;;; - `tap-nonce`: nonce for the initial tap creation (typically u0)
;;; - `borrow-amount`: amount of liquidity to borrow from the reservoir
;;; - `borrow-fee`: fee paid to the reservoir for the borrow
;;; (must be >= get-borrow-fee(borrow-amount))
;;; - `my-balance`: caller's balance after the borrow deposit
;;; (should equal tap-amount since no transfers have occurred yet)
;;; - `reservoir-balance`: reservoir's balance after the borrow deposit
;;; (should equal borrow-amount)
;;; - `my-signature`: caller's SIP-018 signature over the post-borrow state
;;; - `reservoir-signature`: reservoir's SIP-018 signature over the post-borrow state
;;; - `borrow-nonce`: nonce for the borrow deposit (must be > tap-nonce)
;;;
;;; Returns:
;;; - `(ok expire-block)` on success, where expire-block is the burn block
;;; height at which the borrowed liquidity expires
;;; - Any error from `create-tap` or `borrow-liquidity`
(define-public (create-tap-with-borrowed-liquidity
(stackflow <stackflow-token>)
(token (optional <sip-010>))
(tap-amount uint)
(tap-nonce uint)
(borrow-amount uint)
(borrow-fee uint)
(my-balance uint)
(reservoir-balance uint)
(my-signature (buff 65))
(reservoir-signature (buff 65))
(borrow-nonce uint)
)
(begin
(try! (create-tap stackflow token tap-amount tap-nonce))
(borrow-liquidity stackflow borrow-amount borrow-fee token my-balance
reservoir-balance my-signature reservoir-signature borrow-nonce)
)
)

;; ----- Read-only functions -----

;;; Calculate the fee for borrowing a given amount.
Expand Down
127 changes: 127 additions & 0 deletions tests/reservoir.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1217,6 +1217,133 @@ describe("reservoir", () => {
});
});

describe("create-tap-with-borrowed-liquidity", () => {
beforeEach(() => {
// Set rate to 10% and add liquidity
simnet.callPublicFn(
"reservoir",
"set-borrow-rate",
[Cl.uint(1000)],
deployer
);
simnet.callPublicFn(
"reservoir",
"add-liquidity",
[Cl.none(), Cl.uint(5000000000)],
deployer
);
});

it("creates a tap and borrows liquidity in one call", () => {
const tapAmount = 1000000;
const tapNonce = 0;
const borrowAmount = 50000;
const borrowFee = 5000; // 10%
const borrowNonce = 1;

const mySignature = generateDepositSignature(
address1PK,
null,
address1,
reservoirContract,
tapAmount,
borrowAmount,
borrowNonce,
reservoirContract
);

const reservoirSignature = generateDepositSignature(
deployerPK,
null,
reservoirContract,
address1,
borrowAmount,
tapAmount,
borrowNonce,
reservoirContract
);

const { result } = simnet.callPublicFn(
"reservoir",
"create-tap-with-borrowed-liquidity",
[
Cl.principal(stackflowContract),
Cl.none(),
Cl.uint(tapAmount),
Cl.uint(tapNonce),
Cl.uint(borrowAmount),
Cl.uint(borrowFee),
Cl.uint(tapAmount),
Cl.uint(borrowAmount),
Cl.buffer(mySignature),
Cl.buffer(reservoirSignature),
Cl.uint(borrowNonce),
],
address1
);
expect(result).toBeOk(
Cl.uint(simnet.burnBlockHeight + BORROW_TERM_BLOCKS)
);

// Verify balances: reservoir funded tap + borrow, minus borrow amount + fee
const stxBalances = simnet.getAssetsMap().get("STX")!;
// reservoir: 5000000000 - borrowAmount + borrowFee
expect(stxBalances.get(reservoirContract)).toBe(4999955000n);
// stackflow: tapAmount + borrowAmount
expect(stxBalances.get(stackflowContract)).toBe(1050000n);
});

it("fails if borrow signature is wrong", () => {
const tapAmount = 1000000;
const borrowAmount = 50000;
const borrowFee = 5000;
const borrowNonce = 1;

// Use a wrong signature (signed with address2's key instead of address1's)
const wrongMySignature = generateDepositSignature(
address2PK,
null,
address1,
reservoirContract,
tapAmount,
borrowAmount,
borrowNonce,
reservoirContract
);

const reservoirSignature = generateDepositSignature(
deployerPK,
null,
reservoirContract,
address1,
borrowAmount,
tapAmount,
borrowNonce,
reservoirContract
);

const { result } = simnet.callPublicFn(
"reservoir",
"create-tap-with-borrowed-liquidity",
[
Cl.principal(stackflowContract),
Cl.none(),
Cl.uint(tapAmount),
Cl.uint(0),
Cl.uint(borrowAmount),
Cl.uint(borrowFee),
Cl.uint(tapAmount),
Cl.uint(borrowAmount),
Cl.buffer(wrongMySignature),
Cl.buffer(reservoirSignature),
Cl.uint(borrowNonce),
],
address1
);
expect(result).toBeErr(Cl.uint(StackflowError.InvalidSignature));
});
});

describe("force-closures", () => {
beforeEach(() => {
// Add liquidity to reservoir
Expand Down
Loading