Skip to content

Commit 423a1c8

Browse files
partial: indexing payments [DO NOT MERGE]
Adds Authorizable and IPCollector.
1 parent 1038cf4 commit 423a1c8

File tree

8 files changed

+1113
-1
lines changed

8 files changed

+1113
-1
lines changed
+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
pragma solidity 0.8.27;
3+
4+
/**
5+
* @title Interface for the {Authorizable} contract
6+
* @notice Implements an authorization scheme that allows authorizers to
7+
* authorize signers to sign on their behalf.
8+
*/
9+
interface IAuthorizable {
10+
/**
11+
* @notice Details for an authorizer-signer pair
12+
* @dev Authorizations can be removed only after a thawing period
13+
*/
14+
struct Authorization {
15+
// Resource owner
16+
address authorizer;
17+
// Timestamp at which thawing period ends (zero if not thawing)
18+
uint256 thawEndTimestamp;
19+
// Whether the signer authorization was revoked
20+
bool revoked;
21+
}
22+
23+
/**
24+
* @notice Emitted when a signer is authorized to sign for a authorizer
25+
* @param authorizer The address of the authorizer
26+
* @param signer The address of the signer
27+
*/
28+
event SignerAuthorized(address indexed authorizer, address indexed signer);
29+
30+
/**
31+
* @notice Emitted when a signer is thawed to be de-authorized
32+
* @param authorizer The address of the authorizer thawing the signer
33+
* @param signer The address of the signer to thaw
34+
* @param thawEndTimestamp The timestamp at which the thawing period ends
35+
*/
36+
event SignerThawing(address indexed authorizer, address indexed signer, uint256 thawEndTimestamp);
37+
38+
/**
39+
* @dev Emitted when the thawing of a signer is cancelled
40+
* @param authorizer The address of the authorizer cancelling the thawing
41+
* @param signer The address of the signer
42+
* @param thawEndTimestamp The timestamp at which the thawing period ends
43+
*/
44+
event SignerThawCanceled(address indexed authorizer, address indexed signer, uint256 thawEndTimestamp);
45+
46+
/**
47+
* @dev Emitted when a signer has been revoked
48+
* @param authorizer The address of the authorizer revoking the signer
49+
* @param signer The address of the signer
50+
*/
51+
event SignerRevoked(address indexed authorizer, address indexed signer);
52+
53+
/**
54+
* Thrown when the signer is already authorized
55+
* @param authorizer The address of the authorizer
56+
* @param signer The address of the signer
57+
* @param revoked The revoked status of the authorization
58+
*/
59+
error SignerAlreadyAuthorized(address authorizer, address signer, bool revoked);
60+
61+
/**
62+
* Thrown when the attempting to modify a revoked signer
63+
* @param signer The address of the signer
64+
*/
65+
error SignerAlreadyRevoked(address signer);
66+
67+
/**
68+
* Thrown when the signer proof deadline is invalid
69+
* @param proofDeadline The deadline for the proof provided
70+
* @param currentTimestamp The current timestamp
71+
*/
72+
error InvalidSignerProofDeadline(uint256 proofDeadline, uint256 currentTimestamp);
73+
74+
/**
75+
* Thrown when the signer proof is invalid
76+
*/
77+
error InvalidSignerProof();
78+
79+
/**
80+
* Thrown when the signer is not authorized by the authorizer
81+
* @param authorizer The address of the authorizer
82+
* @param signer The address of the signer
83+
*/
84+
error SignerNotAuthorized(address authorizer, address signer);
85+
86+
/**
87+
* Thrown when the signer is not thawing
88+
* @param signer The address of the signer
89+
*/
90+
error SignerNotThawing(address signer);
91+
92+
/**
93+
* Thrown when the signer is still thawing
94+
* @param currentTimestamp The current timestamp
95+
* @param thawEndTimestamp The timestamp at which the thawing period ends
96+
*/
97+
error SignerStillThawing(uint256 currentTimestamp, uint256 thawEndTimestamp);
98+
99+
/**
100+
* @notice Authorize a signer to sign on behalf of the authorizer
101+
* @dev Requirements:
102+
* - `signer` must not be already authorized
103+
* - `proofDeadline` must be greater than the current timestamp
104+
* - `proof` must be a valid signature from the signer being authorized
105+
*
106+
* Emits a {SignerAuthorized} event
107+
* @param signer The addres of the signer
108+
* @param proofDeadline The deadline for the proof provided by the signer
109+
* @param proof The proof provided by the signer to be authorized by the authorizer
110+
* consists of (chain id, verifying contract address, domain, proof deadline, authorizer address)
111+
*/
112+
function authorizeSigner(address signer, uint256 proofDeadline, bytes calldata proof) external;
113+
114+
/**
115+
* @notice Starts thawing a signer to be de-authorized
116+
* @dev Thawing a signer signals that signatures from that signer will soon be deemed invalid.
117+
* Once a signer is thawed, they should be viewed as revoked regardless of their revocation status.
118+
* Requirements:
119+
* - `signer` must be authorized by the authorizer calling this function
120+
*
121+
* Emits a {SignerThawing} event
122+
* @param signer The address of the signer to thaw
123+
*/
124+
function thawSigner(address signer) external;
125+
126+
/**
127+
* @notice Stops thawing a signer.
128+
* @dev Requirements:
129+
* - `signer` must be thawing and authorized by the function caller
130+
*
131+
* Emits a {SignerThawCanceled} event
132+
* @param signer The address of the signer to cancel thawing
133+
*/
134+
function cancelThawSigner(address signer) external;
135+
136+
/**
137+
* @notice Revokes a signer if thawed.
138+
* @dev Requirements:
139+
* - `signer` must be thawed and authorized by the function caller
140+
*
141+
* Emits a {SignerRevoked} event
142+
* @param signer The address of the signer
143+
*/
144+
function revokeAuthorizedSigner(address signer) external;
145+
146+
/**
147+
* @notice Returns the thawing period for revoking an authorization
148+
*/
149+
function getRevokeAuthorizationThawingPeriod() external view returns (uint256);
150+
151+
/**
152+
* @notice Returns the authorization details for a signer
153+
*/
154+
function getAuthorization(address signer) external view returns (Authorization memory);
155+
}
+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
pragma solidity 0.8.27;
3+
4+
import { IPaymentsCollector } from "./IPaymentsCollector.sol";
5+
import { IGraphPayments } from "./IGraphPayments.sol";
6+
import { IAuthorizable } from "./IAuthorizable.sol";
7+
8+
/**
9+
* @title Interface for the {IPCollector} contract
10+
* @dev Implements the {IPaymentCollector} interface as defined by the Graph
11+
* Horizon payments protocol.
12+
* @notice Implements a payments collector contract that can be used to collect
13+
* indexing agreement payments.
14+
*/
15+
interface IIPCollector is IAuthorizable, IPaymentsCollector {
16+
/// @notice A struct representing a signed IAV
17+
struct SignedIAV {
18+
// The IAV
19+
IndexingAgreementVoucher iav;
20+
// Signature - 65 bytes: r (32 Bytes) || s (32 Bytes) || v (1 Byte)
21+
bytes signature;
22+
}
23+
24+
/// @notice The Indexing Agreement Voucher (IAV) struct
25+
struct IndexingAgreementVoucher {
26+
// The address of the payer the IAV was issued by
27+
address payer;
28+
// The address of the data service the IAV was issued to
29+
address dataService;
30+
// The address of the service provider the IAV was issued to
31+
address serviceProvider;
32+
// Arbitrary metadata to extend functionality if a data service requires it
33+
bytes metadata;
34+
}
35+
36+
/**
37+
* @notice Emitted when an IAV is collected
38+
* @param payer The address of the payer
39+
* @param dataService The address of the data service
40+
* @param serviceProvider The address of the service provider
41+
* @param metadata Arbitrary metadata
42+
* @param signature The signature of the IAV
43+
*/
44+
event IAVCollected(
45+
address indexed payer,
46+
address indexed dataService,
47+
address indexed serviceProvider,
48+
bytes metadata,
49+
bytes signature
50+
);
51+
52+
/**
53+
* Thrown when the IAV signer is invalid
54+
*/
55+
error IPCollectorInvalidIAVSigner();
56+
57+
/**
58+
* Thrown when the payment type is not IndexingFee
59+
* @param paymentType The provided payment type
60+
*/
61+
error IPCollectorInvalidPaymentType(IGraphPayments.PaymentTypes paymentType);
62+
63+
/**
64+
* Thrown when the caller is not the data service the IAV was issued to
65+
* @param caller The address of the caller
66+
* @param dataService The address of the data service
67+
*/
68+
error IPCollectorCallerNotDataService(address caller, address dataService);
69+
70+
/**
71+
* @dev Computes the hash of a IndexingAgreementVoucher (IAV).
72+
* @param iav The IAV for which to compute the hash.
73+
* @return The hash of the IAV.
74+
*/
75+
function encodeIAV(IndexingAgreementVoucher calldata iav) external view returns (bytes32);
76+
77+
/**
78+
* @dev Recovers the signer address of a signed IndexingAgreementVoucher (IAV).
79+
* @param signedIAV The SignedIAV containing the IAV and its signature.
80+
* @return The address of the signer.
81+
*/
82+
function recoverIAVSigner(SignedIAV calldata signedIAV) external view returns (address);
83+
}

