Skip to content

Commit

Permalink
partial: indexing payments [DO NOT MERGE]
Browse files Browse the repository at this point in the history
Adds Authorizable and IPCollector.
  • Loading branch information
matiasedgeandnode committed Jan 10, 2025
1 parent 1038cf4 commit 423a1c8
Show file tree
Hide file tree
Showing 8 changed files with 1,113 additions and 1 deletion.
155 changes: 155 additions & 0 deletions packages/horizon/contracts/interfaces/IAuthorizable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;

/**
* @title Interface for the {Authorizable} contract
* @notice Implements an authorization scheme that allows authorizers to
* authorize signers to sign on their behalf.
*/
interface IAuthorizable {
/**
* @notice Details for an authorizer-signer pair
* @dev Authorizations can be removed only after a thawing period
*/
struct Authorization {
// Resource owner
address authorizer;
// Timestamp at which thawing period ends (zero if not thawing)
uint256 thawEndTimestamp;
// Whether the signer authorization was revoked
bool revoked;
}

/**
* @notice Emitted when a signer is authorized to sign for a authorizer
* @param authorizer The address of the authorizer
* @param signer The address of the signer
*/
event SignerAuthorized(address indexed authorizer, address indexed signer);

/**
* @notice Emitted when a signer is thawed to be de-authorized
* @param authorizer The address of the authorizer thawing the signer
* @param signer The address of the signer to thaw
* @param thawEndTimestamp The timestamp at which the thawing period ends
*/
event SignerThawing(address indexed authorizer, address indexed signer, uint256 thawEndTimestamp);

/**
* @dev Emitted when the thawing of a signer is cancelled
* @param authorizer The address of the authorizer cancelling the thawing
* @param signer The address of the signer
* @param thawEndTimestamp The timestamp at which the thawing period ends
*/
event SignerThawCanceled(address indexed authorizer, address indexed signer, uint256 thawEndTimestamp);

/**
* @dev Emitted when a signer has been revoked
* @param authorizer The address of the authorizer revoking the signer
* @param signer The address of the signer
*/
event SignerRevoked(address indexed authorizer, address indexed signer);

/**
* Thrown when the signer is already authorized
* @param authorizer The address of the authorizer
* @param signer The address of the signer
* @param revoked The revoked status of the authorization
*/
error SignerAlreadyAuthorized(address authorizer, address signer, bool revoked);

/**
* Thrown when the attempting to modify a revoked signer
* @param signer The address of the signer
*/
error SignerAlreadyRevoked(address signer);

/**
* Thrown when the signer proof deadline is invalid
* @param proofDeadline The deadline for the proof provided
* @param currentTimestamp The current timestamp
*/
error InvalidSignerProofDeadline(uint256 proofDeadline, uint256 currentTimestamp);

/**
* Thrown when the signer proof is invalid
*/
error InvalidSignerProof();

/**
* Thrown when the signer is not authorized by the authorizer
* @param authorizer The address of the authorizer
* @param signer The address of the signer
*/
error SignerNotAuthorized(address authorizer, address signer);

/**
* Thrown when the signer is not thawing
* @param signer The address of the signer
*/
error SignerNotThawing(address signer);

/**
* Thrown when the signer is still thawing
* @param currentTimestamp The current timestamp
* @param thawEndTimestamp The timestamp at which the thawing period ends
*/
error SignerStillThawing(uint256 currentTimestamp, uint256 thawEndTimestamp);

/**
* @notice Authorize a signer to sign on behalf of the authorizer
* @dev Requirements:
* - `signer` must not be already authorized
* - `proofDeadline` must be greater than the current timestamp
* - `proof` must be a valid signature from the signer being authorized
*
* Emits a {SignerAuthorized} event
* @param signer The addres of the signer
* @param proofDeadline The deadline for the proof provided by the signer
* @param proof The proof provided by the signer to be authorized by the authorizer
* consists of (chain id, verifying contract address, domain, proof deadline, authorizer address)
*/
function authorizeSigner(address signer, uint256 proofDeadline, bytes calldata proof) external;

/**
* @notice Starts thawing a signer to be de-authorized
* @dev Thawing a signer signals that signatures from that signer will soon be deemed invalid.
* Once a signer is thawed, they should be viewed as revoked regardless of their revocation status.
* Requirements:
* - `signer` must be authorized by the authorizer calling this function
*
* Emits a {SignerThawing} event
* @param signer The address of the signer to thaw
*/
function thawSigner(address signer) external;

/**
* @notice Stops thawing a signer.
* @dev Requirements:
* - `signer` must be thawing and authorized by the function caller
*
* Emits a {SignerThawCanceled} event
* @param signer The address of the signer to cancel thawing
*/
function cancelThawSigner(address signer) external;

/**
* @notice Revokes a signer if thawed.
* @dev Requirements:
* - `signer` must be thawed and authorized by the function caller
*
* Emits a {SignerRevoked} event
* @param signer The address of the signer
*/
function revokeAuthorizedSigner(address signer) external;

/**
* @notice Returns the thawing period for revoking an authorization
*/
function getRevokeAuthorizationThawingPeriod() external view returns (uint256);

/**
* @notice Returns the authorization details for a signer
*/
function getAuthorization(address signer) external view returns (Authorization memory);
}
83 changes: 83 additions & 0 deletions packages/horizon/contracts/interfaces/IIPCollector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;

