-
Notifications
You must be signed in to change notification settings - Fork 146
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
[WIP] Horizon: add escrow and payments #968
Merged
Merged
Changes from 13 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
2cea50f
chore: add GraphEscrow
Maikol ac75c30
chore: add comment
Maikol cb1c50a
chore: add GraphPayments interface and data
Maikol f22adef
chore: implement Escrow deposit
Maikol 7f1431c
chore: add GraphEscrow implementation
Maikol 30bbbc6
chore: add GraphPayments implementation
Maikol 9b6ed9b
chore: use GraphDeployments on GraphEscrow
Maikol 76539f7
fix: change staking interface
Maikol 914f65f
chore: use addToDelegationPool
Maikol 575faec
chore: move files to folders
Maikol e0e93a0
chore: add unit tests
Maikol 2b7cff5
chore: move Collector functionality to Escrow, now Escrow calls Payments
Maikol 9ab9429
chore: revert when there is insufficient amount in deposit
Maikol 4bced01
Merge remote-tracking branch 'origin/horizon' into mde/horizon-escrow…
Maikol 16eabd8
fix: merge conflicts errors
Maikol c052943
chore: uint tests refactor
Maikol 65157fb
chore: merge horizon branch
Maikol File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
pragma solidity 0.8.24; | ||
|
||
import { IController } from "@graphprotocol/contracts/contracts/governance/IController.sol"; | ||
import { IHorizonStaking } from "@graphprotocol/contracts/contracts/staking/IHorizonStaking.sol"; | ||
import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol"; | ||
import { IGraphEscrow } from "./interfaces/IGraphEscrow.sol"; | ||
import { IGraphPayments } from "./interfaces/IGraphPayments.sol"; | ||
|
||
contract GraphDirectory { | ||
IController public immutable graphController; | ||
IHorizonStaking public immutable graphStaking; | ||
IGraphToken public immutable graphToken; | ||
IGraphEscrow public immutable graphEscrow; | ||
IGraphPayments public immutable graphPayments; | ||
|
||
constructor(address _controller) { | ||
graphController = IController(_controller); | ||
graphStaking = IHorizonStaking(graphController.getContractProxy(keccak256("Staking"))); | ||
graphToken = IGraphToken(graphController.getContractProxy(keccak256("GraphToken"))); | ||
graphEscrow = IGraphEscrow(graphController.getContractProxy(keccak256("GraphEscrow"))); | ||
graphPayments = IGraphPayments(graphController.getContractProxy(keccak256("GraphPayments"))); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.24; | ||
|
||
import { IGraphEscrow } from "../interfaces/IGraphEscrow.sol"; | ||
import { IGraphPayments } from "../interfaces/IGraphPayments.sol"; | ||
import { GraphDirectory } from "../GraphDirectory.sol"; | ||
import { GraphEscrowStorageV1Storage } from "./GraphEscrowStorage.sol"; | ||
import { TokenUtils } from "../utils/TokenUtils.sol"; | ||
|
||
contract GraphEscrow is IGraphEscrow, GraphEscrowStorageV1Storage, GraphDirectory { | ||
// -- Errors -- | ||
|
||
error GraphEscrowNotGraphPayments(); | ||
error GraphEscrowInputsLengthMismatch(); | ||
error GraphEscrowInsufficientThawAmount(); | ||
error GraphEscrowInsufficientAmount(uint256 available, uint256 required); | ||
error GraphEscrowNotThawing(); | ||
error GraphEscrowStillThawing(uint256 currentTimestamp, uint256 thawEndTimestamp); | ||
error GraphEscrowThawingPeriodTooLong(uint256 thawingPeriod, uint256 maxThawingPeriod); | ||
error GraphEscrowCollectorNotAuthorized(address sender, address dataService); | ||
error GraphEscrowCollectorInsufficientAmount(uint256 available, uint256 required); | ||
|
||
// -- Events -- | ||
|
||
event AuthorizedCollector(address indexed sender, address indexed dataService); | ||
event ThawCollector(address indexed sender, address indexed dataService); | ||
event CancelThawCollector(address indexed sender, address indexed dataService); | ||
event RevokeCollector(address indexed sender, address indexed dataService); | ||
event Deposit(address indexed sender, address indexed receiver, uint256 amount); | ||
event CancelThaw(address indexed sender, address indexed receiver); | ||
event Thaw( | ||
address indexed sender, | ||
address indexed receiver, | ||
uint256 amount, | ||
uint256 totalAmountThawing, | ||
uint256 thawEndTimestamp | ||
); | ||
event Withdraw(address indexed sender, address indexed receiver, uint256 amount); | ||
event Collect(address indexed sender, address indexed receiver, uint256 amount); | ||
|
||
// -- Constructor -- | ||
|
||
constructor( | ||
address _controller, | ||
uint256 _revokeCollectorThawingPeriod, | ||
uint256 _withdrawEscrowThawingPeriod | ||
) GraphDirectory(_controller) { | ||
if (_revokeCollectorThawingPeriod > MAX_THAWING_PERIOD) { | ||
revert GraphEscrowThawingPeriodTooLong(_revokeCollectorThawingPeriod, MAX_THAWING_PERIOD); | ||
} | ||
|
||
if (_withdrawEscrowThawingPeriod > MAX_THAWING_PERIOD) { | ||
revert GraphEscrowThawingPeriodTooLong(_withdrawEscrowThawingPeriod, MAX_THAWING_PERIOD); | ||
} | ||
|
||
revokeCollectorThawingPeriod = _revokeCollectorThawingPeriod; | ||
withdrawEscrowThawingPeriod = _withdrawEscrowThawingPeriod; | ||
} | ||
|
||
// approve a data service to collect funds | ||
function approveCollector(address dataService, uint256 amount) external { | ||
authorizedCollectors[msg.sender][dataService].authorized = true; | ||
authorizedCollectors[msg.sender][dataService].amount = amount; | ||
emit AuthorizedCollector(msg.sender, dataService); | ||
} | ||
|
||
// thaw a data service's collector authorization | ||
function thawCollector(address dataService) external { | ||
authorizedCollectors[msg.sender][dataService].thawEndTimestamp = block.timestamp + revokeCollectorThawingPeriod; | ||
emit ThawCollector(msg.sender, dataService); | ||
} | ||
|
||
// cancel thawing a data service's collector authorization | ||
function cancelThawCollector(address dataService) external { | ||
if (authorizedCollectors[msg.sender][dataService].thawEndTimestamp == 0) { | ||
revert GraphEscrowNotThawing(); | ||
} | ||
|
||
authorizedCollectors[msg.sender][dataService].thawEndTimestamp = 0; | ||
emit CancelThawCollector(msg.sender, dataService); | ||
} | ||
|
||
// revoke authorized collector | ||
function revokeCollector(address dataService) external { | ||
Collector storage collector = authorizedCollectors[msg.sender][dataService]; | ||
|
||
if (collector.thawEndTimestamp == 0) { | ||
revert GraphEscrowNotThawing(); | ||
} | ||
|
||
if (collector.thawEndTimestamp > block.timestamp) { | ||
revert GraphEscrowStillThawing(block.timestamp, collector.thawEndTimestamp); | ||
} | ||
|
||
delete authorizedCollectors[msg.sender][dataService]; | ||
emit RevokeCollector(msg.sender, dataService); | ||
} | ||
|
||
// Deposit funds into the escrow for a receiver | ||
function deposit(address receiver, uint256 amount) external { | ||
escrowAccounts[msg.sender][receiver].balance += amount; | ||
TokenUtils.pullTokens(graphToken, msg.sender, amount); | ||
emit Deposit(msg.sender, receiver, amount); | ||
} | ||
|
||
// Deposit funds into the escrow for multiple receivers | ||
function depositMany(address[] calldata receivers, uint256[] calldata amounts) external { | ||
if (receivers.length != amounts.length) { | ||
revert GraphEscrowInputsLengthMismatch(); | ||
} | ||
|
||
uint256 totalAmount = 0; | ||
for (uint256 i = 0; i < receivers.length; i++) { | ||
address receiver = receivers[i]; | ||
uint256 amount = amounts[i]; | ||
|
||
totalAmount += amount; | ||
escrowAccounts[msg.sender][receiver].balance += amount; | ||
emit Deposit(msg.sender, receiver, amount); | ||
} | ||
|
||
TokenUtils.pullTokens(graphToken, msg.sender, totalAmount); | ||
} | ||
|
||
// Requests to thaw a specific amount of escrow from a receiver's escrow account | ||
function thaw(address receiver, uint256 amount) external { | ||
EscrowAccount storage account = escrowAccounts[msg.sender][receiver]; | ||
if (amount == 0) { | ||
// if amount thawing is zero and requested amount is zero this is an invalid request. | ||
// otherwise if amount thawing is greater than zero and requested amount is zero this | ||
// is a cancel thaw request. | ||
if (account.amountThawing == 0) { | ||
revert GraphEscrowInsufficientThawAmount(); | ||
} | ||
account.amountThawing = 0; | ||
account.thawEndTimestamp = 0; | ||
emit CancelThaw(msg.sender, receiver); | ||
return; | ||
} | ||
|
||
// Check if the escrow balance is sufficient | ||
if (account.balance < amount) { | ||
revert GraphEscrowInsufficientAmount({ available: account.balance, required: amount }); | ||
} | ||
|
||
// Set amount to thaw | ||
account.amountThawing = amount; | ||
// Set when the thaw is complete (thawing period number of seconds after current timestamp) | ||
account.thawEndTimestamp = block.timestamp + withdrawEscrowThawingPeriod; | ||
|
||
emit Thaw(msg.sender, receiver, amount, account.amountThawing, account.thawEndTimestamp); | ||
} | ||
|
||
// Withdraws all thawed escrow from a receiver's escrow account | ||
function withdraw(address receiver) external { | ||
EscrowAccount storage account = escrowAccounts[msg.sender][receiver]; | ||
if (account.thawEndTimestamp == 0) { | ||
revert GraphEscrowNotThawing(); | ||
} | ||
|
||
if (account.thawEndTimestamp > block.timestamp) { | ||
revert GraphEscrowStillThawing({ | ||
currentTimestamp: block.timestamp, | ||
thawEndTimestamp: account.thawEndTimestamp | ||
}); | ||
} | ||
|
||
// Amount is the minimum between the amount being thawed and the actual balance | ||
uint256 amount = account.amountThawing > account.balance ? account.balance : account.amountThawing; | ||
|
||
account.balance -= amount; // Reduce the balance by the withdrawn amount (no underflow risk) | ||
account.amountThawing = 0; | ||
account.thawEndTimestamp = 0; | ||
TokenUtils.pushTokens(graphToken, msg.sender, amount); | ||
emit Withdraw(msg.sender, receiver, amount); | ||
} | ||
|
||
// Collect from escrow for a receiver using sender's deposit | ||
function collect( | ||
address sender, | ||
address receiver, // serviceProvider | ||
address dataService, | ||
uint256 amount, | ||
IGraphPayments.PaymentType paymentType, | ||
uint256 tokensDataService | ||
) external { | ||
// Check if collector is authorized and has enough funds | ||
Collector storage collector = authorizedCollectors[sender][msg.sender]; | ||
|
||
if (!collector.authorized) { | ||
revert GraphEscrowCollectorNotAuthorized(sender, msg.sender); | ||
} | ||
|
||
if (collector.amount < amount) { | ||
revert GraphEscrowCollectorInsufficientAmount(collector.amount, amount); | ||
} | ||
|
||
// Reduce amount from approved collector | ||
collector.amount -= amount; | ||
|
||
// Collect tokens from GraphEscrow up to amount available | ||
EscrowAccount storage account = escrowAccounts[sender][receiver]; | ||
uint256 availableAmount = account.balance - account.amountThawing; | ||
if (availableAmount < amount) { | ||
revert GraphEscrowInsufficientAmount(availableAmount, amount); | ||
} | ||
|
||
account.balance -= amount; | ||
emit Collect(sender, receiver, amount); | ||
|
||
// Approve tokens so GraphPayments can pull them | ||
graphToken.approve(address(graphPayments), amount); | ||
graphPayments.collect(receiver, dataService, amount, paymentType, tokensDataService); | ||
} | ||
|
||
// Get the balance of a sender-receiver pair | ||
function getBalance(address sender, address receiver) external view returns (uint256) { | ||
EscrowAccount storage account = escrowAccounts[sender][receiver]; | ||
return account.balance - account.amountThawing; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.24; | ||
|
||
import { IGraphEscrow } from "../interfaces/IGraphEscrow.sol"; | ||
|
||
contract GraphEscrowStorageV1Storage { | ||
// Authorized collectors | ||
mapping(address sender => mapping(address dataService => IGraphEscrow.Collector collector)) | ||
public authorizedCollectors; | ||
|
||
// Stores how much escrow each sender has deposited for each receiver, as well as thawing information | ||
mapping(address sender => mapping(address receiver => IGraphEscrow.EscrowAccount escrowAccount)) | ||
public escrowAccounts; | ||
|
||
// The maximum thawing period (in seconds) for both escrow withdrawal and signer revocation | ||
// This is a precautionary measure to avoid inadvertedly locking funds for too long | ||
uint256 public constant MAX_THAWING_PERIOD = 90 days; | ||
|
||
// Thawing period for authorized collectors | ||
uint256 public immutable revokeCollectorThawingPeriod; | ||
|
||
// The duration (in seconds) in which escrow funds are thawing before they can be withdrawn | ||
uint256 public immutable withdrawEscrowThawingPeriod; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.24; | ||
|
||
import { IGraphPayments } from "./IGraphPayments.sol"; | ||
|
||
interface IGraphEscrow { | ||
struct EscrowAccount { | ||
uint256 balance; // Total escrow balance for a sender-receiver pair | ||
uint256 amountThawing; // Amount of escrow currently being thawed | ||
uint256 thawEndTimestamp; // Timestamp at which thawing period ends (zero if not thawing) | ||
} | ||
|
||
// Collector | ||
struct Collector { | ||
bool authorized; | ||
uint256 amount; | ||
uint256 thawEndTimestamp; | ||
} | ||
|
||
// Deposit funds into the escrow for a receiver | ||
function deposit(address receiver, uint256 amount) external; | ||
|
||
// Deposit funds into the escrow for multiple receivers | ||
function depositMany(address[] calldata receivers, uint256[] calldata amounts) external; | ||
|
||
// Requests to thaw a specific amount of escrow from a receiver's escrow account | ||
function thaw(address receiver, uint256 amount) external; | ||
|
||
// Withdraws all thawed escrow from a receiver's escrow account | ||
function withdraw(address receiver) external; | ||
|
||
// Collect from escrow for a receiver using sender's deposit | ||
function collect( | ||
address sender, | ||
address receiver, | ||
address dataService, | ||
uint256 amount, | ||
IGraphPayments.PaymentType paymentType, | ||
uint256 tokensDataService | ||
) external; | ||
|
||
function getBalance(address sender, address receiver) external view returns (uint256); | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
A payer could bypass the thawing by re-approving a collector for a 0 amount.
I think we should not allow re approvals for amounts that are smaller than the current allowance.
If you want to reduce the allowance it should go through the thawing period.