-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathTopUpPullPaymentWithExpiration.sol
589 lines (544 loc) · 29.3 KB
/
TopUpPullPaymentWithExpiration.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
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
pragma solidity 0.5.10;
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import "../ownership/PayableOwnable.sol";
/// @dev - Top Up Billing model - Total Limits
/// A business that allows their customers to purchase various items or services using Credits.
/// -------------------------------------------------------------------------------------------
/// The business allow their customers to subscribe to a top up billing model. The top-up billing model works as follows:
/// 1. The customer can purchase 100 Credits from the business for 10$.
/// 2. The customer can start spending the 100 Credits for using different services or purchasing different items from the business.
/// 3. When the customer’s Credits drops at 25 units, the business is allowed to charge 7.50$ for 75 Credits, therefore ‘topping up’ to 100 Credits again.
/// -------------------------------------------------------------------------------------------
/// Total Limits
/// ------------
/// The customer specifies that the maximum amount that (s)he is willing to spend in total in this top up billing model is 100$.
/// This means that the business can trigger the top up payment and pull PMA from the customer account only up until 100$ in PMA.
/// Expiration Time
/// ----------------
/// The customer specifies that the top up payment is valid only until 31/12/2019 (expiration time).
/// The business is allowed to trigger the top up pull payments only until the expiration time i.e. 31/12/2019.
/// The customer can increase/decrease the top up total and update the expiration date.
contract TopUpPullPaymentWithExpiration is PayableOwnable {
using SafeMath for uint256;
/// ===============================================================================================================
/// Events
/// ===============================================================================================================
event LogExecutorAdded(address executor);
event LogExecutorRemoved(address executor);
event LogSmartContractActorFunded(string actorRole, address payable actor, uint256 timestamp);
event LogPaymentRegistered(
address customerAddress,
bytes32 paymentID,
bytes32 businessID
);
event LogPaymentCancelled(
address customerAddress,
bytes32 paymentID,
bytes32 businessID
);
event LogPullPaymentExecuted(
address customerAddress,
bytes32 paymentID,
bytes32 businessID,
uint256 amountInPMA,
uint256 conversionRate
);
event LogTotalLimitUpdated(
address customerAddress,
bytes32 paymentID,
uint256 oldLimit,
uint256 newLimit
);
event LogExpirationTimestampUpdated(
address customerAddress,
bytes32 paymentID,
uint256 oldExpirationTimestamp,
uint256 newExpirationTimestamp
);
/// ===============================================================================================================
/// Constants
/// ===============================================================================================================
uint256 constant internal RATE_CALCULATION_NUMBER = 10 ** 26; /// Check `calculatePMAFromFiat()` for more details
uint256 constant internal OVERFLOW_LIMITER_NUMBER = 10 ** 20; /// 1e^20 - Prevent numeric overflows
uint256 constant internal FIAT_TO_CENT_FIXER = 100; /// Fiat currencies have 100 cents in 1 basic monetary unit.
uint256 constant internal ONE_ETHER = 1 ether; /// PumaPay token has 18 decimals - same as one ETHER
uint256 constant internal FUNDING_AMOUNT = 0.5 ether; /// Amount to transfer to owner/executor
uint256 constant internal MINIMUM_AMOUNT_OF_ETH_FOR_OPERATORS = 0.15 ether; /// min amount of ETH for owner/executor
bytes32 constant internal EMPTY_BYTES32 = "";
/// ===============================================================================================================
/// Members
/// ===============================================================================================================
IERC20 public token;
mapping(address => bool) public executors;
mapping(bytes32 => TopUpPayment) public pullPayments;
struct TopUpPayment {
bytes32[2] paymentIDs; /// [0] paymentID / [1] businessID
string currency; /// 3-letter abbr i.e. 'EUR' / 'USD' etc.
address customerAddress; /// wallet address of customer
address treasuryAddress; /// address which pma tokens will be transfer to on execution
address executorAddress; /// address that can execute the pull payment
uint256 initialConversionRate; /// conversion rate for first payment execution
uint256 initialPaymentAmountInCents; /// initial payment amount in fiat in cents
uint256 topUpAmountInCents; /// payment amount in fiat in cents
uint256 startTimestamp; /// when subscription starts - in seconds
uint256 lastPaymentTimestamp; /// timestamp of last payment
uint256 cancelTimestamp; /// timestamp the payment was cancelled
uint256 expirationTimestamp; /// expiration timestamp of the payment
uint256 totalLimit; /// total limit that the customer is willing to pay
uint256 totalSpent; /// total amount spent by the customer
}
/// ===============================================================================================================
/// Modifiers
/// ===============================================================================================================
modifier isValidAddress(address _address) {
require(_address != address(0), "Invalid address - ZERO_ADDRESS provided.");
_;
}
modifier isValidString(string memory _string) {
require(bytes(_string).length > 0, "Invalid string - is empty.");
_;
}
modifier isValidNumber(uint256 _number) {
require(_number > 0, "Invalid number - Must be higher than zero.");
require(_number <= OVERFLOW_LIMITER_NUMBER, "Invalid number - Must be lower than the overflow limit.");
_;
}
modifier isValidByte32(bytes32 _text) {
require(_text != EMPTY_BYTES32, "Invalid byte32 value.");
_;
}
modifier isValidNewTotalLimit(bytes32 _paymentID, uint256 _newAmount) {
require(_newAmount >= pullPayments[_paymentID].totalSpent, "New total amount is less than the amount spent.");
_;
}
modifier isExecutor() {
require(executors[msg.sender], "msg.sender not an executor.");
_;
}
modifier executorExists(address _executor) {
require(executors[_executor], "Executor does not exists.");
_;
}
modifier executorDoesNotExists(address _executor) {
require(!executors[_executor], "Executor already exists.");
_;
}
modifier isPullPaymentExecutor(bytes32 _paymentID) {
require(pullPayments[_paymentID].executorAddress == msg.sender, "msg.sender not allowed to execute this payment.");
_;
}
modifier isCustomer(bytes32 _paymentID) {
require(pullPayments[_paymentID].customerAddress == msg.sender, "msg.sender not allowed to update this payment.");
_;
}
modifier paymentExists(bytes32 _paymentID) {
require(pullPayments[_paymentID].paymentIDs[0] != "", "Pull Payment does not exists.");
_;
}
modifier paymentDoesNotExist(bytes32 _paymentID) {
require(pullPayments[_paymentID].paymentIDs[0] == "", "Pull Payment exists already.");
_;
}
modifier paymentNotCancelled(bytes32 _paymentID) {
require(pullPayments[_paymentID].cancelTimestamp == 0, "Payment is cancelled");
_;
}
modifier isWithinTheTotalLimits(bytes32 _paymentID) {
require(pullPayments[_paymentID].totalSpent.add(pullPayments[_paymentID].topUpAmountInCents) <= pullPayments[_paymentID].totalLimit, "Total limit reached.");
_;
}
modifier isBeforeExpirationTimestamp(bytes32 _paymentID) {
require(pullPayments[_paymentID].expirationTimestamp > now, "Payment is expired.");
_;
}
modifier isValidExpirationTimestamp(uint256 _expirationTimestamp) {
require(_expirationTimestamp > now, "Expiration timestamp must be in the future.");
_;
}
/// ===============================================================================================================
/// Constructor
/// ===============================================================================================================
/// @dev Contract constructor - sets the token address that the contract facilitates.
/// @param _token Token Address.
constructor(address _token)
public {
require(_token != address(0), "Invalid address for token - ZERO_ADDRESS provided.");
token = IERC20(_token);
}
// @notice Will receive any eth sent to the contract
function() external payable {
}
/// ===============================================================================================================
/// Public Functions - Owner Only
/// ===============================================================================================================
/// @dev Adds a new executor. - can be executed only by the owner.
/// When adding a new executor 0.5 ETH is transferred to allow the executor to pay for gas.
/// The balance of the owner is also checked and if funding is needed 0.5 ETH is transferred.
/// @param _executor - address of the executor which cannot be zero address.
function addExecutor(address payable _executor)
public
onlyOwner
isValidAddress(_executor)
executorDoesNotExists(_executor)
{
executors[_executor] = true;
if (isFundingNeeded(_executor)) {
_executor.transfer(FUNDING_AMOUNT);
emit LogSmartContractActorFunded("executor", _executor, now);
}
if (isFundingNeeded(owner())) {
owner().transfer(FUNDING_AMOUNT);
emit LogSmartContractActorFunded("owner", owner(), now);
}
emit LogExecutorAdded(_executor);
}
/// @dev Removes a new executor. - can be executed only by the owner.
/// The balance of the owner is checked and if funding is needed 0.5 ETH is transferred.
/// @param _executor - address of the executor which cannot be zero address.
function removeExecutor(address payable _executor)
public
onlyOwner
isValidAddress(_executor)
executorExists(_executor)
{
executors[_executor] = false;
if (isFundingNeeded(owner())) {
owner().transfer(FUNDING_AMOUNT);
emit LogSmartContractActorFunded("owner", owner(), now);
}
emit LogExecutorRemoved(_executor);
}
/// ===============================================================================================================
/// Public Functions - Executors Only
/// ===============================================================================================================
/// @dev Registers a new top up pull payment to the PumaPay Top Up Pull Payment Contract - The registration can be executed only
/// by one of the executors of the PumaPay Pull Payment Contract and the
/// PumaPay Pull Payment Contract checks that the pull payment has been singed by the customer of the account.
/// The total limit and the expiration timestamp are set on registration and the total and time based amount spent are set to 0.
/// The initial payment amount for the top up payment is being executed on the registration of the pull payment.
/// On registration the initial payment is executed.
/// The balance of the executor (msg.sender) is checked and if funding is needed 0.5 ETH is transferred.
/// Emits 'LogPaymentRegistered' with customer address, pull payment executor address and paymentID.
/// Emits 'LogPullPaymentExecuted' with customer address, paymentID, businessID, amount in PMA and conversion rate.
/// @param v - recovery ID of the ETH signature. - https://github.com/ethereum/EIPs/issues/155
/// @param r - R output of ECDSA signature.
/// @param s - S output of ECDSA signature.
/// @param _paymentIDs - [0] paymentID, [1] businessID
/// @param _addresses - [0] customer, [1] pull payment executor, [2] treasury
/// @param _numbers - [0] initial conversion rate, [1] initial payment amount in cents, [2] top up amount in cents,
/// [3] start timestamp, [4] total limit, [5] expiration timestamp
/// @param _currency - currency of the payment / 3-letter abbr i.e. 'EUR'.
function registerTopUpPayment(
uint8 v,
bytes32 r,
bytes32 s,
bytes32[2] memory _paymentIDs,
address[3] memory _addresses,
uint256[6] memory _numbers,
string memory _currency
)
public
isExecutor()
paymentDoesNotExist(_paymentIDs[0])
isValidString(_currency)
isValidExpirationTimestamp(_numbers[5])
{
require(_paymentIDs[0] != EMPTY_BYTES32, "Invalid byte32 value.");
require(_paymentIDs[1] != EMPTY_BYTES32, "Invalid byte32 value.");
require(_addresses[0] != address(0), "Invalid address - ZERO_ADDRESS provided.");
require(_addresses[1] != address(0), "Invalid address - ZERO_ADDRESS provided.");
require(_addresses[2] != address(0), "Invalid address - ZERO_ADDRESS provided.");
require(_numbers[0] > 0, "Invalid number - Must be higher than zero.");
require(_numbers[1] > 0, "Invalid number - Must be higher than zero.");
require(_numbers[2] > 0, "Invalid number - Must be higher than zero.");
require(_numbers[3] > 0, "Invalid number - Must be higher than zero.");
require(_numbers[4] > 0, "Invalid number - Must be higher than zero.");
require(_numbers[0] <= OVERFLOW_LIMITER_NUMBER, "Invalid number - Must be lower than the overflow limit.");
require(_numbers[1] <= OVERFLOW_LIMITER_NUMBER, "Invalid number - Must be lower than the overflow limit.");
require(_numbers[2] <= OVERFLOW_LIMITER_NUMBER, "Invalid number - Must be lower than the overflow limit.");
require(_numbers[3] <= OVERFLOW_LIMITER_NUMBER, "Invalid number - Must be lower than the overflow limit.");
require(_numbers[4] <= OVERFLOW_LIMITER_NUMBER, "Invalid number - Must be lower than the overflow limit.");
require(_numbers[5] <= OVERFLOW_LIMITER_NUMBER, "Invalid number - Must be lower than the overflow limit.");
bytes32[2] memory paymentIDs = _paymentIDs;
pullPayments[paymentIDs[0]].paymentIDs[0] = paymentIDs[0];
pullPayments[paymentIDs[0]].paymentIDs[1] = paymentIDs[1];
pullPayments[paymentIDs[0]].currency = _currency;
pullPayments[paymentIDs[0]].customerAddress = _addresses[0];
pullPayments[paymentIDs[0]].executorAddress = _addresses[1];
pullPayments[paymentIDs[0]].treasuryAddress = _addresses[2];
pullPayments[paymentIDs[0]].initialConversionRate = _numbers[0];
pullPayments[paymentIDs[0]].initialPaymentAmountInCents = _numbers[1];
pullPayments[paymentIDs[0]].topUpAmountInCents = _numbers[2];
pullPayments[paymentIDs[0]].startTimestamp = _numbers[3];
pullPayments[paymentIDs[0]].totalLimit = _numbers[4];
pullPayments[paymentIDs[0]].expirationTimestamp = _numbers[5];
require(isValidRegistration(
v,
r,
s,
pullPayments[paymentIDs[0]]
),
"Invalid pull payment registration - ECRECOVER_FAILED."
);
executePullPaymentOnRegistration(
[paymentIDs[0], paymentIDs[1]],
[_addresses[0], _addresses[2]],
[_numbers[1], _numbers[0]]
);
if (isFundingNeeded(msg.sender)) {
msg.sender.transfer(FUNDING_AMOUNT);
emit LogSmartContractActorFunded("executor", msg.sender, now);
}
emit LogPaymentRegistered(_addresses[0], paymentIDs[0], paymentIDs[1]);
}
/// @dev Executes a specific top up pull payment based on the payment ID - The pull payment should exist and the payment request
/// should be valid in terms of whether it can be executed i.e. it is within the total limit and the expiration timestamp.
/// If the top up payment is executed outside the time based period set on registration, then we update the set timestamp
/// for the time based limitations and we set the time based amount spent to the top up amount.
/// If the top up payment is executed within the time based period set on registration, then the time based spent amount is
/// incremented by the top up amount.
/// For the execution we calculate the amount in PMA using the conversion rate specified when calling the method.
/// From the 'conversionRate' and the 'topUpAmountInCents' we calculate the amount of PMA that
/// the business need to receive in their treasuryAddress.
/// The smart contract transfers from the customer account to the treasury wallet the amount in PMA.
/// After execution we set the last payment timestamp to NOW and we increase the total spent amount with the top up amount.
/// Emits 'LogPullPaymentExecuted' with customer address, msg.sender as the pull payment executor address and the paymentID.
/// @param _paymentID - ID of the payment.
/// @param _conversionRate - conversion rate with which the payment needs to take place
function executeTopUpPayment(bytes32 _paymentID, uint256 _conversionRate)
public
paymentExists(_paymentID)
paymentNotCancelled(_paymentID)
isPullPaymentExecutor(_paymentID)
isValidNumber(_conversionRate)
isWithinTheTotalLimits(_paymentID)
isBeforeExpirationTimestamp(_paymentID)
returns (bool)
{
TopUpPayment storage payment = pullPayments[_paymentID];
uint256 conversionRate = _conversionRate;
uint256 amountInPMA = calculatePMAFromFiat(payment.topUpAmountInCents, conversionRate);
payment.lastPaymentTimestamp = now;
payment.totalSpent += payment.topUpAmountInCents;
token.transferFrom(payment.customerAddress, payment.treasuryAddress, amountInPMA);
emit LogPullPaymentExecuted(
payment.customerAddress,
payment.paymentIDs[0],
payment.paymentIDs[1],
amountInPMA,
conversionRate
);
return true;
}
/// @dev Cancels a top up pull payment - The cancellation needs can be executed only by one of the
/// executors of the PumaPay Pull Payment Contract and the PumaPay Pull Payment Contract checks
/// that the pull payment's paymentID and businessID have been singed by the customer address.
/// This method sets the cancellation of the pull payment in the pull payments array for this pull payment executor specified.
/// The balance of the executor (msg.sender) is checked and if funding is needed 0.5 ETH is transferred.
/// Emits 'LogPaymentCancelled' with pull payment executor address and paymentID.
/// @param v - recovery ID of the ETH signature. - https://github.com/ethereum/EIPs/issues/155
/// @param r - R output of ECDSA signature.
/// @param s - S output of ECDSA signature.
/// @param _paymentID - ID of the payment.
function cancelTopUpPayment(
uint8 v,
bytes32 r,
bytes32 s,
bytes32 _paymentID
)
public
isExecutor()
paymentExists(_paymentID)
paymentNotCancelled(_paymentID)
{
require(isValidCancellation(v, r, s, _paymentID), "Invalid cancellation - ECRECOVER_FAILED.");
pullPayments[_paymentID].cancelTimestamp = now;
if (isFundingNeeded(msg.sender)) {
msg.sender.transfer(FUNDING_AMOUNT);
emit LogSmartContractActorFunded("executor", msg.sender, now);
}
emit LogPaymentCancelled(
pullPayments[_paymentID].customerAddress,
_paymentID,
pullPayments[_paymentID].paymentIDs[1]
);
}
/// @dev Method that updates the total limit for the top up payment
/// @param _paymentID - the ID of the payment for which total limit will be updated
/// @param _newLimit - new total limit in FIAT cents
function updateTotalLimit(bytes32 _paymentID, uint256 _newLimit)
public
isCustomer(_paymentID)
isValidNumber(_newLimit)
isValidNewTotalLimit(_paymentID, _newLimit)
{
uint256 oldLimit = pullPayments[_paymentID].totalLimit;
pullPayments[_paymentID].totalLimit = _newLimit;
emit LogTotalLimitUpdated(msg.sender, _paymentID, oldLimit, _newLimit);
}
/// @dev Method that updates the expiration timestamp for the top up payment
/// @param _paymentID - the ID of the payment for which total limit will be updated
/// @param _newExpirationTimestamp - new expiration timestamp for the top up payment
function updateExpirationTimestamp(bytes32 _paymentID, uint256 _newExpirationTimestamp)
public
isCustomer(_paymentID)
isValidNumber(_newExpirationTimestamp)
isValidExpirationTimestamp(_newExpirationTimestamp)
{
uint256 oldExpirationTimestamp = pullPayments[_paymentID].expirationTimestamp;
pullPayments[_paymentID].expirationTimestamp = _newExpirationTimestamp;
emit LogExpirationTimestampUpdated(msg.sender, _paymentID, oldExpirationTimestamp, _newExpirationTimestamp);
}
/// @dev Method that updates both the total limit and the expiration timestamp for the top up payment
/// @param _paymentID - the ID of the payment for which total limit will be updated
/// @param _newLimit - new total limit in FIAT cents
/// @param _newExpirationTimestamp - new expiration timestamp for the top up payment
function updateTotalLimitAndExpirationTimestamp(bytes32 _paymentID, uint256 _newLimit, uint256 _newExpirationTimestamp)
public
isCustomer(_paymentID)
isValidNumber(_newLimit)
isValidNumber(_newExpirationTimestamp)
isValidNewTotalLimit(_paymentID, _newLimit)
isValidExpirationTimestamp(_newExpirationTimestamp)
{
updateTotalLimit(_paymentID, _newLimit);
updateExpirationTimestamp(_paymentID, _newExpirationTimestamp);
}
/// @dev method that retrieves the limits specified on the top up payment
/// @param _paymentID - ID of the payment
function retrieveLimits(bytes32 _paymentID)
public
view
returns (uint256 totalLimit, uint256 totalSpent, uint256 expirationTimestamp)
{
return (pullPayments[_paymentID].totalLimit, pullPayments[_paymentID].totalSpent, pullPayments[_paymentID].expirationTimestamp);
}
/// ===============================================================================================================
/// Internal Functions
/// ===============================================================================================================
/// @dev The initial payment of the top up happens on registration. We calculate the amount of PMA that needs to be
/// needs to be transferred from the customer to the treasury wallet, using the rate for the initial payment signed
/// by the customer. The last payment timestamp is updated and a "LogPullPaymentExecuted" event is emitted.
/// @param _paymentIDs - [0] paymentID, [1] businessID
/// @param _addresses - [0] customer, [1] treasury
/// @param _paymentAmounts - [0] initial payment in cents, [1] conversion rate
function executePullPaymentOnRegistration(
bytes32[2] memory _paymentIDs,
address[2] memory _addresses,
uint256[2] memory _paymentAmounts
)
internal
returns (bool)
{
TopUpPayment storage payment = pullPayments[_paymentIDs[0]];
uint256 amountInPMA = calculatePMAFromFiat(_paymentAmounts[0], _paymentAmounts[1]);
payment.lastPaymentTimestamp = now;
token.transferFrom(_addresses[0], _addresses[1], amountInPMA);
emit LogPullPaymentExecuted(
_addresses[0],
_paymentIDs[0],
_paymentIDs[1],
amountInPMA,
_paymentAmounts[1]
);
return true;
}
/// @dev Calculates the PMA Rate for the fiat currency specified - The rate is set every 10 minutes by our PMA server
/// for the currencies specified in the smart contract.
/// RATE CALCULATION EXAMPLE
/// ------------------------
/// RATE ==> 1 PMA = 0.01 USD$
/// 1 USD$ = 1/0.01 PMA = 100 PMA
/// Start the calculation from one ether - PMA Token has 18 decimals
/// Multiply by the DECIMAL_FIXER (1e+10) to fix the multiplication of the rate
/// Multiply with the fiat amount in cents
/// Divide by the Rate of PMA to Fiat in cents
/// Divide by the FIAT_TO_CENT_FIXER to fix the _topUpAmountInCents
/// ---------------------------------------------------------------------------------------------------------------
/// To save on gas, we have 'pre-calculated' the equation below and have set a constant in its place.
/// ONE_ETHER.mul(DECIMAL_FIXER).div(FIAT_TO_CENT_FIXER) = RATE_CALCULATION_NUMBER
/// ONE_ETHER = 10^18 |
/// DECIMAL_FIXER = 10^10 | => 10^18 * 10^10 / 100 ==> 10^26 => RATE_CALCULATION_NUMBER = 10^26
/// FIAT_TO_CENT_FIXER = 100 |
/// NOTE: The aforementioned value is linked to the OVERFLOW_LIMITER_NUMBER which is set to 10^20.
/// ---------------------------------------------------------------------------------------------------------------
/// @param _topUpAmountInCents - payment amount in fiat CENTS so that is always integer
/// @param _conversionRate - conversion rate with which the payment needs to take place
/// NOTE: No modifiers needed to check the uint256 values since we are checking both of them on the caller method
function calculatePMAFromFiat(uint256 _topUpAmountInCents, uint256 _conversionRate)
internal
pure
returns (uint256) {
return RATE_CALCULATION_NUMBER.mul(_topUpAmountInCents).div(_conversionRate);
}
/// @dev Checks if a registration request is valid by comparing the v, r, s params
/// and the hashed params with the customer address.
/// @param v - recovery ID of the ETH signature. - https://github.com/ethereum/EIPs/issues/155
/// @param r - R output of ECDSA signature.
/// @param s - S output of ECDSA signature.
/// @param _pullPayment - pull payment to be validated.
/// @return bool - if the v, r, s params with the hashed params match the customer address
function isValidRegistration(
uint8 v,
bytes32 r,
bytes32 s,
TopUpPayment memory _pullPayment
)
internal
pure
returns (bool)
{
return ecrecover(
keccak256(
abi.encodePacked(
_pullPayment.paymentIDs[0],
_pullPayment.paymentIDs[1],
_pullPayment.currency,
_pullPayment.treasuryAddress,
_pullPayment.initialConversionRate,
_pullPayment.initialPaymentAmountInCents,
_pullPayment.topUpAmountInCents,
_pullPayment.startTimestamp,
_pullPayment.expirationTimestamp,
_pullPayment.totalLimit
)
),
v, r, s) == _pullPayment.customerAddress;
}
/// @dev Checks if a cancellation request is valid by comparing the v, r, s params
/// and the hashed params with the customer address.
/// @param v - recovery ID of the ETH signature. - https://github.com/ethereum/EIPs/issues/155
/// @param r - R output of ECDSA signature.
/// @param s - S output of ECDSA signature.
/// @param _paymentID - ID of the pull payment to be cancelled.
/// @return bool - if the v, r, s params with the hashed params match the customer address
function isValidCancellation(
uint8 v,
bytes32 r,
bytes32 s,
bytes32 _paymentID
)
internal
view
returns (bool){
return ecrecover(
keccak256(
abi.encodePacked(
pullPayments[_paymentID].paymentIDs[0],
pullPayments[_paymentID].paymentIDs[1]
)
),
v, r, s) == pullPayments[_paymentID].customerAddress;
}
/// @dev Checks if the address of an owner/executor needs to be funded.
/// The minimum amount the owner/executors should always have is 0.15 ETH
/// @param _address - address of owner/executors that the balance is checked against.
/// @return bool - whether the address needs more ETH.
function isFundingNeeded(address _address)
internal
view
returns (bool) {
return address(_address).balance <= MINIMUM_AMOUNT_OF_ETH_FOR_OPERATORS;
}
}