Skip to content

Commit 216a3a1

Browse files
authored
Merge pull request #305 from windingtree/add-LifChannels
Add Lif Channels
2 parents 5d2dcba + 1fbc7af commit 216a3a1

File tree

2 files changed

+462
-0
lines changed

2 files changed

+462
-0
lines changed

contracts/LifChannels.sol

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
pragma solidity ^0.4.18;
2+
3+
import "zeppelin-solidity/contracts/token/ERC20.sol";
4+
import "zeppelin-solidity/contracts/math/SafeMath.sol";
5+
import "zeppelin-solidity/contracts/ECRecovery.sol";
6+
7+
/**
8+
@title LifChannels, State channels for ERC20 Lif Token
9+
10+
Contract that provides holders of a ERC20 compatible token the creation of
11+
channels between two users, once a channel is open the users can exchange
12+
signatures offchain to agree on the final value of the transfer.
13+
Uses OpenZeppelin ERC20 and SafeMath lib.
14+
*/
15+
contract LifChannels {
16+
using SafeMath for uint256;
17+
18+
// Add recover method for bytes32 using ECRecovery lib from OpenZeppelin
19+
using ECRecovery for bytes32;
20+
21+
// The ERC20 token to be used
22+
ERC20 public token;
23+
24+
// The amount of time that a receiver has to challenge the sender
25+
uint256 public challengeTime;
26+
27+
// The channels opened
28+
mapping (bytes32 => Channel) public channels;
29+
struct Channel {
30+
uint256 deposit;
31+
uint8 nonce;
32+
}
33+
34+
// The requests from sender to close a channel
35+
mapping (bytes32 => ClosingRequest) public closingRequests;
36+
struct ClosingRequest {
37+
uint256 closingBalance;
38+
uint256 closeTime;
39+
}
40+
41+
/*
42+
* Events
43+
*/
44+
45+
event ChannelCreated(
46+
address indexed sender,
47+
address indexed receiver,
48+
uint8 indexed nonce,
49+
uint256 deposit
50+
);
51+
event ChannelCloseRequested(
52+
address indexed sender,
53+
address indexed receiver,
54+
uint8 indexed nonce,
55+
uint256 balance
56+
);
57+
event ChannelClosed(
58+
address indexed sender,
59+
address indexed receiver,
60+
uint8 indexed nonce,
61+
uint256 balance
62+
);
63+
64+
/**
65+
* @dev Constructor
66+
* @param tokenAddress address, the address of the ERC20 token that will be used
67+
* @param _challengeTime uint256, the time that a channel has before ends
68+
with and uncoopertive close
69+
*/
70+
function LifChannels(
71+
address tokenAddress,
72+
uint256 _challengeTime
73+
) public {
74+
require(tokenAddress != address(0));
75+
require(_challengeTime >= 0);
76+
77+
token = ERC20(tokenAddress);
78+
challengeTime = _challengeTime;
79+
}
80+
81+
/**
82+
* @dev Creates a channel between the msg.sender and the receiver
83+
* @param receiver address, the receiver of the channel
84+
* @param deposit uint256, the balance taht I want to load in the channel
85+
* @param nonce uint8, the nonce number of the channel
86+
*/
87+
function openChannel(
88+
address receiver,
89+
uint256 deposit,
90+
uint8 nonce
91+
) external {
92+
93+
require(nonce > 0);
94+
require(receiver != address(0));
95+
require(deposit > 0);
96+
97+
// Create unique identifier from sender, receiver and current block timestamp
98+
bytes32 channelId = getChannelId(msg.sender, receiver, nonce);
99+
100+
// Check taht teh channel not exist
101+
require(channels[channelId].deposit == 0);
102+
require(channels[channelId].nonce == 0);
103+
require(closingRequests[channelId].closeTime == 0);
104+
105+
// Store channel information
106+
channels[channelId] = Channel({deposit: deposit, nonce: nonce});
107+
ChannelCreated(msg.sender, receiver, nonce, deposit);
108+
109+
// transferFrom deposit from sender to contract
110+
// ! needs prior approval from user
111+
require(token.transferFrom(msg.sender, address(this), deposit));
112+
}
113+
114+
/**
115+
* @dev Starts a close channel request form the sender
116+
* @param receiver address, the receiver of the channel
117+
* @param nonce uint8, the nonce number of the channel
118+
* @param balance uint256, the final balance of teh receiver
119+
*/
120+
function uncooperativeClose(
121+
address receiver,
122+
uint8 nonce,
123+
uint256 balance
124+
) external {
125+
bytes32 channelId = getChannelId(msg.sender, receiver, nonce);
126+
127+
// Check that the closing request dont exist
128+
require(closingRequests[channelId].closeTime == 0);
129+
130+
// Check that the balance is less that the deposited
131+
require(balance <= channels[channelId].deposit);
132+
133+
// Mark channel as closed and create closing request
134+
closingRequests[channelId].closeTime = block.timestamp.add(challengeTime);
135+
require(closingRequests[channelId].closeTime > block.timestamp);
136+
closingRequests[channelId].closingBalance = balance;
137+
ChannelCloseRequested(msg.sender, receiver, nonce, balance);
138+
}
139+
140+
/**
141+
* @dev Close a channel with the agreement of the sender and receiver
142+
* @param receiver address, the receiver of the channel
143+
* @param nonce uint8, the nonce number of the channel
144+
* @param balanceMsgSig bytes, the signature of the sender
145+
* @param closingSig bytes, the signature of the receiver
146+
*/
147+
function cooperativeClose(
148+
address receiver,
149+
uint8 nonce,
150+
uint256 balance,
151+
bytes balanceMsgSig,
152+
bytes closingSig
153+
) external {
154+
// Derive receiver address from signature
155+
bytes32 msgHash = keccak256(balanceMsgSig);
156+
require(receiver == msgHash.recover(closingSig));
157+
158+
// Derive sender address from signed balance proof
159+
address sender = getSignerOfBalanceHash(receiver, nonce, balance, balanceMsgSig);
160+
161+
close(sender, receiver, nonce, balance);
162+
}
163+
164+
/**
165+
* @dev Close a channel with an existing closing request
166+
* @param receiver address, the receiver of the channel
167+
* @param nonce uint8, the nonce number of the channel
168+
*/
169+
function closeChannel(address receiver, uint8 nonce) external {
170+
bytes32 channelId = getChannelId(msg.sender, receiver, nonce);
171+
172+
// Check that the closing request was created
173+
require(closingRequests[channelId].closeTime > 0);
174+
175+
// Make sure the challengeTime has ended
176+
require(block.timestamp > closingRequests[channelId].closeTime);
177+
178+
close(msg.sender, receiver, nonce,
179+
closingRequests[channelId].closingBalance
180+
);
181+
}
182+
183+
/**
184+
* @dev Get the channel info
185+
* @param sender address, the sender of the channel
186+
* @param receiver address, the receiver of the channel
187+
* @param nonce uint8, the nonce number of the channel
188+
*/
189+
function getChannelInfo(
190+
address sender,
191+
address receiver,
192+
uint8 nonce
193+
) external view returns (bytes32, uint256, uint256, uint256) {
194+
bytes32 channelId = getChannelId(sender, receiver, nonce);
195+
require(channels[channelId].nonce > 0);
196+
197+
return (
198+
channelId,
199+
channels[channelId].deposit,
200+
closingRequests[channelId].closeTime,
201+
closingRequests[channelId].closingBalance
202+
);
203+
}
204+
205+
/*
206+
* Public functions
207+
*/
208+
209+
/**
210+
* @dev Get the signer of a balance hash signed with a generated hash on chain
211+
* @param receiver address, the receiver to hash
212+
* @param nonce uint8, the nonce number of the channel
213+
* @param balance uint256, the balance to hash
214+
* @param msgSigned bytes, the balance hash signed
215+
*/
216+
function getSignerOfBalanceHash(
217+
address receiver,
218+
uint8 nonce,
219+
uint256 balance,
220+
bytes msgSigned
221+
) public view returns (address) {
222+
bytes32 msgHash = generateBalanceHash(receiver, nonce, balance);
223+
// Derive address from signature
224+
address signer = msgHash.recover(msgSigned);
225+
return signer;
226+
}
227+
228+
/**
229+
* @dev Generate a hash balance for an address
230+
* @param receiver address, the receiver to hash
231+
* @param nonce uint8, the nonce number of the channel
232+
* @param balance uint256, the balance to hash
233+
*/
234+
function generateBalanceHash(
235+
address receiver,
236+
uint8 nonce,
237+
uint256 balance
238+
) public view returns (bytes32) {
239+
return keccak256(receiver, nonce, balance, address(this));
240+
}
241+
242+
/**
243+
* @dev Generate a keccak256 hash
244+
* @param message bytes, the mesage to hash
245+
*/
246+
function generateKeccak256(bytes message) public pure returns(bytes32) {
247+
return keccak256(message);
248+
}
249+
250+
/**
251+
* @dev Generate a channel id
252+
* @param sender address, the sender in the channel
253+
* @param receiver address, the receiver in the channel
254+
* @param nonce uint8, the nonce number of the channel
255+
*/
256+
function getChannelId(
257+
address sender,
258+
address receiver,
259+
uint8 nonce
260+
) public pure returns (bytes32 data) {
261+
return keccak256(sender, receiver, nonce);
262+
}
263+
264+
/*
265+
* Internal functions
266+
*/
267+
268+
/**
269+
* @dev Close a channel
270+
* @param sender address, the sender in the channel
271+
* @param receiver address, the receiver in the channel
272+
* @param nonce uint8, the nonce number of the channel
273+
* @param receiverBalance uint256, the final balance of the receiver
274+
*/
275+
function close(
276+
address sender,
277+
address receiver,
278+
uint8 nonce,
279+
uint256 receiverBalance
280+
) internal {
281+
bytes32 channelId = getChannelId(sender, receiver, nonce);
282+
Channel memory channel = channels[channelId];
283+
284+
require(channel.nonce > 0);
285+
require(receiverBalance <= channel.deposit);
286+
287+
// Remove closed channel structures
288+
// channel.nonce will become 0
289+
// Change state before transfer call
290+
delete channels[channelId];
291+
delete closingRequests[channelId];
292+
293+
// Send balance to the receiver, as it is always <= deposit
294+
require(token.transfer(receiver, receiverBalance));
295+
296+
// Send deposit - balance back to sender
297+
require(token.transfer(sender, channel.deposit.sub(receiverBalance)));
298+
299+
ChannelClosed(sender, receiver, nonce, receiverBalance);
300+
}
301+
302+
}

0 commit comments

Comments
 (0)