Skip to content

Commit

Permalink
chore: move Collector functionality to Escrow, now Escrow calls Payments
Browse files Browse the repository at this point in the history
  • Loading branch information
Maikol committed May 8, 2024
1 parent e0e93a0 commit 2b7cff5
Show file tree
Hide file tree
Showing 11 changed files with 303 additions and 280 deletions.
107 changes: 89 additions & 18 deletions packages/horizon/contracts/escrow/GraphEscrow.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;

import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol";

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 --
Expand All @@ -17,9 +16,16 @@ contract GraphEscrow is IGraphEscrow, GraphEscrowStorageV1Storage, GraphDirector
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(
Expand All @@ -32,25 +38,68 @@ contract GraphEscrow is IGraphEscrow, GraphEscrowStorageV1Storage, GraphDirector
event Withdraw(address indexed sender, address indexed receiver, uint256 amount);
event Collect(address indexed sender, address indexed receiver, uint256 amount);

// -- Modifier --
// -- Constructor --

modifier onlyGraphPayments() {
if (msg.sender != address(graphPayments)) {
revert GraphEscrowNotGraphPayments();
constructor(
address _controller,
uint256 _revokeCollectorThawingPeriod,
uint256 _withdrawEscrowThawingPeriod
) GraphDirectory(_controller) {
if (_revokeCollectorThawingPeriod > MAX_THAWING_PERIOD) {
revert GraphEscrowThawingPeriodTooLong(_revokeCollectorThawingPeriod, MAX_THAWING_PERIOD);
}
_;
}

// -- Constructor --
if (_withdrawEscrowThawingPeriod > MAX_THAWING_PERIOD) {
revert GraphEscrowThawingPeriodTooLong(_withdrawEscrowThawingPeriod, MAX_THAWING_PERIOD);
}

constructor(address _controller, uint256 _withdrawEscrowThawingPeriod) GraphDirectory(_controller) {
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;
graphToken.transferFrom(msg.sender, address(this), amount);
TokenUtils.pullTokens(graphToken, msg.sender, amount);
emit Deposit(msg.sender, receiver, amount);
}

Expand All @@ -70,7 +119,7 @@ contract GraphEscrow is IGraphEscrow, GraphEscrowStorageV1Storage, GraphDirector
emit Deposit(msg.sender, receiver, amount);
}

graphToken.transferFrom(msg.sender, address(this), totalAmount);
TokenUtils.pullTokens(graphToken, msg.sender, totalAmount);
}

// Requests to thaw a specific amount of escrow from a receiver's escrow account
Expand Down Expand Up @@ -122,20 +171,42 @@ contract GraphEscrow is IGraphEscrow, GraphEscrowStorageV1Storage, GraphDirector
account.balance -= amount; // Reduce the balance by the withdrawn amount (no underflow risk)
account.amountThawing = 0;
account.thawEndTimestamp = 0;
graphToken.transfer(msg.sender, amount);
TokenUtils.pushTokens(graphToken, msg.sender, amount);
emit Withdraw(msg.sender, receiver, amount);
}

// Collect from escrow (up to amount available in escrow) for a receiver using sender's deposit
function collect(address sender, address receiver, uint256 amount) external onlyGraphPayments {
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 available = account.balance - account.amountThawing;

// TODO: should we revert if not enough funds are available?
uint256 collectAmount = amount > available ? available : amount;

account.balance -= collectAmount;
graphToken.transfer(msg.sender, collectAmount);
emit Collect(sender, receiver, collectAmount);

// Approve tokens so GraphPayments can pull them
graphToken.approve(address(graphPayments), collectAmount);
graphPayments.collect(receiver, dataService, collectAmount, paymentType, tokensDataService);
}
}
7 changes: 7 additions & 0 deletions packages/horizon/contracts/escrow/GraphEscrowStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ 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;
Expand All @@ -12,6 +16,9 @@ contract GraphEscrowStorageV1Storage {
// 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;
}
18 changes: 17 additions & 1 deletion packages/horizon/contracts/interfaces/IGraphEscrow.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
// 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;

Expand All @@ -21,5 +30,12 @@ interface IGraphEscrow {
function withdraw(address receiver) external;

// Collect from escrow (up to amount available in escrow) for a receiver using sender's deposit
function collect(address sender, address receiver, uint256 amount) external;
function collect(
address sender,
address receiver,
address dataService,
uint256 amount,
IGraphPayments.PaymentType paymentType,
uint256 tokensDataService
) external;
}
23 changes: 2 additions & 21 deletions packages/horizon/contracts/interfaces/IGraphPayments.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,12 @@ interface IGraphPayments {
QueryFees
}

