/
MixinRefunds.sol
166 lines (144 loc) · 4.37 KB
/
MixinRefunds.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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import './MixinKeys.sol';
import './MixinLockCore.sol';
import './MixinRoles.sol';
import './MixinFunds.sol';
contract MixinRefunds is
MixinRoles,
MixinFunds,
MixinLockCore,
MixinKeys
{
// CancelAndRefund will return funds based on time remaining minus this penalty.
// This is calculated as `proRatedRefund * refundPenaltyBasisPoints / BASIS_POINTS_DEN`.
uint public refundPenaltyBasisPoints;
uint public freeTrialLength;
event CancelKey(
uint indexed tokenId,
address indexed owner,
address indexed sendTo,
uint refund
);
event RefundPenaltyChanged(
uint freeTrialLength,
uint refundPenaltyBasisPoints
);
function _initializeMixinRefunds() internal
{
// default to 10%
refundPenaltyBasisPoints = 1000;
}
/**
* @dev Invoked by the lock owner to destroy the user's ket and perform a refund and cancellation
* of the key
*/
function expireAndRefundFor(
address payable _keyOwner,
uint amount
) external {
_onlyLockManager();
_hasValidKey(_keyOwner);
_cancelAndRefund(_keyOwner, amount);
}
/**
* @dev Destroys the key and sends a refund based on the amount of time remaining.
* @param _tokenId The id of the key to cancel.
*/
function cancelAndRefund(uint _tokenId)
external
{
_onlyKeyManagerOrApproved(_tokenId);
address payable keyOwner = payable(ownerOf(_tokenId));
uint refund = _getCancelAndRefundValue(keyOwner);
_cancelAndRefund(keyOwner, refund);
}
/**
* Allow the owner to change the refund penalty.
*/
function updateRefundPenalty(
uint _freeTrialLength,
uint _refundPenaltyBasisPoints
) external {
_onlyLockManager();
emit RefundPenaltyChanged(
_freeTrialLength,
_refundPenaltyBasisPoints
);
freeTrialLength = _freeTrialLength;
refundPenaltyBasisPoints = _refundPenaltyBasisPoints;
}
/**
* @dev Determines how much of a refund a key owner would receive if they issued
* a cancelAndRefund block.timestamp.
* Note that due to the time required to mine a tx, the actual refund amount will be lower
* than what the user reads from this call.
*/
function getCancelAndRefundValueFor(
address _keyOwner
)
external view
returns (uint refund)
{
return _getCancelAndRefundValue(_keyOwner);
}
/**
* @dev cancels the key for the given keyOwner and sends the refund to the msg.sender.
*/
function _cancelAndRefund(
address payable _keyOwner,
uint refund
) internal
{
Key memory key = getKeyByOwner(_keyOwner);
emit CancelKey(key.tokenId, _keyOwner, msg.sender, refund);
// expirationTimestamp is a proxy for hasKey, setting this to `block.timestamp` instead
// of 0 so that we can still differentiate hasKey from hasValidKey.
_updateKeyExpirationTimestamp(_keyOwner, block.timestamp);
if (refund > 0) {
// Security: doing this last to avoid re-entrancy concerns
_transfer(tokenAddress, _keyOwner, refund);
}
// inform the hook if there is one registered
if(address(onKeyCancelHook) != address(0))
{
onKeyCancelHook.onKeyCancel(msg.sender, _keyOwner, refund);
}
}
/**
* @dev Determines how much of a refund a key owner would receive if they issued
* a cancelAndRefund now.
* @param _keyOwner The owner of the key check the refund value for.
*/
function _getCancelAndRefundValue(
address _keyOwner
)
private view
returns (uint refund)
{
_hasValidKey(_keyOwner);
Key memory key = getKeyByOwner(_keyOwner);
// return entire purchased price if key is non-expiring
if(expirationDuration == type(uint).max) {
return keyPrice;
}
// Math: safeSub is not required since `hasValidKey` confirms timeRemaining is positive
uint timeRemaining = key.expirationTimestamp - block.timestamp;
if(timeRemaining + freeTrialLength >= expirationDuration) {
refund = keyPrice;
} else {
refund = keyPrice * timeRemaining / expirationDuration;
}
// Apply the penalty if this is not a free trial
if(freeTrialLength == 0 || timeRemaining + freeTrialLength < expirationDuration)
{
uint penalty = keyPrice * refundPenaltyBasisPoints / BASIS_POINTS_DEN;
if (refund > penalty) {
refund -= penalty;
} else {
refund = 0;
}
}
}
uint256[1000] private __safe_upgrade_gap;
}