-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spike: Payment channel authorization grants #36 #41
Changes from all commits
4220674
c4fa730
1b12800
76cd691
78efafa
733d866
4b6f4d3
288f792
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ contract Payment { | |
error AmountRequired(); | ||
error ChannelLocked(); | ||
error InsufficientFunds(); | ||
error NotAuthorized(); | ||
|
||
event UnlockTimerStarted( | ||
address indexed publisher, address indexed provider, bytes32 indexed podId, uint256 unlockedAt | ||
|
@@ -30,6 +31,7 @@ contract Payment { | |
uint256 withdrawnByProvider; | ||
uint256 unlockTime; // minimum time in seconds needed to unlock the funds | ||
uint256 unlockedAt; // time @ unlock + unlockTime | ||
mapping(address => bool) authorized; | ||
} | ||
|
||
// publisher => provider => token => PodID => funds | ||
|
@@ -73,11 +75,13 @@ contract Payment { | |
} | ||
|
||
// initiate the process of unlocking the funds stored in the contract | ||
function unlock(address provider, bytes32 podId) public { | ||
address publisher = msg.sender; | ||
function unlock(address publisher, address provider, bytes32 podId) public { | ||
// check if the caller is authorized to unlock channel | ||
Channel storage channel = channels[publisher][provider][podId]; | ||
if (channel.investedByPublisher == 0) revert DoesNotExist(); | ||
|
||
if (msg.sender != publisher) { | ||
if (channel.authorized[msg.sender] == false) revert NotAuthorized(); | ||
} | ||
uint256 newUnlockedAt = block.timestamp + channel.unlockTime; | ||
if (channel.unlockedAt == 0 || channel.unlockedAt < newUnlockedAt) { | ||
channel.unlockedAt = newUnlockedAt; | ||
|
@@ -86,9 +90,12 @@ contract Payment { | |
} | ||
|
||
// transfer the now-unlocked funds back to the publisher | ||
function withdrawUnlocked(address provider, bytes32 podId) public { | ||
address publisher = msg.sender; | ||
function withdrawUnlocked(address publisher, address provider, bytes32 podId) public { | ||
// check if the caller is authorized to withdraw | ||
Channel storage channel = channels[publisher][provider][podId]; | ||
if (msg.sender != publisher) { | ||
if (channel.authorized[msg.sender] == false) revert NotAuthorized(); | ||
} | ||
if (channel.unlockedAt == 0 || block.timestamp < channel.unlockedAt) revert ChannelLocked(); | ||
|
||
uint256 leftoverFunds = channel.investedByPublisher - channel.withdrawnByProvider; | ||
|
@@ -98,13 +105,17 @@ contract Payment { | |
|
||
emit Unlocked(publisher, provider, podId, leftoverFunds); | ||
|
||
token.safeTransfer(publisher, leftoverFunds); | ||
token.safeTransfer(msg.sender, leftoverFunds); | ||
} | ||
|
||
// withdrawUnlockedFunds and destroy all previous traces of the channel's existence | ||
function closeChannel(address provider, bytes32 podId) public { | ||
address publisher = msg.sender; | ||
function closeChannel(address publisher, address provider, bytes32 podId) public { | ||
// check if the caller is authorized to close the channel | ||
Channel storage channel = channels[publisher][provider][podId]; | ||
if (channel.investedByPublisher == 0) revert DoesNotExist(); | ||
if (msg.sender != publisher) { | ||
if (channel.authorized[msg.sender] == false) revert NotAuthorized(); | ||
} | ||
if (channel.unlockedAt == 0 || block.timestamp < channel.unlockedAt) revert ChannelLocked(); | ||
|
||
uint256 leftoverFunds = channel.investedByPublisher - channel.withdrawnByProvider; | ||
|
@@ -113,7 +124,7 @@ contract Payment { | |
if (leftoverFunds != 0) emit Unlocked(publisher, provider, podId, leftoverFunds); | ||
emit ChannelClosed(publisher, provider, podId); | ||
|
||
if (leftoverFunds != 0) token.safeTransfer(publisher, leftoverFunds); | ||
if (leftoverFunds != 0) token.safeTransfer(msg.sender, leftoverFunds); | ||
} | ||
|
||
// allows the provider to withdraw as many tokens as would be needed to reach totalWithdrawAmount since the opening of the channel | ||
|
@@ -159,4 +170,61 @@ contract Payment { | |
Channel storage channel = channels[publisher][provider][podId]; | ||
return channel.withdrawnByProvider; | ||
} | ||
|
||
// authorize other addresses to create subChannels | ||
function authorize(address _authorized, address provider, bytes32 podId) public { | ||
Channel storage channel = channels[msg.sender][provider][podId]; | ||
if (channel.investedByPublisher == 0) revert DoesNotExist(); | ||
channel.authorized[_authorized] = true; | ||
} | ||
|
||
function isAuthorized(address publisher, address provider, bytes32 podId, address _address) | ||
public | ||
view | ||
returns (bool) | ||
{ | ||
return channels[publisher][provider][podId].authorized[_address]; | ||
} | ||
|
||
// create a subChannel from a main channel | ||
function createSubChannel( | ||
address publisher, | ||
address provider, | ||
bytes32 podId, | ||
address newProvider, | ||
bytes32 newPodId, | ||
uint256 amount | ||
) public { | ||
Channel storage channel = channels[publisher][provider][podId]; | ||
// Ensure the channel exists | ||
if (channel.investedByPublisher == 0) revert DoesNotExist(); | ||
|
||
// Check if the caller is authorized | ||
if (!channel.authorized[msg.sender]) revert NotAuthorized(); | ||
|
||
// Ensure there is enough invested by the publisher | ||
if (channel.investedByPublisher < amount) revert InsufficientFunds(); | ||
|
||
// Ensure channel is unlocked | ||
if (channel.unlockedAt == 0 || block.timestamp < channel.unlockedAt) revert ChannelLocked(); | ||
|
||
// Deduct the amount from the main channel's funds | ||
channel.investedByPublisher -= amount; | ||
|
||
// Create the subChannel for the authorized caller | ||
Channel storage subChannel = channels[msg.sender][newProvider][newPodId]; | ||
if (subChannel.investedByPublisher != 0) revert AlreadyExists(); | ||
|
||
// authorize the main channel publisher to control the subchannel | ||
subChannel.authorized[publisher] = true; | ||
|
||
// Ensure There are enough LeftOver funds | ||
if (channel.investedByPublisher - channel.withdrawnByProvider < amount) revert InsufficientFunds(); | ||
|
||
// fund the new sub channel with the deducted amount from the main channel | ||
subChannel.investedByPublisher += amount; | ||
subChannel.unlockTime = channel.unlockTime; // Inherit unlock time from main channel | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm reading the code right, this lets the publisher instantly unlock any channel, by doing (in all below calls,
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct, Should be Fixed now. |
||
|
||
emit Deposited(msg.sender, newProvider, newPodId, amount); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Decreasing the amount invested by the publisher without requiring the channel to be unlocked first allows the publisher to drain the channel of any funds within it, by creating a subchannel with all the funds they can, then unlocking that subchannel, and withdrawing from it at a leisurely pace, without the provider able to submit a
withdraw()
call.Also, since the code never checks whether
channel.investedByPublisher - channel.withdrawnByProvider < amount
(see, e.g. line 121,leftoverFunds = ..
), it's possible to withdraw more than was initially invested into a channel, thus draining the whole contract of funds.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed as well