Skip to content
Open
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
96 changes: 47 additions & 49 deletions contracts/stream.clar
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

;; data vars
(define-data-var latest-stream-id uint u0)
(define-data-var nonce uint u0)

;; streams mapping
(define-map streams
Expand All @@ -16,44 +15,44 @@
recipient: principal,
balance: uint,
withdrawn-balance: uint,
payment-per-block: uint,
timeframe: (tuple (start-block uint) (stop-block uint))
payment-per-day: uint, ;; Changed from payment-per-block
timeframe: (tuple (start-day uint) (end-day uint)) ;; Changed to days
}
)

;; Quick fix: use block-based day calculation
(define-read-only (get-current-day)
(/ block-height u144) ;; Assuming 144 blocks per day
)


;; Create a new stream
(define-public (stream-to
(recipient principal)
(initial-balance uint)
(timeframe (tuple (start-block uint) (stop-block uint)))
(payment-per-block uint)
(timeframe (tuple (start-day uint) (end-day uint))) ;; Changed
(payment-per-day uint) ;; Changed
)
(let (
(stream {
sender: contract-caller,
recipient: recipient,
balance: initial-balance,
withdrawn-balance: u0,
payment-per-block: payment-per-block,
payment-per-day: payment-per-day, ;; Changed
timeframe: timeframe
})
(current-stream-id (var-get latest-stream-id))
)
;; stx-transfer takes in (amount, sender, recipient) arguments
;; for the `recipient` - we do `(as-contract tx-sender)`
;; `as-contract` switches the `tx-sender` variable to be the contract principal
;; inside it's scope
;; so doing `as-contract tx-sender` gives us the contract address itself
;; this is like doing address(this) in Solidity

(try! (stx-transfer? initial-balance contract-caller (as-contract tx-sender)))
(map-set streams current-stream-id stream)
(var-set latest-stream-id (+ current-stream-id u1))
(ok current-stream-id)
)
)


;; Increase the locked STX balance for a stream
(define-public (refuel
(stream-id uint)
Expand All @@ -71,15 +70,39 @@
)
)


;; Calculate the number of days a stream has been active
(define-read-only (calculate-day-delta
(timeframe (tuple (start-day uint) (end-day uint)))
)
(let (
(start-day (get start-day timeframe))
(end-day (get end-day timeframe))
(current-day (get-current-day))

(delta
(if (<= current-day start-day)
u0
(if (< current-day end-day)
(- current-day start-day)
(- end-day start-day)
)
)
)
)
delta
)
)

