-
Notifications
You must be signed in to change notification settings - Fork 110
/
HashedTimelock.sol
227 lines (213 loc) · 7.05 KB
/
HashedTimelock.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
pragma solidity ^0.5.0;
/**
* @title Hashed Timelock Contracts (HTLCs) on Ethereum ETH.
*
* This contract provides a way to create and keep HTLCs for ETH.
*
* See HashedTimelockERC20.sol for a contract that provides the same functions
* for ERC20 tokens.
*
* Protocol:
*
* 1) newContract(receiver, hashlock, timelock) - a sender calls this to create
* a new HTLC and gets back a 32 byte contract id
* 2) withdraw(contractId, preimage) - once the receiver knows the preimage of
* the hashlock hash they can claim the ETH with this function
* 3) refund() - after timelock has expired and if the receiver did not
* withdraw funds the sender / creator of the HTLC can get their ETH
* back with this function.
*/
contract HashedTimelock {
event LogHTLCNew(
bytes32 indexed contractId,
address indexed sender,
address indexed receiver,
uint amount,
bytes32 hashlock,
uint timelock
);
event LogHTLCWithdraw(bytes32 indexed contractId);
event LogHTLCRefund(bytes32 indexed contractId);
struct LockContract {
address payable sender;
address payable receiver;
uint amount;
bytes32 hashlock; // sha-2 sha256 hash
uint timelock; // UNIX timestamp seconds - locked UNTIL this time
bool withdrawn;
bool refunded;
bytes32 preimage;
}
modifier fundsSent() {
require(msg.value > 0, "msg.value must be > 0");
_;
}
modifier futureTimelock(uint _time) {
// only requirement is the timelock time is after the last blocktime (now).
// probably want something a bit further in the future then this.
// but this is still a useful sanity check:
require(_time > now, "timelock time must be in the future");
_;
}
modifier contractExists(bytes32 _contractId) {
require(haveContract(_contractId), "contractId does not exist");
_;
}
modifier hashlockMatches(bytes32 _contractId, bytes32 _x) {
require(
contracts[_contractId].hashlock == sha256(abi.encodePacked(_x)),
"hashlock hash does not match"
);
_;
}
modifier withdrawable(bytes32 _contractId) {
require(contracts[_contractId].receiver == msg.sender, "withdrawable: not receiver");
require(contracts[_contractId].withdrawn == false, "withdrawable: already withdrawn");
require(contracts[_contractId].timelock > now, "withdrawable: timelock time must be in the future");
_;
}
modifier refundable(bytes32 _contractId) {
require(contracts[_contractId].sender == msg.sender, "refundable: not sender");
require(contracts[_contractId].refunded == false, "refundable: already refunded");
require(contracts[_contractId].withdrawn == false, "refundable: already withdrawn");
require(contracts[_contractId].timelock <= now, "refundable: timelock not yet passed");
_;
}
mapping (bytes32 => LockContract) contracts;
/**
* @dev Sender sets up a new hash time lock contract depositing the ETH and
* providing the reciever lock terms.
*
* @param _receiver Receiver of the ETH.
* @param _hashlock A sha-2 sha256 hash hashlock.
* @param _timelock UNIX epoch seconds time that the lock expires at.
* Refunds can be made after this time.
* @return contractId Id of the new HTLC. This is needed for subsequent
* calls.
*/
function newContract(address payable _receiver, bytes32 _hashlock, uint _timelock)
external
payable
fundsSent
futureTimelock(_timelock)
returns (bytes32 contractId)
{
contractId = sha256(
abi.encodePacked(
msg.sender,
_receiver,
msg.value,
_hashlock,
_timelock
)
);
// Reject if a contract already exists with the same parameters. The
// sender must change one of these parameters to create a new distinct
// contract.
if (haveContract(contractId))
revert("Contract already exists");
contracts[contractId] = LockContract(
msg.sender,
_receiver,
msg.value,
_hashlock,
_timelock,
false,
false,
0x0
);
emit LogHTLCNew(
contractId,
msg.sender,
_receiver,
msg.value,
_hashlock,
_timelock
);
}
/**
* @dev Called by the receiver once they know the preimage of the hashlock.
* This will transfer the locked funds to their address.
*
* @param _contractId Id of the HTLC.
* @param _preimage sha256(_preimage) should equal the contract hashlock.
* @return bool true on success
*/
function withdraw(bytes32 _contractId, bytes32 _preimage)
external
contractExists(_contractId)
hashlockMatches(_contractId, _preimage)
withdrawable(_contractId)
returns (bool)
{
LockContract storage c = contracts[_contractId];
c.preimage = _preimage;
c.withdrawn = true;
c.receiver.transfer(c.amount);
emit LogHTLCWithdraw(_contractId);
return true;
}
/**
* @dev Called by the sender if there was no withdraw AND the time lock has
* expired. This will refund the contract amount.
*
* @param _contractId Id of HTLC to refund from.
* @return bool true on success
*/
function refund(bytes32 _contractId)
external
contractExists(_contractId)
refundable(_contractId)
returns (bool)
{
LockContract storage c = contracts[_contractId];
c.refunded = true;
c.sender.transfer(c.amount);
emit LogHTLCRefund(_contractId);
return true;
}
/**
* @dev Get contract details.
* @param _contractId HTLC contract id
* @return All parameters in struct LockContract for _contractId HTLC
*/
function getContract(bytes32 _contractId)
public
view
returns (
address sender,
address receiver,
uint amount,
bytes32 hashlock,
uint timelock,
bool withdrawn,
bool refunded,
bytes32 preimage
)
{
if (haveContract(_contractId) == false)
return (address(0), address(0), 0, 0, 0, false, false, 0);
LockContract storage c = contracts[_contractId];
return (
c.sender,
c.receiver,
c.amount,
c.hashlock,
c.timelock,
c.withdrawn,
c.refunded,
c.preimage
);
}
/**
* @dev Is there a contract with id _contractId.
* @param _contractId Id into contracts mapping.
*/
function haveContract(bytes32 _contractId)
internal
view
returns (bool exists)
{
exists = (contracts[_contractId].sender != address(0));
}
}