-
-
Notifications
You must be signed in to change notification settings - Fork 240
/
MixinPurchase.sol
103 lines (89 loc) · 3.43 KB
/
MixinPurchase.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
pragma solidity 0.5.12;
import './MixinDisableAndDestroy.sol';
import './MixinKeys.sol';
import './MixinLockCore.sol';
import '@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol';
import './MixinFunds.sol';
import './MixinEventHooks.sol';
/**
* @title Mixin for the purchase-related functions.
* @author HardlyDifficult
* @dev `Mixins` are a design pattern seen in the 0x contracts. It simply
* separates logically groupings of code to ease readability.
*/
contract MixinPurchase is
MixinFunds,
MixinDisableAndDestroy,
MixinLockCore,
MixinKeys,
MixinEventHooks
{
using SafeMath for uint;
/**
* @dev Purchase function
* @param _value the number of tokens to pay for this purchase >= the current keyPrice - any applicable discount
* (_value is ignored when using ETH)
* @param _recipient address of the recipient of the purchased key
* @param _referrer address of the user making the referral
* @param _data arbitrary data populated by the front-end which initiated the sale
* @dev Setting _value to keyPrice exactly doubles as a security feature. That way if the lock owner increases the
* price while my transaction is pending I can't be charged more than I expected (only applicable to ERC-20 when more
* than keyPrice is approved for spending).
*/
function purchase(
uint256 _value,
address _recipient,
address _referrer,
bytes calldata _data
) external payable
onlyIfAlive
notSoldOut
{
require(_recipient != address(0), 'INVALID_ADDRESS');
// Assign the key
Key storage toKey = keyByOwner[_recipient];
if (toKey.tokenId == 0) {
// Assign a new tokenId (if a new owner or previously transfered)
_assignNewTokenId(toKey);
_recordOwner(_recipient, toKey.tokenId);
}
if (toKey.expirationTimestamp >= block.timestamp) {
// This is an existing owner trying to extend their key
toKey.expirationTimestamp = toKey.expirationTimestamp.add(expirationDuration);
} else {
// SafeAdd is not required here since expirationDuration is capped to a tiny value
// (relative to the size of a uint)
toKey.expirationTimestamp = block.timestamp + expirationDuration;
}
// Let's get the actual price for the key from the Unlock smart contract
uint discount;
uint tokens;
uint inMemoryKeyPrice = keyPrice;
(discount, tokens) = unlockProtocol.computeAvailableDiscountFor(_recipient, inMemoryKeyPrice);
if (discount > inMemoryKeyPrice) {
inMemoryKeyPrice = 0;
} else {
// SafeSub not required as the if statement already confirmed `inMemoryKeyPrice - discount` cannot underflow
inMemoryKeyPrice -= discount;
}
if (discount > 0) {
unlockProtocol.recordConsumedDiscount(discount, tokens);
}
unlockProtocol.recordKeyPurchase(inMemoryKeyPrice, getHasValidKey(_referrer) ? _referrer : address(0));
// trigger event
emit Transfer(
address(0), // This is a creation.
_recipient,
toKey.tokenId
);
// We explicitly allow for greater amounts of ETH or tokens to allow 'donations'
if(tokenAddress != address(0)) {
require(_value >= inMemoryKeyPrice, 'INSUFFICIENT_VALUE');
inMemoryKeyPrice = _value;
}
// Security: after state changes to minimize risk of re-entrancy
uint pricePaid = _chargeAtLeast(inMemoryKeyPrice);
// Security: last line to minimize risk of re-entrancy
_onKeySold(_recipient, _referrer, pricePaid, _data);
}
}