-
Notifications
You must be signed in to change notification settings - Fork 147
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
partial: indexing payments [DO NOT MERGE]
Adds Authorizable and IPCollector.
- Loading branch information
1 parent
1038cf4
commit 423a1c8
Showing
8 changed files
with
1,113 additions
and
1 deletion.
There are no files selected for viewing
155 changes: 155 additions & 0 deletions
155
packages/horizon/contracts/interfaces/IAuthorizable.sol
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,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); | ||
} |
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,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); | ||
} |
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
95 changes: 95 additions & 0 deletions
95
packages/horizon/contracts/payments/collectors/IPCollector.sol
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,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)) | ||
) | ||
); | ||
} | ||
} |
Oops, something went wrong.