-
Notifications
You must be signed in to change notification settings - Fork 62
/
MultipleArbitrableTokenTransaction.sol
414 lines (351 loc) · 19.3 KB
/
MultipleArbitrableTokenTransaction.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
/**
* @authors: [@n1c01a5, @hellwolf]
* @reviewers: [@ferittuncer, @unknownunknown1]
* @auditors: []
* @bounties: []
* @deployments: []
*/
/** @title Multiple Arbitrable ERC20 Token Transaction
* This is a contract for multiple arbitrated token transactions which can be reversed by an arbitrator.
* This can be used for buying goods, services and for paying freelancers.
* Parties are identified as "sender" and "receiver".
*/
pragma solidity ^0.4.24;
import "./Arbitrator.sol";
import "./IArbitrable.sol";
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
contract MultipleArbitrableTokenTransaction is IArbitrable {
// **************************** //
// * Contract variables * //
// **************************** //
uint8 constant AMOUNT_OF_CHOICES = 2;
enum Party {Sender, Receiver}
enum Status {NoDispute, WaitingSender, WaitingReceiver, DisputeCreated, Resolved}
enum RulingOptions {NoRuling, SenderWins, ReceiverWins}
struct Transaction {
address sender;
address receiver;
uint256 amount;
uint256 timeoutPayment; // Time in seconds after which the transaction can be automatically executed if not disputed.
uint disputeId; // If dispute exists, the ID of the dispute.
uint senderFee; // Total fees paid by the sender.
uint receiverFee; // Total fees paid by the receiver.
uint lastInteraction; // Last interaction for the dispute procedure.
Status status;
}
Transaction[] public transactions;
Arbitrator arbitrator; // Address of the arbitrator contract.
ERC20 token; // Address of the token contract.
bytes arbitratorExtraData; // Extra data to set up the arbitration.
uint feeTimeout; // Time in seconds a party can take to pay arbitration fees before being considered unresponding and lose the dispute.
mapping (uint => uint) public disputeIDtoTransactionID;
// **************************** //
// * Events * //
// **************************** //
/** @dev To be emitted when meta-evidence is submitted.
* @param _metaEvidenceID Unique identifier of meta-evidence. Should be the `transactionID`.
* @param _evidence A link to the meta-evidence JSON.
*/
event MetaEvidence(uint indexed _metaEvidenceID, string _evidence);
/** @dev To be emitted when a party pays or reimburses the other.
* @param _transactionID The index of the transaction.
* @param _amount The amount paid.
* @param _party The party that paid.
*/
event Payment(uint indexed _transactionID, uint _amount, address _party);
/** @dev Indicate that a party has to pay a fee or would otherwise be considered as losing.
* @param _transactionID The index of the transaction.
* @param _party The party who has to pay.
*/
event HasToPayFee(uint indexed _transactionID, Party _party);
/** @dev To be raised when evidence is submitted. Should point to the resource (evidences are not to be stored on chain due to gas considerations).
* @param _arbitrator The arbitrator of the contract.
* @param _evidenceGroupID Unique identifier of the group of evidence that relates the evidences to a dispute.
* @param _party The address of the party submitting the evidence. Note that 0 is kept for evidences not submitted by any party.
* @param _evidence A link to an evidence JSON that follows the ERC 1497 Evidence standard (https://github.com/ethereum/EIPs/issues/1497).
*/
event Evidence(Arbitrator indexed _arbitrator, uint indexed _evidenceGroupID, address indexed _party, string _evidence);
/** @dev To be emitted when a dispute is created to link the correct meta-evidence to the disputeID.
* @param _arbitrator The arbitrator of the contract.
* @param _disputeID ID of the dispute in the Arbitrator contract.
* @param _metaEvidenceID Unique identifier of meta-evidence. Should be the `transactionID`.
* @param _evidenceGroupID Unique identifier of the group of evidence that are linked to this dispute.
*/
event Dispute(Arbitrator indexed _arbitrator, uint indexed _disputeID, uint _metaEvidenceID, uint _evidenceGroupID);
/** @dev To be raised when a ruling is given.
* @param _arbitrator The arbitrator giving the ruling.
* @param _disputeID ID of the dispute in the Arbitrator contract.
* @param _ruling The ruling which was given.
*/
event Ruling(Arbitrator indexed _arbitrator, uint indexed _disputeID, uint _ruling);
// **************************** //
// * Arbitrable functions * //
// * Modifying the state * //
// **************************** //
/** @dev Constructor.
* @param _token The address of the transacted token.
* @param _arbitrator The arbitrator of the contract.
* @param _arbitratorExtraData Extra data for the arbitrator.
* @param _feeTimeout Arbitration fee timeout for the parties.
*/
constructor (
ERC20 _token,
Arbitrator _arbitrator,
bytes _arbitratorExtraData,
uint _feeTimeout
) public {
token = _token;
arbitrator = _arbitrator;
arbitratorExtraData = _arbitratorExtraData;
feeTimeout = _feeTimeout;
}
/** @dev Create a transaction. UNTRUSTED.
* @param _amount The amount of tokens in this transaction.
* @param _timeoutPayment Time after which a party automatically loses a dispute.
* @param _receiver The recipient of the transaction.
* @param _metaEvidence Link to the meta-evidence.
* @return The index of the transaction.
*/
function createTransaction(
uint _amount,
uint _timeoutPayment,
address _receiver,
string _metaEvidence
) public returns (uint transactionIndex) {
// Transfers token from sender wallet to contract.
require(token.transferFrom(msg.sender, address(this), _amount), "Sender does not have enough funds.");
transactions.push(Transaction({
sender: msg.sender,
receiver: _receiver,
amount: _amount,
timeoutPayment: _timeoutPayment,
disputeId: 0,
senderFee: 0,
receiverFee: 0,
lastInteraction: now,
status: Status.NoDispute
}));
emit MetaEvidence(transactions.length - 1, _metaEvidence);
return transactions.length - 1;
}
/** @dev Pay receiver. To be called if the good or service is provided. UNTRUSTED.
* @param _transactionID The index of the transaction.
* @param _amount Amount to pay in tokens.
*/
function pay(uint _transactionID, uint _amount) public {
Transaction storage transaction = transactions[_transactionID];
require(transaction.sender == msg.sender, "The caller must be the sender.");
require(transaction.status == Status.NoDispute, "The transaction shouldn't be disputed.");
require(_amount <= transaction.amount, "The amount paid has to be less or equal than the transaction.");
transaction.amount -= _amount;
require(token.transfer(transaction.receiver, _amount) != false, "The `transfer` function must not fail.");
emit Payment(_transactionID, _amount, msg.sender);
}
/** @dev Reimburse sender. To be called if the good or service can't be fully provided. UNTRUSTED.
* @param _transactionID The index of the transaction.
* @param _amountReimbursed Amount to reimburse in tokens.
*/
function reimburse(uint _transactionID, uint _amountReimbursed) public {
Transaction storage transaction = transactions[_transactionID];
require(transaction.receiver == msg.sender, "The caller must be the receiver.");
require(transaction.status == Status.NoDispute, "The transaction shouldn't be disputed.");
require(_amountReimbursed <= transaction.amount, "The amount reimbursed has to be less or equal than the transaction.");
transaction.amount -= _amountReimbursed;
require(token.transfer(transaction.sender, _amountReimbursed) != false, "The `transfer` function must not fail.");
emit Payment(_transactionID, _amountReimbursed, msg.sender);
}
/** @dev Transfer the transaction's amount to the receiver if the timeout has passed. UNTRUSTED.
* @param _transactionID The index of the transaction.
*/
function executeTransaction(uint _transactionID) public {
Transaction storage transaction = transactions[_transactionID];
require(now - transaction.lastInteraction >= transaction.timeoutPayment, "The timeout has not passed yet.");
require(transaction.status == Status.NoDispute, "The transaction shouldn't be disputed.");
uint amount = transaction.amount;
transaction.amount = 0;
transaction.status = Status.Resolved;
require(token.transfer(transaction.receiver, amount) != false, "The `transfer` function must not fail.");
}
/** @dev Reimburse sender if receiver fails to pay the fee. UNTRUSTED.
* @param _transactionID The index of the transaction.
*/
function timeOutBySender(uint _transactionID) public {
Transaction storage transaction = transactions[_transactionID];
require(transaction.status == Status.WaitingReceiver, "The transaction is not waiting on the receiver.");
require(now - transaction.lastInteraction >= feeTimeout, "Timeout time has not passed yet.");
executeRuling(_transactionID, uint(RulingOptions.SenderWins));
}
/** @dev Pay receiver if sender fails to pay the fee. UNTRUSTED.
* @param _transactionID The index of the transaction.
*/
function timeOutByReceiver(uint _transactionID) public {
Transaction storage transaction = transactions[_transactionID];
require(transaction.status == Status.WaitingSender, "The transaction is not waiting on the sender.");
require(now - transaction.lastInteraction >= feeTimeout, "Timeout time has not passed yet.");
executeRuling(_transactionID, uint(RulingOptions.ReceiverWins));
}
/** @dev Pay the arbitration fee to raise a dispute. To be called by the sender. UNTRUSTED.
* Note that the arbitrator can have `createDispute` throw, which will make this function throw and therefore lead to a party being timed-out.
* This is not a vulnerability as the arbitrator can rule in favor of one party anyway.
* @param _transactionID The index of the transaction.
*/
function payArbitrationFeeBySender(uint _transactionID) public payable {
Transaction storage transaction = transactions[_transactionID];
uint arbitrationCost = arbitrator.arbitrationCost(arbitratorExtraData);
require(transaction.status < Status.DisputeCreated, "Dispute has already been created.");
require(msg.sender == transaction.sender, "The caller must be the sender.");
transaction.senderFee += msg.value;
// Require that the total paid to be at least the arbitration cost.
require(transaction.senderFee >= arbitrationCost, "The sender fee must cover arbitration costs.");
transaction.lastInteraction = now;
// The receiver still has to pay. This can also happen if he has paid, but `arbitrationCost` has increased.
if (transaction.receiverFee < arbitrationCost) {
transaction.status = Status.WaitingReceiver;
emit HasToPayFee(_transactionID, Party.Receiver);
} else { // The receiver has also paid the fee. We create the dispute.
raiseDispute(_transactionID, arbitrationCost);
}
}
/** @dev Pay the arbitration fee to raise a dispute. To be called by the receiver. UNTRUSTED.
* Note that this function mirrors payArbitrationFeeBySender.
* @param _transactionID The index of the transaction.
*/
function payArbitrationFeeByReceiver(uint _transactionID) public payable {
Transaction storage transaction = transactions[_transactionID];
uint arbitrationCost = arbitrator.arbitrationCost(arbitratorExtraData);
require(transaction.status < Status.DisputeCreated, "Dispute has already been created.");
require(msg.sender == transaction.receiver, "The caller must be the receiver.");
transaction.receiverFee += msg.value;
// Require that the total paid to be at least the arbitration cost.
require(transaction.receiverFee >= arbitrationCost, "The receiver fee must cover arbitration costs.");
transaction.lastInteraction = now;
// The sender still has to pay. This can also happen if he has paid, but arbitrationCost has increased.
if (transaction.senderFee < arbitrationCost) {
transaction.status = Status.WaitingSender;
emit HasToPayFee(_transactionID, Party.Sender);
} else { // The sender has also paid the fee. We create the dispute.
raiseDispute(_transactionID, arbitrationCost);
}
}
/** @dev Create a dispute. UNTRUSTED.
* @param _transactionID The index of the transaction.
* @param _arbitrationCost Amount to pay the arbitrator.
*/
function raiseDispute(uint _transactionID, uint _arbitrationCost) internal {
Transaction storage transaction = transactions[_transactionID];
transaction.status = Status.DisputeCreated;
transaction.disputeId = arbitrator.createDispute.value(_arbitrationCost)(AMOUNT_OF_CHOICES, arbitratorExtraData);
disputeIDtoTransactionID[transaction.disputeId] = _transactionID;
emit Dispute(arbitrator, transaction.disputeId, _transactionID, _transactionID);
// Refund sender if it overpaid.
if (transaction.senderFee > _arbitrationCost) {
uint extraFeeSender = transaction.senderFee - _arbitrationCost;
transaction.senderFee = _arbitrationCost;
transaction.sender.send(extraFeeSender);
}
// Refund receiver if it overpaid.
if (transaction.receiverFee > _arbitrationCost) {
uint extraFeeReceiver = transaction.receiverFee - _arbitrationCost;
transaction.receiverFee = _arbitrationCost;
transaction.receiver.send(extraFeeReceiver);
}
}
/** @dev Submit a reference to evidence. EVENT.
* @param _transactionID The index of the transaction.
* @param _evidence A link to an evidence using its URI.
*/
function submitEvidence(uint _transactionID, string _evidence) public {
Transaction storage transaction = transactions[_transactionID];
require(
msg.sender == transaction.receiver || msg.sender == transaction.sender,
"The caller must be the receiver or the sender."
);
require(
transaction.status < Status.Resolved,
"Must not send evidence if the dispute is resolved."
);
emit Evidence(arbitrator, _transactionID, msg.sender, _evidence);
}
/** @dev Appeal an appealable ruling. UNTRUSTED.
* Transfer the funds to the arbitrator.
* Note that no checks are required as the checks are done by the arbitrator.
* @param _transactionID The index of the transaction.
*/
function appeal(uint _transactionID) public payable {
Transaction storage transaction = transactions[_transactionID];
arbitrator.appeal.value(msg.value)(transaction.disputeId, arbitratorExtraData);
}
/** @dev Give a ruling for a dispute. Must be called by the arbitrator.
* The purpose of this function is to ensure that the address calling it has the right to rule on the contract.
* @param _disputeID ID of the dispute in the Arbitrator contract.
* @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Not able/wanting to make a decision".
*/
function rule(uint _disputeID, uint _ruling) public {
uint transactionID = disputeIDtoTransactionID[_disputeID];
Transaction storage transaction = transactions[transactionID];
require(msg.sender == address(arbitrator), "The caller must be the arbitrator.");
require(transaction.status == Status.DisputeCreated, "The dispute has already been resolved.");
emit Ruling(Arbitrator(msg.sender), _disputeID, _ruling);
executeRuling(transactionID, _ruling);
}
/** @dev Execute a ruling of a dispute. It reimburses the fee to the winning party.
* @param _transactionID The index of the transaction.
* @param _ruling Ruling given by the arbitrator. 1: Reimburse the receiver. 2: Pay the sender.
*/
function executeRuling(uint _transactionID, uint _ruling) internal {
Transaction storage transaction = transactions[_transactionID];
require(_ruling <= AMOUNT_OF_CHOICES, "Invalid ruling.");
uint amount = transaction.amount;
uint senderFee = transaction.senderFee;
uint receiverFee = transaction.receiverFee;
transaction.amount = 0;
transaction.senderFee = 0;
transaction.receiverFee = 0;
transaction.status = Status.Resolved;
// Give the arbitration fee back.
// Note that we use `send` to prevent a party from blocking the execution.
if (_ruling == uint(RulingOptions.SenderWins)) {
transaction.sender.send(senderFee);
require(token.transfer(transaction.sender, amount) != false, "The `transfer` function must not fail.");
} else if (_ruling == uint(RulingOptions.ReceiverWins)) {
transaction.receiver.send(receiverFee);
require(token.transfer(transaction.receiver, amount) != false, "The `transfer` function must not fail.");
} else {
// `senderFee` and `receiverFee` are equal to the arbitration cost.
uint split_arbitration_fee = senderFee / 2;
transaction.receiver.send(split_arbitration_fee);
transaction.sender.send(split_arbitration_fee);
// In the case of an uneven token amount, one token can be burnt.
require(token.transfer(transaction.receiver, amount / 2) != false, "The `transfer` function must not fail.");
require(token.transfer(transaction.sender, amount / 2) != false, "The `transfer` function must not fail.");
}
}
// **************************** //
// * Constant getters * //
// **************************** //
/** @dev Getter to know the count of transactions.
* @return countTransactions The count of transactions.
*/
function getCountTransactions() public view returns (uint countTransactions) {
return transactions.length;
}
/** @dev Get IDs for transactions where the specified address is the receiver and/or the sender.
* This function must be used by the UI and not by other smart contracts.
* Note that the complexity is O(t), where t is amount of arbitrable transactions.
* @param _address The specified address.
* @return transactionIDs The transaction IDs.
*/
function getTransactionIDsByAddress(address _address) public view returns (uint[] transactionIDs) {
uint count = 0;
for (uint i = 0; i < transactions.length; i++) {
if (transactions[i].sender == _address || transactions[i].receiver == _address)
count++;
}
transactionIDs = new uint[](count);
count = 0;
for (uint j = 0; j < transactions.length; j++) {
if (transactions[j].sender == _address || transactions[j].receiver == _address)
transactionIDs[count++] = j;
}
}
}