-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathe2pAirEscrow.sol
241 lines (208 loc) · 8.35 KB
/
e2pAirEscrow.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
import './utilities/SafeMath.sol';
import './utilities/Stoppable.sol';
import './token/ERC20/ERC20.sol';
/**
* @title e2pAir Escrow Contract
* @dev Contract sends tokens from airdropper's account to receiver on claim.
*
* When deploying contract, airdroper provides airdrop parametrs: token, amount
* of tokens and amount of eth should be claimed per link and airdrop transit
* address and deposits ether needed for the airdrop.
*
* Airdrop transit address is used to verify that links are signed by airdropper.
*
* Airdropper generates claim links. Each link contains a private key
* signed by the airdrop transit private key. The link private key can be used
* once to sign receiver's address. Receiver provides signature
* to the Relayer Server, which calls smart contract to withdraw tokens.
*
* On claim smart contract verifies, that receiver provided address signed
* by a link private key.
* If everything is correct smart contract sends tokens and ether to receiver.
*
* Anytime airdropper can get back unclaimed ether using getEtherBack method.
*
*/
contract e2pAirEscrow is Stoppable {
address public TOKEN_ADDRESS; // token to distribute
uint public CLAIM_AMOUNT; // tokens claimed per link
uint public REFERRAL_AMOUNT; // referral reward
uint public CLAIM_AMOUNT_ETH; // ether claimed per link
address public AIRDROPPER; // airdropper address, which has tokens to distribute
address public AIRDROP_TRANSIT_ADDRESS; // special address, used on claim to verify
// that links signed by the airdropper
event LogWithdraw(
address transitAddress,
address receiver,
uint timestamp
);
// Mappings of transit address => true if link is used.
mapping (address => bool) usedTransitAddresses;
/**
* @dev Contructor that sets airdrop params and receives ether needed for the
* airdrop.
* @param _tokenAddress address Token address to distribute
* @param _claimAmount uint tokens (in atomic values) claimed per link
* @param _claimAmountEth uint ether (in wei) claimed per link
* @param _airdropTransitAddress special address, used on claim to verify that links signed by airdropper
*/
constructor(address _tokenAddress,
uint _claimAmount,
uint _referralAmount,
uint _claimAmountEth,
address _airdropTransitAddress) public payable {
AIRDROPPER = msg.sender;
TOKEN_ADDRESS = _tokenAddress;
CLAIM_AMOUNT = _claimAmount;
REFERRAL_AMOUNT = _referralAmount;
CLAIM_AMOUNT_ETH = _claimAmountEth;
AIRDROP_TRANSIT_ADDRESS = _airdropTransitAddress;
}
/**
* @dev Verify that address is signed with needed private key.
* @param _transitAddress transit address assigned to transfer
* @param _addressSigned address Signed address.
* @param _v ECDSA signature parameter v.
* @param _r ECDSA signature parameters r.
* @param _s ECDSA signature parameters s.
* @return True if signature is correct.
*/
function verifyLinkPrivateKey(
address _transitAddress,
address _addressSigned,
address _referralAddress,
uint8 _v,
bytes32 _r,
bytes32 _s)
public pure returns(bool success) {
bytes32 prefixedHash = keccak256("\x19Ethereum Signed Message:\n32", _addressSigned, _referralAddress);
address retAddr = ecrecover(prefixedHash, _v, _r, _s);
return retAddr == _transitAddress;
}
/**
* @dev Verify that address is signed with needed private key.
* @param _transitAddress transit address assigned to transfer
* @param _addressSigned address Signed address.
* @param _v ECDSA signature parameter v.
* @param _r ECDSA signature parameters r.
* @param _s ECDSA signature parameters s.
* @return True if signature is correct.
*/
function verifyReceiverAddress(
address _transitAddress,
address _addressSigned,
uint8 _v,
bytes32 _r,
bytes32 _s)
public pure returns(bool success) {
bytes32 prefixedHash = keccak256("\x19Ethereum Signed Message:\n32", _addressSigned);
address retAddr = ecrecover(prefixedHash, _v, _r, _s);
return retAddr == _transitAddress;
}
/**
* @dev Verify that claim params are correct and the link key wasn't used before.
* @param _recipient address to receive tokens.
* @param _transitAddress transit address provided by the airdropper
* @param _keyV ECDSA signature parameter v. Signed by the airdrop transit key.
* @param _keyR ECDSA signature parameters r. Signed by the airdrop transit key.
* @param _keyS ECDSA signature parameters s. Signed by the airdrop transit key.
* @param _recipientV ECDSA signature parameter v. Signed by the link key.
* @param _recipientR ECDSA signature parameters r. Signed by the link key.
* @param _recipientS ECDSA signature parameters s. Signed by the link key.
* @return True if claim params are correct.
*/
function checkWithdrawal(
address _recipient,
address _referralAddress,
address _transitAddress,
uint8 _keyV,
bytes32 _keyR,
bytes32 _keyS,
uint8 _recipientV,
bytes32 _recipientR,
bytes32 _recipientS)
public view returns(bool success) {
// verify that link wasn't used before
require(usedTransitAddresses[_transitAddress] == false);
// verifying that key is legit and signed by AIRDROP_TRANSIT_ADDRESS's key
require(verifyLinkPrivateKey(AIRDROP_TRANSIT_ADDRESS, _transitAddress, _referralAddress, _keyV, _keyR, _keyS));
// verifying that recepients address signed correctly
require(verifyReceiverAddress(_transitAddress, _recipient, _recipientV, _recipientR, _recipientS));
// verifying that there is enough ether to make transfer
require(address(this).balance >= CLAIM_AMOUNT_ETH);
return true;
}
/**
* @dev Withdraw tokens to receiver address if withdraw params are correct.
* @param _recipient address to receive tokens.
* @param _transitAddress transit address provided to receiver by the airdropper
* @param _keyV ECDSA signature parameter v. Signed by the airdrop transit key.
* @param _keyR ECDSA signature parameters r. Signed by the airdrop transit key.
* @param _keyS ECDSA signature parameters s. Signed by the airdrop transit key.
* @param _recipientV ECDSA signature parameter v. Signed by the link key.
* @param _recipientR ECDSA signature parameters r. Signed by the link key.
* @param _recipientS ECDSA signature parameters s. Signed by the link key.
* @return True if tokens (and ether) were successfully sent to receiver.
*/
function withdraw(
address _recipient,
address _referralAddress,
address _transitAddress,
uint8 _keyV,
bytes32 _keyR,
bytes32 _keyS,
uint8 _recipientV,
bytes32 _recipientR,
bytes32 _recipientS
)
public
whenNotPaused
whenNotStopped
returns (bool success) {
require(checkWithdrawal(_recipient,
_referralAddress,
_transitAddress,
_keyV,
_keyR,
_keyS,
_recipientV,
_recipientR,
_recipientS));
// save to state that address was used
usedTransitAddresses[_transitAddress] = true;
// send tokens
if (CLAIM_AMOUNT > 0 && TOKEN_ADDRESS != 0x0000000000000000000000000000000000000000) {
StandardToken token = StandardToken(TOKEN_ADDRESS);
token.transferFrom(AIRDROPPER, _recipient, CLAIM_AMOUNT);
}
// send tokens to the address who refferred the airdrop
if (REFERRAL_AMOUNT > 0 && _referralAddress != 0x0000000000000000000000000000000000000000) {
token.transferFrom(AIRDROPPER, _referralAddress, REFERRAL_AMOUNT);
}
// send ether (if needed)
if (CLAIM_AMOUNT_ETH > 0) {
_recipient.transfer(CLAIM_AMOUNT_ETH);
}
// Log Withdrawal
emit LogWithdraw(_transitAddress, _recipient, now);
return true;
}
/**
* @dev Get boolean if link is already claimed.
* @param _transitAddress transit address provided to receiver by the airdropper
* @return True if the transit address was already used.
*/
function isLinkClaimed(address _transitAddress)
public view returns (bool claimed) {
return usedTransitAddresses[_transitAddress];
}
/**
* @dev Withdraw ether back deposited to the smart contract.
* @return True if ether was withdrawn.
*/
function getEtherBack() public returns (bool success) {
require(msg.sender == AIRDROPPER);
AIRDROPPER.transfer(address(this).balance);
return true;
}
}