From 172db751a5597afe4f22316fdf24721a0b84f1c4 Mon Sep 17 00:00:00 2001 From: davidbrai Date: Mon, 2 Dec 2024 09:40:15 +0000 Subject: [PATCH 1/3] add unstreamedETHForNoun --- .../contracts/StreamEscrow.sol | 11 +++++ .../test/foundry/StreamEscrow.t.sol | 48 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/packages/nouns-contracts/contracts/StreamEscrow.sol b/packages/nouns-contracts/contracts/StreamEscrow.sol index 5acd8a077..10048abd9 100644 --- a/packages/nouns-contracts/contracts/StreamEscrow.sol +++ b/packages/nouns-contracts/contracts/StreamEscrow.sol @@ -245,6 +245,17 @@ contract StreamEscrow is IStreamEscrow { return !streams[nounId].canceled && streams[nounId].lastTick > currentTick; } + function unstreamedETHForNoun(uint256 nounId) public view returns (uint256) { + Stream memory stream = streams[nounId]; + uint32 currentTick_ = currentTick; + if (!isStreamActive(stream, currentTick_)) { + return 0; + } + + uint256 ticksLeft = stream.lastTick - currentTick_; + return ticksLeft * stream.ethPerTick; + } + function isStreamActive(Stream memory stream, uint32 tick) internal pure returns (bool) { return !stream.canceled && stream.lastTick > tick; } diff --git a/packages/nouns-contracts/test/foundry/StreamEscrow.t.sol b/packages/nouns-contracts/test/foundry/StreamEscrow.t.sol index 99d7e1666..4ea363ced 100644 --- a/packages/nouns-contracts/test/foundry/StreamEscrow.t.sol +++ b/packages/nouns-contracts/test/foundry/StreamEscrow.t.sol @@ -717,6 +717,54 @@ contract RescueTokensTest is BaseStreamEscrowTest { } } +contract UnstreamedETHTest is BaseStreamEscrowTest { + function test_unstreamedETHForNoun() public { + vm.prank(streamCreator); + escrow.forwardAllAndCreateStream{ value: 1 ether }({ nounId: 1, streamLengthInTicks: 20 }); + + // 1 ether / 20 = 0.05 eth per tick + assertEq(escrow.unstreamedETHForNoun(1), 1 ether); + + // forward 5 ticks + for (uint i; i < 5; i++) { + forwardOneDay(); + } + // check unstreamed eth + assertEq(escrow.unstreamedETHForNoun(1), 0.75 ether); + + // forward 15 more ticks + for (uint i; i < 15; i++) { + forwardOneDay(); + } + // check unstreamed eth + assertEq(escrow.unstreamedETHForNoun(1), 0 ether); + } + + function test_unstreamETHForNoun_canceledStream() public { + vm.prank(streamCreator); + escrow.forwardAllAndCreateStream{ value: 1 ether }({ nounId: 1, streamLengthInTicks: 20 }); + + // 1 ether / 20 = 0.05 eth per tick + assertEq(escrow.unstreamedETHForNoun(1), 1 ether); + + // forward 5 ticks + for (uint i; i < 5; i++) { + forwardOneDay(); + } + // check unstreamed eth + assertEq(escrow.unstreamedETHForNoun(1), 0.75 ether); + + // cancel stream + vm.prank(streamCreator); + nounsToken.approve(address(escrow), 1); + vm.prank(streamCreator); + escrow.cancelStream(1); + + // check unstreamed eth is zero + assertEq(escrow.unstreamedETHForNoun(1), 0 ether); + } +} + contract StreamEscrowGasTest is BaseStreamEscrowTest { function setUp() public virtual override { super.setUp(); From f1c4fdf7e696c1fc99b0da0546d4e34f47011179 Mon Sep 17 00:00:00 2001 From: davidbrai Date: Mon, 2 Dec 2024 09:43:05 +0000 Subject: [PATCH 2/3] add test --- .../nouns-contracts/test/foundry/StreamEscrow.t.sol | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/nouns-contracts/test/foundry/StreamEscrow.t.sol b/packages/nouns-contracts/test/foundry/StreamEscrow.t.sol index 4ea363ced..1bc3e8d00 100644 --- a/packages/nouns-contracts/test/foundry/StreamEscrow.t.sol +++ b/packages/nouns-contracts/test/foundry/StreamEscrow.t.sol @@ -740,7 +740,7 @@ contract UnstreamedETHTest is BaseStreamEscrowTest { assertEq(escrow.unstreamedETHForNoun(1), 0 ether); } - function test_unstreamETHForNoun_canceledStream() public { + function test_unstreamedETHForNoun_canceledStream() public { vm.prank(streamCreator); escrow.forwardAllAndCreateStream{ value: 1 ether }({ nounId: 1, streamLengthInTicks: 20 }); @@ -763,6 +763,17 @@ contract UnstreamedETHTest is BaseStreamEscrowTest { // check unstreamed eth is zero assertEq(escrow.unstreamedETHForNoun(1), 0 ether); } + + function test_unstreamedETHForNoun_returnsZeroForNonExistentStream() public { + assertEq(escrow.unstreamedETHForNoun(1), 0 ether); + + // forward 5 ticks + for (uint i; i < 5; i++) { + forwardOneDay(); + } + + assertEq(escrow.unstreamedETHForNoun(3), 0 ether); + } } contract StreamEscrowGasTest is BaseStreamEscrowTest { From 8888e0d9e3c0a5e8bca797829f73d699e795f34e Mon Sep 17 00:00:00 2001 From: davidbrai Date: Tue, 3 Dec 2024 11:53:55 +0000 Subject: [PATCH 3/3] add natspec --- packages/nouns-contracts/contracts/StreamEscrow.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/nouns-contracts/contracts/StreamEscrow.sol b/packages/nouns-contracts/contracts/StreamEscrow.sol index 10048abd9..d174841d2 100644 --- a/packages/nouns-contracts/contracts/StreamEscrow.sol +++ b/packages/nouns-contracts/contracts/StreamEscrow.sol @@ -245,6 +245,11 @@ contract StreamEscrow is IStreamEscrow { return !streams[nounId].canceled && streams[nounId].lastTick > currentTick; } + /** + * @notice Returns the amount of ETH that was not yet streamed for a specific Noun token. + * Returns zero for inactive streams. + * @param nounId The ID of the Noun token to check the stream for. + */ function unstreamedETHForNoun(uint256 nounId) public view returns (uint256) { Stream memory stream = streams[nounId]; uint32 currentTick_ = currentTick;