// Collector
struct Collector {
bool authorized;
uint256 amount;
uint256 thawEndTimestamp;
}

// approve a data service to collect funds
function approveCollector(address dataService, uint256 amount) external;

// thaw a data service's collector authorization
function thawCollector(address dataService) external;

// cancel thawing a data service's collector authorization
function cancelThawCollector(address dataService) external;

// revoke authorized collector
function revokeCollector(address dataService) external;

// collect funds from a sender, pay cuts and forward the rest to the receiver
function collect(
address sender,
address receiver,
address dataService,
uint256 amount,
PaymentType paymentType,
uint256 dataServiceCut
uint256 tokensDataService
) external;
}
87 changes: 10 additions & 77 deletions packages/horizon/contracts/payments/GraphPayments.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;

import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol";
import { IHorizonStaking } from "@graphprotocol/contracts/contracts/staking/IHorizonStaking.sol";

import { IGraphPayments } from "../interfaces/IGraphPayments.sol";
import { IGraphEscrow } from "../interfaces/IGraphEscrow.sol";
import { GraphDirectory } from "../GraphDirectory.sol";
import { GraphPaymentsStorageV1Storage } from "./GraphPaymentsStorage.sol";
import { TokenUtils } from "../utils/TokenUtils.sol";

contract GraphPayments is IGraphPayments, GraphPaymentsStorageV1Storage, GraphDirectory {
// -- Errors --
Expand All @@ -19,11 +16,6 @@ contract GraphPayments is IGraphPayments, GraphPaymentsStorageV1Storage, GraphDi

// -- 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);

// -- Modifier --

// -- Parameters --
Expand All @@ -32,93 +24,34 @@ contract GraphPayments is IGraphPayments, GraphPaymentsStorageV1Storage, GraphDi

// -- Constructor --

constructor(
address _controller,
uint256 _revokeCollectorThawingPeriod,
uint256 _protocolPaymentCut
) GraphDirectory(_controller) {
revokeCollectorThawingPeriod = _revokeCollectorThawingPeriod;
constructor(address _controller, uint256 _protocolPaymentCut) GraphDirectory(_controller) {
protocolPaymentCut = _protocolPaymentCut;
}

// 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 GraphPaymentsNotThawing();
}

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 GraphPaymentsNotThawing();
}

if (collector.thawEndTimestamp > block.timestamp) {
revert GraphPaymentsStillThawing(block.timestamp, collector.thawEndTimestamp);
}

delete authorizedCollectors[msg.sender][dataService];
emit RevokeCollector(msg.sender, dataService);
}

// collect funds from a sender, pay cuts and forward the rest to the receiver
function collect(
address sender,
address receiver, // serviceProvider
address dataService,
uint256 amount,
IGraphPayments.PaymentType paymentType,
uint256 dataServiceCut
uint256 tokensDataService
) external {
Collector storage collector = authorizedCollectors[sender][msg.sender];

if (!collector.authorized) {
revert GraphPaymentsCollectorNotAuthorized(sender, msg.sender);
}

if (collector.amount < amount) {
revert GraphPaymentsCollectorInsufficientAmount(collector.amount, amount);
}

// Reduce amount from approved collector
collector.amount -= amount;

// Collect tokens from GraphEscrow
graphEscrow.collect(sender, receiver, amount);
TokenUtils.pullTokens(graphToken, msg.sender, amount);

// Pay protocol cut
uint256 protocolCut = (amount * protocolPaymentCut) / MAX_PPM;
graphToken.burn(protocolCut);
uint256 tokensProtocol = (amount * protocolPaymentCut) / MAX_PPM;
TokenUtils.burnTokens(graphToken, tokensProtocol);

// Pay data service cut
uint256 dataServicePayment = (amount * dataServiceCut) / MAX_PPM;
graphToken.transfer(msg.sender, dataServicePayment);
TokenUtils.pushTokens(graphToken, dataService, tokensDataService);

// Get delegation cut
uint256 delegatorCut = graphStaking.getDelegationCut(receiver, uint8(paymentType));
uint256 delegatorPayment = (amount * delegatorCut) / MAX_PPM;
graphStaking.addToDelegationPool(receiver, delegatorPayment);

// Pay the rest to the receiver
uint256 receiverPayment = amount - protocolCut - dataServicePayment - delegatorPayment;
graphToken.transfer(receiver, receiverPayment);
uint256 receiverPayment = amount - tokensProtocol - tokensDataService - delegatorPayment;
TokenUtils.pushTokens(graphToken, receiver, receiverPayment);
}
}
Loading

0 comments on commit 2b7cff5

Please sign in to comment.