@@ -4,17 +4,13 @@ pragma experimental ABIEncoderV2;
4
4
import "./Governed.sol " ;
5
5
import "./GraphToken.sol " ;
6
6
import "./Staking.sol " ;
7
- import "./bytes/BytesLib.sol " ;
8
- import "@openzeppelin/contracts/cryptography/ECDSA.sol " ;
9
7
10
8
11
9
/*
12
10
* @title DisputeManager
13
11
* @dev Provides a way to align the incentives of participants ensuring that query results are trustful.
14
12
*/
15
13
contract DisputeManager is Governed {
16
- using BytesLib for bytes ;
17
- using ECDSA for bytes32 ;
18
14
using SafeMath for uint256 ;
19
15
20
16
// Disputes contain info neccessary for the Arbitrator to verify and resolve
@@ -27,44 +23,29 @@ contract DisputeManager is Governed {
27
23
28
24
// -- Attestation --
29
25
30
- // Store IPFS hash as 32 byte hash and 2 byte hash function
31
- // Note: Not future proof against IPFS planned updates to support multihash, which would require a len field
32
- // Note: hashFunction - 0x1220 is 'Qm', or SHA256 with 32 byte length
33
- struct IpfsHash {
34
- bytes32 hash;
35
- uint16 hashFunction;
36
- }
37
-
38
- // Signed message sent from IndexNode in response to a request
39
- // Note: Message is located at the given IPFS content address
26
+ // Attestation sent from IndexNode in response to a request
40
27
struct Attestation {
41
- // Content Identifier for request message sent from user to indexing node
42
- IpfsHash requestCID;
43
- // Content Identifier for signed response message from indexing node
44
- IpfsHash responseCID;
45
- // Amount of computational account units (gas) used to process query
46
- uint256 gasUsed;
47
- // Amount of data sent in the response
48
- uint256 responseNumBytes;
49
- // ECDSA vrs signature (using secp256k1)
28
+ bytes32 requestCID;
29
+ bytes32 responseCID;
30
+ bytes32 subgraphID;
50
31
uint8 v;
51
32
bytes32 r;
52
33
bytes32 s;
53
34
}
54
35
55
- uint256 private constant ATTESTATION_SIZE_BYTES = 192 ;
56
- uint256 private constant SIGNATURE_SIZE_BYTES = 65 ;
36
+ uint256 private constant ATTESTATION_SIZE_BYTES = 161 ;
37
+ uint256 private constant RECEIPT_SIZE_BYTES = 96 ;
57
38
58
39
// -- EIP-712 --
59
40
60
41
bytes32 private constant DOMAIN_TYPE_HASH = keccak256 (
61
- "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract) "
42
+ "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt ) "
62
43
);
63
44
bytes32 private constant DOMAIN_NAME_HASH = keccak256 ("Graph Protocol " );
64
- bytes32 private constant DOMAIN_VERSION_HASH = keccak256 ("0.1 " );
45
+ bytes32 private constant DOMAIN_VERSION_HASH = keccak256 ("0 " );
65
46
bytes32 private constant DOMAIN_SALT = 0xa070ffb1cd7409649bf77822cce74495468e06dbfaef09556838bf188679b9c2 ;
66
- bytes32 private constant ATTESTATION_TYPE_HASH = keccak256 (
67
- "Attestation(IpfsHash requestCID,IpfsHash responseCID,uint256 gasUsed,uint256 responseNumBytes)IpfsHash( bytes32 hash,uint16 hashFunction ) "
47
+ bytes32 private constant RECEIPT_TYPE_HASH = keccak256 (
48
+ "Receipt(bytes32 requestCID,bytes32 responseCID,bytes32 subgraphID ) "
68
49
);
69
50
70
51
// 100% in parts per million
@@ -103,6 +84,10 @@ contract DisputeManager is Governed {
103
84
104
85
// -- Events --
105
86
87
+ /**
88
+ * @dev Emitted when `disputeID` is created for `subgraphID` and `indexNode` by `fisherman`.
89
+ * The event emits the amount `tokens` deposited by the fisherman and `attestation` submitted.
90
+ */
106
91
event DisputeCreated (
107
92
bytes32 disputeID ,
108
93
bytes32 indexed subgraphID ,
@@ -112,6 +97,11 @@ contract DisputeManager is Governed {
112
97
bytes attestation
113
98
);
114
99
100
+ /**
101
+ * @dev Emitted when arbitrator accepts a `disputeID` for `subgraphID` and `indexNode`
102
+ * created by `fisherman`.
103
+ * The event emits the amount `tokens` transferred to the fisherman, the deposit plus reward.
104
+ */
115
105
event DisputeAccepted (
116
106
bytes32 disputeID ,
117
107
bytes32 indexed subgraphID ,
@@ -120,6 +110,11 @@ contract DisputeManager is Governed {
120
110
uint256 tokens
121
111
);
122
112
113
+ /**
114
+ * @dev Emitted when arbitrator rejects a `disputeID` for `subgraphID` and `indexNode`
115
+ * created by `fisherman`.
116
+ * The event emits the amount `tokens` burned from the fisherman deposit.
117
+ */
123
118
event DisputeRejected (
124
119
bytes32 disputeID ,
125
120
bytes32 indexed subgraphID ,
@@ -128,6 +123,11 @@ contract DisputeManager is Governed {
128
123
uint256 tokens
129
124
);
130
125
126
+ /**
127
+ * @dev Emitted when arbitrator draw a `disputeID` for `subgraphID` and `indexNode`
128
+ * created by `fisherman`.
129
+ * The event emits the amount `tokens` used as deposit and returned to the fisherman.
130
+ */
131
131
event DisputeIgnored (
132
132
bytes32 disputeID ,
133
133
bytes32 indexed subgraphID ,
@@ -211,20 +211,21 @@ contract DisputeManager is Governed {
211
211
}
212
212
213
213
/**
214
- * @dev Get the hash of encoded message to use as disputeID
215
- * @notice Return the disputeID for a particular attestation
216
- * @param _attestation Signed Attestation message
217
- * @return Hash of encoded message used as disputeID
214
+ * @dev Get the message hash that an indexer used to sign the receipt.
215
+ * Encodes a receipt using a domain separator, as described on
216
+ * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#specification.
217
+ * @notice Return the message hash used to sign the receipt
218
+ * @param _receipt Receipt returned by indexer and submitted by fisherman
219
+ * @return Message hash used to sign the receipt
218
220
*/
219
- function getDisputeID (bytes memory _attestation ) public view returns (bytes32 ) {
220
- // TODO: add a nonce?
221
+ function encodeHashReceipt (bytes memory _receipt ) public view returns (bytes32 ) {
221
222
return
222
223
keccak256 (
223
224
abi.encodePacked (
224
225
"\x19\x01 " , // EIP-191 encoding pad, EIP-712 version 1
225
226
DOMAIN_SEPARATOR,
226
227
keccak256 (
227
- abi.encode (ATTESTATION_TYPE_HASH, _attestation ) // EIP 712-encoded message hash
228
+ abi.encode (RECEIPT_TYPE_HASH, _receipt ) // EIP 712-encoded message hash
228
229
)
229
230
)
230
231
);
@@ -281,7 +282,7 @@ contract DisputeManager is Governed {
281
282
/**
282
283
* @dev Accept tokens
283
284
* @notice Receive Graph tokens
284
- * @param _from Token holder's address
285
+ * @param _from Token sender address
285
286
* @param _value Amount of Graph Tokens
286
287
* @param _data Extra data payload
287
288
*/
@@ -292,18 +293,8 @@ contract DisputeManager is Governed {
292
293
// Make sure the token is the caller of this function
293
294
require (msg .sender == address (token), "Caller is not the GRT token contract " );
294
295
295
- // Decode subgraphID
296
- bytes32 subgraphID = _data.slice (0 , 32 ).toBytes32 (0 );
297
-
298
- // Decode attestation
299
- bytes memory attestation = _data.slice (32 , ATTESTATION_SIZE_BYTES);
300
- require (attestation.length == ATTESTATION_SIZE_BYTES, "Signature must be 192 bytes long " );
301
-
302
- // Decode attestation signature
303
- bytes memory sig = _data.slice (32 + ATTESTATION_SIZE_BYTES, SIGNATURE_SIZE_BYTES);
304
- require (sig.length == SIGNATURE_SIZE_BYTES, "Signature must be 65 bytes long " );
305
-
306
- _createDispute (attestation, sig, subgraphID, _from, _value);
296
+ // Create a dispute using the received attestation
297
+ _createDispute (_data, _from, _value);
307
298
308
299
return true ;
309
300
}
@@ -397,25 +388,29 @@ contract DisputeManager is Governed {
397
388
398
389
/**
399
390
* @dev Create a dispute for the arbitrator to resolve
400
- * @param _attestation Attestation message
401
- * @param _sig Attestation signature
402
- * @param _subgraphID subgraphID for the attestation message
391
+ * @param _attestationData Attestation bytes submitted by the fisherman
403
392
* @param _fisherman Creator of dispute
404
393
* @param _deposit Amount of tokens staked as deposit
405
394
*/
406
- function _createDispute (
407
- bytes memory _attestation ,
408
- bytes memory _sig ,
409
- bytes32 _subgraphID ,
410
- address _fisherman ,
411
- uint256 _deposit
412
- ) private {
413
- // Obtain the hash of the fully-encoded message, per EIP-712 encoding
414
- bytes32 disputeID = getDisputeID (_attestation);
395
+ function _createDispute (bytes memory _attestationData , address _fisherman , uint256 _deposit ) private {
396
+ // Check attestation data length
397
+ require (_attestationData.length == ATTESTATION_SIZE_BYTES, "Attestation must be 161 bytes long " );
415
398
416
- // Obtain the signer of the fully-encoded EIP-712 message hash
417
- // Note: The signer of the attestation is the indexNode that served it
418
- address indexNode = disputeID.recover (_sig);
399
+ // Decode attestation
400
+ Attestation memory attestation = _parseAttestation (_attestationData);
401
+
402
+ // Get attestation signer
403
+ address indexNode = _recoverAttestationSigner (attestation);
404
+
405
+ // Create a disputeID
406
+ bytes32 disputeID = keccak256 (
407
+ abi.encodePacked (
408
+ attestation.requestCID,
409
+ attestation.responseCID,
410
+ attestation.subgraphID,
411
+ indexNode
412
+ )
413
+ );
419
414
420
415
// This also validates that index node exists
421
416
require (staking.hasStake (indexNode), "Dispute has no stake by the index node " );
@@ -427,9 +422,35 @@ contract DisputeManager is Governed {
427
422
require (! isDisputeCreated (disputeID), "Dispute already created " ); // Must be empty
428
423
429
424
// Store dispute
430
- disputes[disputeID] = Dispute (_subgraphID, indexNode, _fisherman, _deposit);
425
+ disputes[disputeID] = Dispute (attestation.subgraphID, indexNode, _fisherman, _deposit);
426
+
427
+ emit DisputeCreated (
428
+ disputeID,
429
+ attestation.subgraphID,
430
+ indexNode,
431
+ _fisherman,
432
+ _deposit,
433
+ _attestationData
434
+ );
435
+ }
431
436
432
- emit DisputeCreated (disputeID, _subgraphID, indexNode, _fisherman, _deposit, _attestation);
437
+ /**
438
+ * @dev Recover the signer address of the `_attestation`
439
+ * @param _attestation The attestation struct
440
+ * @return Signer address
441
+ */
442
+ function _recoverAttestationSigner (Attestation memory _attestation ) private view returns (address ) {
443
+ // Obtain the hash of the fully-encoded message, per EIP-712 encoding
444
+ bytes memory receipt = abi.encode (
445
+ _attestation.requestCID,
446
+ _attestation.responseCID,
447
+ _attestation.subgraphID
448
+ );
449
+ bytes32 messageHash = encodeHashReceipt (receipt);
450
+
451
+ // Obtain the signer of the fully-encoded EIP-712 message hash
452
+ // NOTE: The signer of the attestation is the indexer that served the request
453
+ return _recover (messageHash, _attestation.v, _attestation.r, _attestation.s);
433
454
}
434
455
435
456
/**
@@ -443,4 +464,83 @@ contract DisputeManager is Governed {
443
464
}
444
465
return id;
445
466
}
467
+
468
+ /**
469
+ * @dev Parse the bytes attestation into a struct from `_data`
470
+ * @return Attestation struct
471
+ */
472
+ function _parseAttestation (bytes memory _data ) private pure returns (Attestation memory ) {
473
+ // Decode receipt
474
+ (bytes32 requestCID , bytes32 responseCID , bytes32 subgraphID ) = abi.decode (
475
+ _data, (bytes32 , bytes32 , bytes32 )
476
+ );
477
+
478
+ // Decode signature
479
+ // Signature is expected to be in the order defined in the Attestation struct
480
+ uint8 v = _toUint8 (_data, RECEIPT_SIZE_BYTES);
481
+ bytes32 r = _toBytes32 (_data, RECEIPT_SIZE_BYTES + 1 );
482
+ bytes32 s = _toBytes32 (_data, RECEIPT_SIZE_BYTES + 33 );
483
+
484
+ return Attestation (requestCID, responseCID, subgraphID, v, r, s);
485
+ }
486
+
487
+ /**
488
+ * @dev Returns the address that signed a hashed message (`hash`) with
489
+ * signature `v`, `r', `s`. This address can then be used for verification purposes.
490
+ * @return The address recovered from the hash and signature.
491
+ */
492
+ function _recover (bytes32 _hash , uint8 _v , bytes32 _r , bytes32 _s ) private pure returns (address ) {
493
+ // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
494
+ // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
495
+ // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
496
+ // signatures from current libraries generate a unique signature with an s-value in the lower half order.
497
+ //
498
+ // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
499
+ // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
500
+ // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
501
+ // these malleable signatures as well.
502
+ if (uint256 (_s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 ) {
503
+ revert ("ECDSA: invalid signature 's' value " );
504
+ }
505
+
506
+ if (_v != 27 && _v != 28 ) {
507
+ revert ("ECDSA: invalid signature 'v' value " );
508
+ }
509
+
510
+ // If the signature is valid (and not malleable), return the signer address
511
+ address signer = ecrecover (_hash, _v, _r, _s);
512
+ require (signer != address (0 ), "ECDSA: invalid signature " );
513
+
514
+ return signer;
515
+ }
516
+
517
+ /**
518
+ * @dev Parse a uint8 from `_bytes` starting at offset `_start`
519
+ * @return uint8 value
520
+ */
521
+ function _toUint8 (bytes memory _bytes , uint256 _start ) internal pure returns (uint8 ) {
522
+ require (_bytes.length >= (_start + 1 ), "Bytes: out of bounds " );
523
+ uint8 tempUint;
524
+
525
+ assembly {
526
+ tempUint := mload (add (add (_bytes, 0x1 ), _start))
527
+ }
528
+
529
+ return tempUint;
530
+ }
531
+
532
+ /**
533
+ * @dev Parse a bytes32 from `_bytes` starting at offset `_start`
534
+ * @return bytes32 value
535
+ */
536
+ function _toBytes32 (bytes memory _bytes , uint256 _start ) internal pure returns (bytes32 ) {
537
+ require (_bytes.length >= (_start + 32 ), "Bytes: out of bounds " );
538
+ bytes32 tempBytes32;
539
+
540
+ assembly {
541
+ tempBytes32 := mload (add (add (_bytes, 0x20 ), _start))
542
+ }
543
+
544
+ return tempBytes32;
545
+ }
446
546
}
0 commit comments