|
| 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