import { IPaymentsCollector } from "./IPaymentsCollector.sol";
import { IGraphPayments } from "./IGraphPayments.sol";
import { IAuthorizable } from "./IAuthorizable.sol";

/**
* @title Interface for the {IPCollector} contract
* @dev Implements the {IPaymentCollector} interface as defined by the Graph
* Horizon payments protocol.
* @notice Implements a payments collector contract that can be used to collect
* indexing agreement payments.
*/
interface IIPCollector is IAuthorizable, IPaymentsCollector {
/// @notice A struct representing a signed IAV
struct SignedIAV {
// The IAV
IndexingAgreementVoucher iav;
// Signature - 65 bytes: r (32 Bytes) || s (32 Bytes) || v (1 Byte)
bytes signature;
}

/// @notice The Indexing Agreement Voucher (IAV) struct
struct IndexingAgreementVoucher {
// The address of the payer the IAV was issued by
address payer;
// The address of the data service the IAV was issued to
address dataService;
// The address of the service provider the IAV was issued to
address serviceProvider;
// Arbitrary metadata to extend functionality if a data service requires it
bytes metadata;
}

/**
* @notice Emitted when an IAV is collected
* @param payer The address of the payer
* @param dataService The address of the data service
* @param serviceProvider The address of the service provider
* @param metadata Arbitrary metadata
* @param signature The signature of the IAV
*/
event IAVCollected(
address indexed payer,
address indexed dataService,
address indexed serviceProvider,
bytes metadata,
bytes signature
);

/**
* Thrown when the IAV signer is invalid
*/
error IPCollectorInvalidIAVSigner();

/**
* Thrown when the payment type is not IndexingFee
* @param paymentType The provided payment type
*/
error IPCollectorInvalidPaymentType(IGraphPayments.PaymentTypes paymentType);

/**
* Thrown when the caller is not the data service the IAV was issued to
* @param caller The address of the caller
* @param dataService The address of the data service
*/
error IPCollectorCallerNotDataService(address caller, address dataService);

/**
* @dev Computes the hash of a IndexingAgreementVoucher (IAV).
* @param iav The IAV for which to compute the hash.
* @return The hash of the IAV.
*/
function encodeIAV(IndexingAgreementVoucher calldata iav) external view returns (bytes32);

/**
* @dev Recovers the signer address of a signed IndexingAgreementVoucher (IAV).
* @param signedIAV The SignedIAV containing the IAV and its signature.
* @return The address of the signer.
*/
function recoverIAVSigner(SignedIAV calldata signedIAV) external view returns (address);
}
2 changes: 1 addition & 1 deletion packages/horizon/contracts/mocks/ControllerMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ contract ControllerMock is IController {
* @param id Contract id (keccak256 hash of contract name)
* @return Address of the proxy contract for the provided id
*/
function getContractProxy(bytes32 id) external view override returns (address) {
function getContractProxy(bytes32 id) external view virtual override returns (address) {
return _registry[id];
}

Expand Down
95 changes: 95 additions & 0 deletions packages/horizon/contracts/payments/collectors/IPCollector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;

import { Authorizable } from "../../utilities/Authorizable.sol";
import { GraphDirectory } from "../../utilities/GraphDirectory.sol";
import { IIPCollector } from "../../interfaces/IIPCollector.sol";
import { IGraphPayments } from "../../interfaces/IGraphPayments.sol";
import { PPMMath } from "../../libraries/PPMMath.sol";

import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

/**
* @title IPCollector contract
* @dev Implements the {IIPCollector} interface.
* @notice A payments collector contract that can be used to collect payments using an IAV (Indexing Agreement Voucher).
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
contract IPCollector is EIP712, GraphDirectory, Authorizable, IIPCollector {
using PPMMath for uint256;

/// @notice The EIP712 typehash for the IndexingAgreementVoucher struct
bytes32 private constant EIP712_IAV_TYPEHASH =
keccak256("IndexingAgreementVoucher(address dataService,address serviceProvider,bytes metadata)");

/**
* @notice Constructs a new instance of the IPCollector contract.
* @param _eip712Name The name of the EIP712 domain.
* @param _eip712Version The version of the EIP712 domain.
* @param _controller The address of the Graph controller.
* @param _revokeSignerThawingPeriod The duration (in seconds) in which a signer is thawing before they can be revoked.
*/
constructor(
string memory _eip712Name,
string memory _eip712Version,
address _controller,
uint256 _revokeSignerThawingPeriod
) EIP712(_eip712Name, _eip712Version) GraphDirectory(_controller) Authorizable(_revokeSignerThawingPeriod) {}

/**
* @notice Initiate a payment collection through the payments protocol.
* See {IGraphPayments.collect}.
* @dev Caller must be the data service the IAV was issued to.
* @dev The signer of the IAV must be authorized.
* @notice REVERT: This function may revert if ECDSA.recover fails, check ECDSA library for details.
*/
function collect(IGraphPayments.PaymentTypes _paymentType, bytes calldata _data) external view returns (uint256) {
require(_paymentType == IGraphPayments.PaymentTypes.IndexingFee, IPCollectorInvalidPaymentType(_paymentType));

(SignedIAV memory signedIAV, uint256 dataServiceCut) = abi.decode(_data, (SignedIAV, uint256));
require(
signedIAV.iav.dataService == msg.sender,
IPCollectorCallerNotDataService(msg.sender, signedIAV.iav.dataService)
);

address signer = _recoverIAVSigner(signedIAV);
address payer = signedIAV.iav.payer;
require(_isAuthorized(payer, signer), IPCollectorInvalidIAVSigner());

return _collect(signedIAV.iav, dataServiceCut);
}

function _collect(IndexingAgreementVoucher memory, uint256) private pure returns (uint256) {
revert("Not implemented");
}

/**
* @notice See {IIPCollector.recoverIAVSigner}
*/
function recoverIAVSigner(SignedIAV calldata _signedIAV) external view returns (address) {
return _recoverIAVSigner(_signedIAV);
}

function _recoverIAVSigner(SignedIAV memory _signedIAV) private view returns (address) {
bytes32 messageHash = _encodeIAV(_signedIAV.iav);
return ECDSA.recover(messageHash, _signedIAV.signature);
}

/**
* @notice See {IIPCollector.encodeIAV}
*/
function encodeIAV(IndexingAgreementVoucher calldata _iav) external view returns (bytes32) {
return _encodeIAV(_iav);
}

function _encodeIAV(IndexingAgreementVoucher memory _iav) private view returns (bytes32) {
return
_hashTypedDataV4(
keccak256(
abi.encode(EIP712_IAV_TYPEHASH, _iav.dataService, _iav.serviceProvider, keccak256(_iav.metadata))
)
);
}
}
Loading

0 comments on commit 423a1c8

Please sign in to comment.