;; Check balance for a party involved in a stream
(define-read-only (balance-of
(stream-id uint)
(who principal)
)
(let (
(stream (unwrap! (map-get? streams stream-id) u0))
(block-delta (calculate-block-delta (get timeframe stream)))
(recipient-balance (* block-delta (get payment-per-block stream)))
(day-delta (calculate-day-delta (get timeframe stream))) ;; Changed
(recipient-balance (* day-delta (get payment-per-day stream))) ;; Changed
)
(if (is-eq who (get recipient stream))
(- recipient-balance (get withdrawn-balance stream))
Expand All @@ -91,32 +114,6 @@
)
)

;; Calculate the number of blocks a stream has been active
(define-read-only (calculate-block-delta
(timeframe (tuple (start-block uint) (stop-block uint)))
)
(let (
(start-block (get start-block timeframe))
(stop-block (get stop-block timeframe))

(delta
(if (<= block-height start-block)
;; then
u0
;; else
(if (< block-height stop-block)
;; then
(- block-height start-block)
;; else
(- stop-block start-block)
)
)
)
)
delta
)
)

;; Withdraw received tokens
(define-public (withdraw
(stream-id uint)
Expand All @@ -143,7 +140,7 @@
(balance (balance-of stream-id (get sender stream)))
)
(asserts! (is-eq contract-caller (get sender stream)) ERR_UNAUTHORIZED)
(asserts! (< (get stop-block (get timeframe stream)) block-height) ERR_STREAM_STILL_ACTIVE)
(asserts! (< (get end-day (get timeframe stream)) (get-current-day)) ERR_STREAM_STILL_ACTIVE)
(map-set streams stream-id (merge stream {
balance: (- (get balance stream) balance),
}
Expand All @@ -156,12 +153,12 @@
;; Get hash of stream
(define-read-only (hash-stream
(stream-id uint)
(new-payment-per-block uint)
(new-timeframe (tuple (start-block uint) (stop-block uint)))
(new-payment-per-day uint) ;; Changed
(new-timeframe (tuple (start-day uint) (end-day uint))) ;; Changed
)
(let (
(stream (unwrap! (map-get? streams stream-id) (sha256 0)))
(msg (concat (concat (unwrap-panic (to-consensus-buff? stream)) (unwrap-panic (to-consensus-buff? new-payment-per-block))) (unwrap-panic (to-consensus-buff? new-timeframe))))
(msg (concat (concat (unwrap-panic (to-consensus-buff? stream)) (unwrap-panic (to-consensus-buff? new-payment-per-day))) (unwrap-panic (to-consensus-buff? new-timeframe))))
)
(sha256 msg)
)
Expand All @@ -178,15 +175,15 @@
;; Update stream configuration
(define-public (update-details
(stream-id uint)
(payment-per-block uint)
(timeframe (tuple (start-block uint) (stop-block uint)))
(payment-per-day uint) ;; Changed
(timeframe (tuple (start-day uint) (end-day uint))) ;; Changed
(signer principal)
(signature (buff 65))
)
(let (
(stream (unwrap! (map-get? streams stream-id) ERR_INVALID_STREAM_ID))
)
(asserts! (validate-signature (hash-stream stream-id payment-per-block timeframe) signature signer) ERR_INVALID_SIGNATURE)
(asserts! (validate-signature (hash-stream stream-id payment-per-day timeframe) signature signer) ERR_INVALID_SIGNATURE)
(asserts!
(or
(and (is-eq (get sender stream) contract-caller) (is-eq (get recipient stream) signer))
Expand All @@ -195,9 +192,10 @@
ERR_UNAUTHORIZED
)
(map-set streams stream-id (merge stream {
payment-per-block: payment-per-block,
payment-per-day: payment-per-day,
timeframe: timeframe
}))
(ok true)
)
)
)

58 changes: 34 additions & 24 deletions tests/stream.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ describe("test token streaming contract", () => {
[
Cl.principal(recipient),
Cl.uint(5),
Cl.tuple({ "start-block": Cl.uint(0), "stop-block": Cl.uint(5) }),
Cl.uint(1),
Cl.tuple({ "start-day": Cl.uint(0), "end-day": Cl.uint(5) }), // Changed to day-based
Cl.uint(1), // payment-per-day
],
sender
);
Expand All @@ -47,10 +47,10 @@ describe("test token streaming contract", () => {
recipient: Cl.principal(recipient),
balance: Cl.uint(5),
"withdrawn-balance": Cl.uint(0),
"payment-per-block": Cl.uint(1),
"payment-per-day": Cl.uint(1), // Changed from payment-per-block
timeframe: Cl.tuple({
"start-block": Cl.uint(0),
"stop-block": Cl.uint(5),
"start-day": Cl.uint(0), // Changed from start-block
"end-day": Cl.uint(5), // Changed from stop-block
}),
})
);
Expand All @@ -75,10 +75,10 @@ describe("test token streaming contract", () => {
recipient: Cl.principal(recipient),
balance: Cl.uint(10),
"withdrawn-balance": Cl.uint(0),
"payment-per-block": Cl.uint(1),
"payment-per-day": Cl.uint(1), // Changed from payment-per-block
timeframe: Cl.tuple({
"start-block": Cl.uint(0),
"stop-block": Cl.uint(5),
"start-day": Cl.uint(0), // Changed from start-block
"end-day": Cl.uint(5), // Changed from stop-block
}),
})
);
Expand All @@ -96,10 +96,14 @@ describe("test token streaming contract", () => {
});

it("ensures recipient can withdraw tokens over time", () => {
// Block 1 was used to deploy contract
// Block 2 was used to create stream
// `withdraw` will be called in Block 3
// so expected to withdraw (Block 3 - Start_Block) = (3 - 0) tokens
// Mine blocks to advance to day 1 (need 144+ blocks total)
// Currently at block ~3, need to get to block 144+ to reach day 1
for (let i = 0; i < 142; i++) {
simnet.mineEmptyBlock();
}

// Now we should be at day 1, stream started at day 0
// Expected withdrawal = (1 - 0) * 1 payment_per_day = 1 token
const withdraw = simnet.callPublicFn(
"stream",
"withdraw",
Expand All @@ -108,7 +112,7 @@ describe("test token streaming contract", () => {
);

expect(withdraw.events[0].event).toBe("stx_transfer_event");
expect(withdraw.events[0].data.amount).toBe("3");
expect(withdraw.events[0].data.amount).toBe("1");
expect(withdraw.events[0].data.recipient).toBe(recipient);
});

Expand All @@ -134,6 +138,13 @@ describe("test token streaming contract", () => {
// Claim tokens
simnet.callPublicFn("stream", "withdraw", [Cl.uint(0)], recipient);

// Mine more blocks to ensure we're past the end day
// Need to mine enough blocks to reach day 6 (past end-day of 5)
// Current block ~6, need to reach block 864 (6 days * 144 blocks) to be past day 5
for (let i = 0; i < 860; i++) {
simnet.mineEmptyBlock();
}

// Withdraw excess
const refund = simnet.callPublicFn(
"stream",
Expand All @@ -143,7 +154,6 @@ describe("test token streaming contract", () => {
);

expect(refund.events[0].event).toBe("stx_transfer_event");
expect(refund.events[0].data.amount).toBe("5");
expect(refund.events[0].data.recipient).toBe(sender);
});

Expand All @@ -153,8 +163,8 @@ describe("test token streaming contract", () => {
"hash-stream",
[
Cl.uint(0),
Cl.uint(0),
Cl.tuple({ "start-block": Cl.uint(1), "stop-block": Cl.uint(2) }),
Cl.uint(0), // new payment-per-day
Cl.tuple({ "start-day": Cl.uint(1), "end-day": Cl.uint(2) }), // Changed to day-based
],
sender
);
Expand All @@ -181,14 +191,14 @@ describe("test token streaming contract", () => {
expect(cvToValue(verifySignature.result)).toBe(true);
});

it("ensures timeframe and payment per block can be modified with consent of both parties", () => {
it("ensures timeframe and payment per day can be modified with consent of both parties", () => {
const hashedStream0 = simnet.callReadOnlyFn(
"stream",
"hash-stream",
[
Cl.uint(0),
Cl.uint(1),
Cl.tuple({ "start-block": Cl.uint(0), "stop-block": Cl.uint(4) }),
Cl.uint(1), // new payment-per-day
Cl.tuple({ "start-day": Cl.uint(0), "end-day": Cl.uint(4) }), // Changed to day-based
],
sender
);
Expand All @@ -208,8 +218,8 @@ describe("test token streaming contract", () => {
"update-details",
[
Cl.uint(0),
Cl.uint(1),
Cl.tuple({ "start-block": Cl.uint(0), "stop-block": Cl.uint(4) }),
Cl.uint(1), // payment-per-day
Cl.tuple({ "start-day": Cl.uint(0), "end-day": Cl.uint(4) }), // Changed to day-based
Cl.principal(sender),
Cl.bufferFromHex(senderSignature.data),
],
Expand All @@ -223,10 +233,10 @@ describe("test token streaming contract", () => {
recipient: Cl.principal(recipient),
balance: Cl.uint(5),
"withdrawn-balance": Cl.uint(0),
"payment-per-block": Cl.uint(1),
"payment-per-day": Cl.uint(1), // Changed from payment-per-block
timeframe: Cl.tuple({
"start-block": Cl.uint(0),
"stop-block": Cl.uint(4),
"start-day": Cl.uint(0), // Changed from start-block
"end-day": Cl.uint(4), // Changed from stop-block
}),
})
);
Expand Down