Diff for: packages/horizon/contracts/mocks/ControllerMock.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ contract ControllerMock is IController {
103103
* @param id Contract id (keccak256 hash of contract name)
104104
* @return Address of the proxy contract for the provided id
105105
*/
106-
function getContractProxy(bytes32 id) external view override returns (address) {
106+
function getContractProxy(bytes32 id) external view virtual override returns (address) {
107107
return _registry[id];
108108
}
109109

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
pragma solidity 0.8.27;
3+
4+
import { Authorizable } from "../../utilities/Authorizable.sol";
5+
import { GraphDirectory } from "../../utilities/GraphDirectory.sol";
6+
import { IIPCollector } from "../../interfaces/IIPCollector.sol";
7+
import { IGraphPayments } from "../../interfaces/IGraphPayments.sol";
8+
import { PPMMath } from "../../libraries/PPMMath.sol";
9+
10+
import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
11+
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
12+
13+
/**
14+
* @title IPCollector contract
15+
* @dev Implements the {IIPCollector} interface.
16+
* @notice A payments collector contract that can be used to collect payments using an IAV (Indexing Agreement Voucher).
17+
* @custom:security-contact Please email [email protected] if you find any
18+
* bugs. We may have an active bug bounty program.
19+
*/
20+
contract IPCollector is EIP712, GraphDirectory, Authorizable, IIPCollector {
21+
using PPMMath for uint256;
22+
23+
/// @notice The EIP712 typehash for the IndexingAgreementVoucher struct
24+
bytes32 private constant EIP712_IAV_TYPEHASH =
25+
keccak256("IndexingAgreementVoucher(address dataService,address serviceProvider,bytes metadata)");
26+
27+
/**
28+
* @notice Constructs a new instance of the IPCollector contract.
29+
* @param _eip712Name The name of the EIP712 domain.
30+
* @param _eip712Version The version of the EIP712 domain.
31+
* @param _controller The address of the Graph controller.
32+
* @param _revokeSignerThawingPeriod The duration (in seconds) in which a signer is thawing before they can be revoked.
33+
*/
34+
constructor(
35+
string memory _eip712Name,
36+
string memory _eip712Version,
37+
address _controller,
38+
uint256 _revokeSignerThawingPeriod
39+
) EIP712(_eip712Name, _eip712Version) GraphDirectory(_controller) Authorizable(_revokeSignerThawingPeriod) {}
40+
41+
/**
42+
* @notice Initiate a payment collection through the payments protocol.
43+
* See {IGraphPayments.collect}.
44+
* @dev Caller must be the data service the IAV was issued to.
45+
* @dev The signer of the IAV must be authorized.
46+
* @notice REVERT: This function may revert if ECDSA.recover fails, check ECDSA library for details.
47+
*/
48+
function collect(IGraphPayments.PaymentTypes _paymentType, bytes calldata _data) external view returns (uint256) {
49+
require(_paymentType == IGraphPayments.PaymentTypes.IndexingFee, IPCollectorInvalidPaymentType(_paymentType));
50+
51+
(SignedIAV memory signedIAV, uint256 dataServiceCut) = abi.decode(_data, (SignedIAV, uint256));
52+
require(
53+
signedIAV.iav.dataService == msg.sender,
54+
IPCollectorCallerNotDataService(msg.sender, signedIAV.iav.dataService)
55+
);
56+
57+
address signer = _recoverIAVSigner(signedIAV);
58+
address payer = signedIAV.iav.payer;
59+
require(_isAuthorized(payer, signer), IPCollectorInvalidIAVSigner());
60+
61+
return _collect(signedIAV.iav, dataServiceCut);
62+
}
63+
64+
function _collect(IndexingAgreementVoucher memory, uint256) private pure returns (uint256) {
65+
revert("Not implemented");
66+
}
67+
68+
/**
69+
* @notice See {IIPCollector.recoverIAVSigner}
70+
*/
71+
function recoverIAVSigner(SignedIAV calldata _signedIAV) external view returns (address) {
72+
return _recoverIAVSigner(_signedIAV);
73+
}
74+
75+
function _recoverIAVSigner(SignedIAV memory _signedIAV) private view returns (address) {
76+
bytes32 messageHash = _encodeIAV(_signedIAV.iav);
77+
return ECDSA.recover(messageHash, _signedIAV.signature);
78+
}
79+
80+
/**
81+
* @notice See {IIPCollector.encodeIAV}
82+
*/
83+
function encodeIAV(IndexingAgreementVoucher calldata _iav) external view returns (bytes32) {
84+
return _encodeIAV(_iav);
85+
}
86+
87+
function _encodeIAV(IndexingAgreementVoucher memory _iav) private view returns (bytes32) {
88+
return
89+
_hashTypedDataV4(
90+
keccak256(
91+
abi.encode(EIP712_IAV_TYPEHASH, _iav.dataService, _iav.serviceProvider, keccak256(_iav.metadata))
92+
)
93+
);
94+
}
95+
}

0 commit comments

Comments
 (0)