├── .gitignore ├── LICENSE ├── README.md ├── contracts ├── BimodalLib.sol ├── BimodalProxy.sol ├── ChallengeLib.sol ├── ChallengeProxy.sol ├── DepositLib.sol ├── DepositProxy.sol ├── ERC20.sol ├── ERC20TokenImplementation.sol ├── MerkleVerifier.sol ├── MerkleVerifierProxy.sol ├── Migrations.sol ├── NOCUSTCommitChain.sol ├── RecoveryLib.sol ├── RecoveryProxy.sol ├── SafeMath │ ├── SafeMathLib256.sol │ ├── SafeMathLib32.sol │ └── SafeMathLib64.sol ├── WithdrawalLib.sol └── WithdrawalProxy.sol ├── migrations ├── 1_initial_migration.js ├── 2_deploy_bimodal.js ├── 3_deploy_verifier.js ├── 4_deploy_challenges.js ├── 5_deploy_withdrawals.js ├── 6_deploy_recovery.js ├── 7_deploy_deposits.js └── 8_deploy_paymenthub.js ├── package-lock.json ├── package.json └── truffle.js /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | coverage 4 | .0x-artifacts 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Liquidity Network 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | - Compiler: solc 0.4.24+commit.e67f0147.Emscripten.clang 2 | - NOCUST paper: https://eprint.iacr.org/2018/642.pdf 3 | - Technical specifications: http://specs.liquidity.network/ 4 | - Website: http://liquidity.network/ 5 | 6 | ## Disclaimer 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /contracts/BimodalLib.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./ERC20.sol"; 4 | import "./SafeMath/SafeMathLib256.sol"; 5 | 6 | /** 7 | * This library defines the bi-modal commit-chain ledger. It provides data 8 | * structure definitions, accessors and mutators. 9 | */ 10 | library BimodalLib { 11 | using SafeMathLib256 for uint256; 12 | 13 | // ENUMS 14 | enum ChallengeType { 15 | NONE, 16 | STATE_UPDATE, 17 | TRANSFER_DELIVERY, 18 | SWAP_ENACTMENT 19 | } 20 | 21 | // DATA MODELS 22 | /** 23 | * Aggregate field datastructure used to sum up deposits / withdrawals for an eon. 24 | */ 25 | struct AmountAggregate { 26 | uint256 eon; 27 | uint256 amount; 28 | } 29 | 30 | /** 31 | * The structure for a submitted commit-chain checkpoint. 32 | */ 33 | struct Checkpoint { 34 | uint256 eonNumber; 35 | bytes32 merkleRoot; 36 | uint256 liveChallenges; 37 | } 38 | 39 | /** 40 | * A structure representing a single commit-chain wallet. 41 | */ 42 | struct Wallet { 43 | // Deposits performed in the last three eons 44 | AmountAggregate[3] depositsKept; 45 | // Withdrawals requested and not yet confirmed 46 | Withdrawal[] withdrawals; 47 | // Recovery flag denoting whether this account has retrieved its funds 48 | bool recovered; 49 | } 50 | 51 | /** 52 | * A structure denoting a single withdrawal request. 53 | */ 54 | struct Withdrawal { 55 | uint256 eon; 56 | uint256 amount; 57 | } 58 | 59 | /** 60 | * A structure containing the information of a single challenge. 61 | */ 62 | struct Challenge { 63 | // State Update Challenges 64 | ChallengeType challengeType; // 0 65 | uint256 block; // 1 66 | uint256 initialStateEon; // 2 67 | uint256 initialStateBalance; // 3 68 | uint256 deltaHighestSpendings; // 4 69 | uint256 deltaHighestGains; // 5 70 | uint256 finalStateBalance; // 6 71 | uint256 deliveredTxNonce; // 7 72 | uint64 trailIdentifier; // 8 73 | } 74 | 75 | /** 76 | * The types of parent-chain operations logged into the accumulator. 77 | */ 78 | enum Operation { 79 | DEPOSIT, 80 | WITHDRAWAL, 81 | CANCELLATION 82 | } 83 | 84 | /* solhint-disable var-name-mixedcase */ 85 | /** 86 | * The structure for an instance of the commit-chain ledger. 87 | */ 88 | struct Ledger { 89 | // OPERATIONAL CONSTANTS 90 | uint8 EONS_KEPT; 91 | uint8 DEPOSITS_KEPT; 92 | uint256 MIN_CHALLENGE_GAS_COST; 93 | uint256 BLOCKS_PER_EON; 94 | uint256 BLOCKS_PER_EPOCH; 95 | uint256 EXTENDED_BLOCKS_PER_EPOCH; 96 | // STATE VARIABLES 97 | uint256 genesis; 98 | address operator; 99 | Checkpoint[5] checkpoints; 100 | bytes32[5] parentChainAccumulator; // bytes32[EONS_KEPT] 101 | uint256 lastSubmissionEon; 102 | mapping (address => mapping (address => mapping (address => Challenge))) challengeBook; 103 | mapping (address => mapping (address => Wallet)) walletBook; 104 | mapping (address => AmountAggregate[5]) deposits; 105 | mapping (address => AmountAggregate[5]) pendingWithdrawals; 106 | mapping (address => AmountAggregate[5]) confirmedWithdrawals; 107 | mapping (address => uint64) tokenToTrail; 108 | address[] trailToToken; 109 | } 110 | /* solhint-enable */ 111 | 112 | // INITIALIZATION 113 | function init( 114 | Ledger storage self, 115 | uint256 blocksPerEon, 116 | address operator 117 | ) 118 | public 119 | { 120 | self.BLOCKS_PER_EON = blocksPerEon; 121 | self.BLOCKS_PER_EPOCH = self.BLOCKS_PER_EON.div(4); 122 | self.EXTENDED_BLOCKS_PER_EPOCH = self.BLOCKS_PER_EON.div(3); 123 | self.EONS_KEPT = 5; // eons kept on chain 124 | self.DEPOSITS_KEPT = 3; // deposit aggregates kept on chain 125 | self.MIN_CHALLENGE_GAS_COST = 0.005 szabo; // 5 gwei minimum gas reimbursement cost 126 | self.operator = operator; 127 | self.genesis = block.number; 128 | } 129 | 130 | // DATA ACCESS 131 | /** 132 | * This method calculates the current eon number using the genesis block number 133 | * and eon duration. 134 | */ 135 | function currentEon( 136 | Ledger storage self 137 | ) 138 | public 139 | view 140 | returns (uint256) 141 | { 142 | return block.number.sub(self.genesis).div(self.BLOCKS_PER_EON).add(1); 143 | } 144 | 145 | /** 146 | * This method calculates the current era number 147 | */ 148 | function currentEra( 149 | Ledger storage self 150 | ) 151 | public 152 | view 153 | returns (uint256) 154 | { 155 | return block.number.sub(self.genesis).mod(self.BLOCKS_PER_EON); 156 | } 157 | 158 | /** 159 | * This method is used to embed a parent-chain operation into the accumulator 160 | * through hashing its values. The on-chain accumulator is used to provide a 161 | * reference with respect to which the operator can commit checkpoints. 162 | */ 163 | function appendOperationToEonAccumulator( 164 | Ledger storage self, 165 | uint256 eon, 166 | ERC20 token, 167 | address participant, 168 | Operation operation, 169 | uint256 value 170 | ) 171 | public 172 | { 173 | self.parentChainAccumulator[eon.mod(self.EONS_KEPT)] = keccak256(abi.encodePacked( 174 | self.parentChainAccumulator[eon.mod(self.EONS_KEPT)], 175 | eon, 176 | token, 177 | participant, 178 | operation, 179 | value)); 180 | } 181 | 182 | /** 183 | * Retrieves the total pending withdrawal amount at a specific eon. 184 | */ 185 | function getPendingWithdrawalsAtEon( 186 | Ledger storage self, 187 | ERC20 token, 188 | uint256 eon 189 | ) 190 | public 191 | view 192 | returns (uint256) 193 | { 194 | uint256 lastAggregateEon = 0; 195 | for (uint256 i = 0; i < self.EONS_KEPT; i++) { 196 | AmountAggregate storage currentAggregate = self.pendingWithdrawals[token][eon.mod(self.EONS_KEPT)]; 197 | if (currentAggregate.eon == eon) { 198 | return currentAggregate.amount; 199 | } else if (currentAggregate.eon > lastAggregateEon && currentAggregate.eon < eon) { 200 | // As this is a running aggregate value, if the target eon value is not set, 201 | // the most recent value is provided and assumed to have remained constant. 202 | lastAggregateEon = currentAggregate.eon; 203 | } 204 | if (eon == 0) { 205 | break; 206 | } 207 | eon = eon.sub(1); 208 | } 209 | if (lastAggregateEon == 0) { 210 | return 0; 211 | } 212 | return self.pendingWithdrawals[token][lastAggregateEon.mod(self.EONS_KEPT)].amount; 213 | } 214 | 215 | /** 216 | * Increases the total pending withdrawal amount at a specific eon. 217 | */ 218 | function addToRunningPendingWithdrawals( 219 | Ledger storage self, 220 | ERC20 token, 221 | uint256 eon, 222 | uint256 value 223 | ) 224 | public 225 | { 226 | AmountAggregate storage aggregate = self.pendingWithdrawals[token][eon.mod(self.EONS_KEPT)]; 227 | // As this is a running aggregate, the target eon and all those that 228 | // come after it are updated to reflect the increase. 229 | if (aggregate.eon < eon) { // implies eon > 0 230 | aggregate.amount = getPendingWithdrawalsAtEon(self, token, eon.sub(1)).add(value); 231 | aggregate.eon = eon; 232 | } else { 233 | aggregate.amount = aggregate.amount.add(value); 234 | } 235 | } 236 | 237 | /** 238 | * Decreases the total pending withdrawal amount at a specific eon. 239 | */ 240 | function deductFromRunningPendingWithdrawals( 241 | Ledger storage self, 242 | ERC20 token, 243 | uint256 eon, 244 | uint256 latestEon, 245 | uint256 value 246 | ) 247 | public 248 | { 249 | /* Initalize empty aggregates to running values */ 250 | for (uint256 i = 0; i < self.EONS_KEPT; i++) { 251 | uint256 targetEon = eon.add(i); 252 | AmountAggregate storage aggregate = self.pendingWithdrawals[token][targetEon.mod(self.EONS_KEPT)]; 253 | if (targetEon > latestEon) { 254 | break; 255 | } else if (aggregate.eon < targetEon) { // implies targetEon > 0 256 | // Set constant running value 257 | aggregate.eon = targetEon; 258 | aggregate.amount = getPendingWithdrawalsAtEon(self, token, targetEon.sub(1)); 259 | } 260 | } 261 | /* Update running values */ 262 | for (i = 0; i < self.EONS_KEPT; i++) { 263 | targetEon = eon.add(i); 264 | aggregate = self.pendingWithdrawals[token][targetEon.mod(self.EONS_KEPT)]; 265 | if (targetEon > latestEon) { 266 | break; 267 | } else if (aggregate.eon < targetEon) { 268 | revert('X'); // This is impossible. 269 | } else { 270 | aggregate.amount = aggregate.amount.sub(value); 271 | } 272 | } 273 | } 274 | 275 | /** 276 | * Get the total number of live challenges for a specific eon. 277 | */ 278 | function getLiveChallenges( 279 | Ledger storage self, 280 | uint256 eon 281 | ) 282 | public 283 | view 284 | returns (uint) 285 | { 286 | Checkpoint storage checkpoint = self.checkpoints[eon.mod(self.EONS_KEPT)]; 287 | if (checkpoint.eonNumber != eon) { 288 | return 0; 289 | } 290 | return checkpoint.liveChallenges; 291 | } 292 | 293 | /** 294 | * Get checkpoint data or assume it to be empty if non-existant. 295 | */ 296 | function getOrCreateCheckpoint( 297 | Ledger storage self, 298 | uint256 targetEon, 299 | uint256 latestEon 300 | ) 301 | public 302 | returns (Checkpoint storage checkpoint) 303 | { 304 | require(latestEon < targetEon.add(self.EONS_KEPT) && targetEon <= latestEon); 305 | 306 | uint256 index = targetEon.mod(self.EONS_KEPT); 307 | checkpoint = self.checkpoints[index]; 308 | 309 | if (checkpoint.eonNumber != targetEon) { 310 | checkpoint.eonNumber = targetEon; 311 | checkpoint.merkleRoot = bytes32(0); 312 | checkpoint.liveChallenges = 0; 313 | } 314 | 315 | return checkpoint; 316 | } 317 | 318 | /** 319 | * Get the total amount pending withdrawal by a wallet at a specific eon. 320 | */ 321 | function getWalletPendingWithdrawalAmountAtEon( 322 | Ledger storage self, 323 | ERC20 token, 324 | address holder, 325 | uint256 eon 326 | ) 327 | public 328 | view 329 | returns (uint256 amount) 330 | { 331 | amount = 0; 332 | 333 | Wallet storage accountingEntry = self.walletBook[token][holder]; 334 | Withdrawal[] storage withdrawals = accountingEntry.withdrawals; 335 | for (uint32 i = 0; i < withdrawals.length; i++) { 336 | Withdrawal storage withdrawal = withdrawals[i]; 337 | if (withdrawal.eon == eon) { 338 | amount = amount.add(withdrawal.amount); 339 | } else if (withdrawal.eon > eon) { 340 | break; 341 | } 342 | } 343 | } 344 | 345 | /** 346 | * Get the total amounts deposited and pending withdrawal at the current eon. 347 | */ 348 | function getCurrentEonDepositsWithdrawals( 349 | Ledger storage self, 350 | ERC20 token, 351 | address holder 352 | ) 353 | public 354 | view 355 | returns (uint256 currentEonDeposits, uint256 currentEonWithdrawals) 356 | { 357 | 358 | currentEonDeposits = 0; 359 | currentEonWithdrawals = 0; 360 | 361 | Wallet storage accountingEntry = self.walletBook[token][holder]; 362 | Challenge storage challengeEntry = self.challengeBook[token][holder][holder]; 363 | 364 | AmountAggregate storage depositEntry = 365 | accountingEntry.depositsKept[challengeEntry.initialStateEon.mod(self.DEPOSITS_KEPT)]; 366 | 367 | if (depositEntry.eon == challengeEntry.initialStateEon) { 368 | currentEonDeposits = currentEonDeposits.add(depositEntry.amount); 369 | } 370 | 371 | currentEonWithdrawals = getWalletPendingWithdrawalAmountAtEon(self, token, holder, challengeEntry.initialStateEon); 372 | 373 | return (currentEonDeposits, currentEonWithdrawals); 374 | } 375 | 376 | // UTILITY 377 | function addToAggregate( 378 | AmountAggregate storage aggregate, 379 | uint256 eon, 380 | uint256 value 381 | ) 382 | public 383 | { 384 | if (eon > aggregate.eon) { 385 | aggregate.eon = eon; 386 | aggregate.amount = value; 387 | } else { 388 | aggregate.amount = aggregate.amount.add(value); 389 | } 390 | } 391 | 392 | function clearAggregate( 393 | AmountAggregate storage aggregate 394 | ) 395 | public 396 | { 397 | aggregate.eon = 0; 398 | aggregate.amount = 0; 399 | } 400 | 401 | function signedMessageECRECOVER( 402 | bytes32 message, 403 | bytes32 r, bytes32 s, uint8 v 404 | ) 405 | public 406 | pure 407 | returns (address) 408 | { 409 | return ecrecover( 410 | keccak256(abi.encodePacked( 411 | "\x19Ethereum Signed Message:\n32", 412 | keccak256(abi.encodePacked( 413 | "\x19Liquidity.Network Authorization:\n32", 414 | message)))), 415 | v, r, s); 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /contracts/BimodalProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./ERC20.sol"; 4 | import "./BimodalLib.sol"; 5 | import "./SafeMath/SafeMathLib256.sol"; 6 | 7 | contract BimodalProxy { 8 | using SafeMathLib256 for uint256; 9 | using BimodalLib for BimodalLib.Ledger; 10 | 11 | // EVENTS 12 | event CheckpointSubmission(uint256 indexed eon, bytes32 merkleRoot); 13 | 14 | event Deposit(address indexed token, address indexed recipient, uint256 amount); 15 | 16 | event WithdrawalRequest(address indexed token, address indexed requestor, uint256 amount); 17 | 18 | event WithdrawalConfirmation(address indexed token, address indexed requestor, uint256 amount); 19 | 20 | event ChallengeIssued(address indexed token, address indexed recipient, address indexed sender); 21 | 22 | event StateUpdate( 23 | address indexed token, 24 | address indexed account, 25 | uint256 indexed eon, 26 | uint64 trail, 27 | bytes32[] allotmentChain, 28 | bytes32[] membershipChain, 29 | uint256[] values, 30 | uint256[2][3] lrDeltasPassiveMark, 31 | bytes32 activeStateChecksum, 32 | bytes32 passiveChecksum, 33 | bytes32 r, bytes32 s, uint8 v); 34 | 35 | // BIMODAL LEDGER DATA 36 | BimodalLib.Ledger internal ledger; 37 | 38 | // INITIALIZATION 39 | constructor( 40 | uint256 blocksPerEon, 41 | address operator 42 | ) 43 | public 44 | { 45 | ledger.init(blocksPerEon, operator); 46 | } 47 | 48 | // SAFETY MODIFIERS 49 | modifier onlyOperator() { 50 | require(msg.sender == ledger.operator); 51 | _; 52 | } 53 | 54 | modifier onlyWhenContractUnpunished() { 55 | require( 56 | !hasOutstandingChallenges() && !hasMissedCheckpointSubmission(), 57 | 'p'); 58 | _; 59 | } 60 | 61 | // PUBLIC DATA EXPOSURE 62 | function getClientContractStateVariables( 63 | ERC20 token, 64 | address holder 65 | ) 66 | public 67 | view 68 | returns ( 69 | uint256 latestCheckpointEonNumber, 70 | bytes32[5] latestCheckpointsMerkleRoots, 71 | uint256[5] latestCheckpointsLiveChallenges, 72 | uint256 currentEonDeposits, 73 | uint256 previousEonDeposits, 74 | uint256 secondPreviousEonDeposits, 75 | uint256[2][] pendingWithdrawals, 76 | uint256 holderBalance 77 | ) 78 | { 79 | latestCheckpointEonNumber = ledger.lastSubmissionEon; 80 | for (uint32 i = 0; i < ledger.EONS_KEPT && i < ledger.currentEon(); i++) { 81 | BimodalLib.Checkpoint storage checkpoint = 82 | ledger.checkpoints[ledger.lastSubmissionEon.sub(i).mod(ledger.EONS_KEPT)]; 83 | latestCheckpointsMerkleRoots[i] = checkpoint.merkleRoot; 84 | latestCheckpointsLiveChallenges[i] = checkpoint.liveChallenges; 85 | } 86 | 87 | holderBalance = ledger.currentEon(); 88 | currentEonDeposits = getDepositsAtEon(token, holder, holderBalance); 89 | if (holderBalance > 1) { 90 | previousEonDeposits = getDepositsAtEon(token, holder, holderBalance - 1); 91 | } 92 | if (holderBalance > 2) { 93 | secondPreviousEonDeposits = getDepositsAtEon(token, holder, holderBalance - 2); 94 | } 95 | BimodalLib.Wallet storage wallet = ledger.walletBook[token][holder]; 96 | pendingWithdrawals = new uint256[2][](wallet.withdrawals.length); 97 | for (i = 0; i < wallet.withdrawals.length; i++) { 98 | BimodalLib.Withdrawal storage withdrawal = wallet.withdrawals[i]; 99 | pendingWithdrawals[i] = [withdrawal.eon, withdrawal.amount]; 100 | } 101 | holderBalance = token != address(this) ? token.balanceOf(holder) : holder.balance; 102 | } 103 | 104 | function getServerContractStateVariables() 105 | public 106 | view 107 | returns ( 108 | bytes32 parentChainAccumulator, 109 | uint256 lastSubmissionEon, 110 | bytes32 lastCheckpointRoot, 111 | bool isCheckpointSubmitted, 112 | bool missedCheckpointSubmission, 113 | uint256 liveChallenges 114 | ) 115 | { 116 | uint256 currentEon = ledger.currentEon(); 117 | parentChainAccumulator = getParentChainAccumulatorAtSlot(uint8(currentEon.mod(ledger.EONS_KEPT))); 118 | 119 | BimodalLib.Checkpoint storage lastCheckpoint = ledger.checkpoints[ledger.lastSubmissionEon.mod(ledger.EONS_KEPT)]; 120 | lastSubmissionEon = ledger.lastSubmissionEon; 121 | lastCheckpointRoot = lastCheckpoint.merkleRoot; 122 | 123 | isCheckpointSubmitted = lastSubmissionEon == currentEon; 124 | missedCheckpointSubmission = hasMissedCheckpointSubmission(); 125 | 126 | liveChallenges = getLiveChallenges(currentEon); 127 | } 128 | 129 | function getServerContractLedgerStateVariables( 130 | uint256 eonNumber, 131 | ERC20 token 132 | ) 133 | public 134 | view 135 | returns ( 136 | uint256 pendingWithdrawals, 137 | uint256 confirmedWithdrawals, 138 | uint256 deposits, 139 | uint256 totalBalance 140 | ) 141 | { 142 | uint8 eonSlot = uint8(eonNumber.mod(ledger.EONS_KEPT)); 143 | uint256 targetEon = 0; 144 | (targetEon, pendingWithdrawals) = getPendingWithdrawalsAtSlot(token, eonSlot); 145 | if (targetEon != eonNumber) { 146 | pendingWithdrawals = 0; 147 | } 148 | (targetEon, confirmedWithdrawals) = getConfirmedWithdrawalsAtSlot(token, eonSlot); 149 | if (targetEon != eonNumber) { 150 | confirmedWithdrawals = 0; 151 | } 152 | (targetEon, deposits) = getDepositsAtSlot(token, eonSlot); 153 | if (targetEon != eonNumber) { 154 | deposits = 0; 155 | } 156 | // totalBalance is for current state and not for eonNumber, which is stange 157 | totalBalance = token != address(this) ? token.balanceOf(this) : address(this).balance; 158 | } 159 | 160 | function hasOutstandingChallenges() 161 | public 162 | view 163 | returns (bool) 164 | { 165 | return ledger.getLiveChallenges(ledger.currentEon().sub(1)) > 0 166 | && ledger.currentEra() > ledger.BLOCKS_PER_EPOCH; 167 | } 168 | 169 | function hasMissedCheckpointSubmission() 170 | public 171 | view 172 | returns (bool) 173 | { 174 | return ledger.currentEon().sub(ledger.lastSubmissionEon) > 1; 175 | } 176 | 177 | function getCheckpointAtSlot( 178 | uint8 slot 179 | ) 180 | public 181 | view 182 | returns ( 183 | uint256, 184 | bytes32, 185 | uint256 186 | ) 187 | { 188 | BimodalLib.Checkpoint storage checkpoint = ledger.checkpoints[slot]; 189 | return ( 190 | checkpoint.eonNumber, 191 | checkpoint.merkleRoot, 192 | checkpoint.liveChallenges 193 | ); 194 | } 195 | 196 | function getParentChainAccumulatorAtSlot( 197 | uint8 slot 198 | ) 199 | public 200 | view 201 | returns (bytes32) 202 | { 203 | return ledger.parentChainAccumulator[slot]; 204 | } 205 | 206 | function getChallenge( 207 | ERC20 token, 208 | address sender, 209 | address recipient 210 | ) 211 | public 212 | view 213 | returns ( 214 | BimodalLib.ChallengeType, 215 | uint256, 216 | uint, 217 | uint256, 218 | uint256, 219 | uint256, 220 | uint256, 221 | uint256, 222 | uint64 223 | ) 224 | { 225 | BimodalLib.Challenge storage challenge = ledger.challengeBook[token][recipient][sender]; 226 | return ( 227 | challenge.challengeType, 228 | challenge.block, 229 | challenge.initialStateEon, 230 | challenge.initialStateBalance, 231 | challenge.deltaHighestSpendings, 232 | challenge.deltaHighestGains, 233 | challenge.finalStateBalance, 234 | challenge.deliveredTxNonce, 235 | challenge.trailIdentifier); 236 | } 237 | 238 | function getIsWalletRecovered( 239 | ERC20 token, 240 | address holder 241 | ) 242 | public 243 | view 244 | returns ( 245 | bool 246 | ) 247 | { 248 | BimodalLib.Wallet storage wallet = ledger.walletBook[token][holder]; 249 | return ( 250 | wallet.recovered); 251 | } 252 | 253 | function getDepositsAtEon( 254 | ERC20 token, 255 | address addr, 256 | uint256 eon 257 | ) 258 | public 259 | view 260 | returns (uint256) 261 | { 262 | (uint256 aggregateEon, uint256 aggregateAmount) = 263 | getWalletDepositAggregateAtSlot(token, addr, uint8(eon.mod(ledger.DEPOSITS_KEPT))); 264 | return aggregateEon == eon ? aggregateAmount : 0; 265 | } 266 | 267 | function getDepositsAtSlot( 268 | ERC20 token, 269 | uint8 slot 270 | ) 271 | public 272 | view 273 | returns (uint, uint) 274 | { 275 | BimodalLib.AmountAggregate storage aggregate = ledger.deposits[token][slot]; 276 | return ( 277 | aggregate.eon, 278 | aggregate.amount); 279 | } 280 | 281 | function getWalletDepositAggregateAtSlot( 282 | ERC20 token, 283 | address addr, 284 | uint8 slot 285 | ) 286 | public 287 | view 288 | returns (uint, uint) 289 | { 290 | BimodalLib.AmountAggregate memory deposit = ledger.walletBook[token][addr].depositsKept[slot]; 291 | return ( 292 | deposit.eon, 293 | deposit.amount); 294 | } 295 | 296 | function getPendingWithdrawalsAtEon( 297 | ERC20 token, 298 | uint256 eon 299 | ) 300 | public 301 | view 302 | returns (uint) 303 | { 304 | return ledger.getPendingWithdrawalsAtEon(token, eon); 305 | } 306 | 307 | function getPendingWithdrawalsAtSlot( 308 | ERC20 token, 309 | uint8 slot 310 | ) 311 | public 312 | view 313 | returns (uint, uint) 314 | { 315 | BimodalLib.AmountAggregate storage aggregate = ledger.pendingWithdrawals[token][slot]; 316 | return ( 317 | aggregate.eon, 318 | aggregate.amount); 319 | } 320 | 321 | function getConfirmedWithdrawalsAtSlot( 322 | ERC20 token, 323 | uint8 slot 324 | ) 325 | public 326 | view 327 | returns (uint, uint) 328 | { 329 | BimodalLib.AmountAggregate storage aggregate = ledger.confirmedWithdrawals[token][slot]; 330 | return ( 331 | aggregate.eon, 332 | aggregate.amount); 333 | } 334 | 335 | function getWalletPendingWithdrawalAmountAtEon( 336 | ERC20 token, 337 | address holder, 338 | uint256 eon 339 | ) 340 | public 341 | view 342 | returns (uint256) 343 | { 344 | return ledger.getWalletPendingWithdrawalAmountAtEon(token, holder, eon); 345 | } 346 | 347 | function getTokenTrail( 348 | ERC20 token 349 | ) 350 | public 351 | view 352 | returns (uint64) 353 | { 354 | return ledger.tokenToTrail[token]; 355 | } 356 | 357 | function getTokenAtTrail( 358 | uint64 trail 359 | ) 360 | public 361 | view 362 | returns (address) 363 | { 364 | return ledger.trailToToken[trail]; 365 | } 366 | 367 | function getCurrentEonDepositsWithdrawals( 368 | ERC20 token, 369 | address holder 370 | ) 371 | public 372 | view 373 | returns (uint256 currentEonDeposits, uint256 currentEonWithdrawals) 374 | { 375 | return ledger.getCurrentEonDepositsWithdrawals(token, holder); 376 | } 377 | 378 | function EONS_KEPT() // solhint-disable-line func-name-mixedcase 379 | public 380 | view 381 | returns (uint8) 382 | { 383 | return ledger.EONS_KEPT; 384 | } 385 | 386 | function DEPOSITS_KEPT() // solhint-disable-line func-name-mixedcase 387 | public 388 | view 389 | returns (uint8) 390 | { 391 | return ledger.DEPOSITS_KEPT; 392 | } 393 | 394 | function MIN_CHALLENGE_GAS_COST() // solhint-disable-line func-name-mixedcase 395 | public 396 | view 397 | returns (uint) 398 | { 399 | return ledger.MIN_CHALLENGE_GAS_COST; 400 | } 401 | 402 | function BLOCKS_PER_EON() // solhint-disable-line func-name-mixedcase 403 | public 404 | view 405 | returns (uint) 406 | { 407 | return ledger.BLOCKS_PER_EON; 408 | } 409 | 410 | function BLOCKS_PER_EPOCH() // solhint-disable-line func-name-mixedcase 411 | public 412 | view 413 | returns (uint) 414 | { 415 | return ledger.BLOCKS_PER_EPOCH; 416 | } 417 | 418 | function EXTENDED_BLOCKS_PER_EPOCH() // solhint-disable-line func-name-mixedcase 419 | public 420 | view 421 | returns (uint) 422 | { 423 | return ledger.EXTENDED_BLOCKS_PER_EPOCH; 424 | } 425 | 426 | function genesis() 427 | public 428 | view 429 | returns (uint) 430 | { 431 | return ledger.genesis; 432 | } 433 | 434 | function operator() 435 | public 436 | view 437 | returns (address) 438 | { 439 | return ledger.operator; 440 | } 441 | 442 | function lastSubmissionEon() 443 | public 444 | view 445 | returns (uint) 446 | { 447 | return ledger.lastSubmissionEon; 448 | } 449 | 450 | function currentEon() 451 | public 452 | view 453 | returns (uint) 454 | { 455 | return ledger.currentEon(); 456 | } 457 | 458 | function currentEra() 459 | public 460 | view 461 | returns (uint) 462 | { 463 | return ledger.currentEra(); 464 | } 465 | 466 | function getLiveChallenges(uint256 eon) 467 | public 468 | view 469 | returns (uint) 470 | { 471 | BimodalLib.Checkpoint storage checkpoint = ledger.checkpoints[eon.mod(ledger.EONS_KEPT)]; 472 | if (checkpoint.eonNumber != eon) { 473 | return 0; 474 | } 475 | return checkpoint.liveChallenges; 476 | } 477 | 478 | function signedMessageECRECOVER( 479 | bytes32 message, 480 | bytes32 r, bytes32 s, uint8 v 481 | ) 482 | public 483 | pure 484 | returns (address) 485 | { 486 | return BimodalLib.signedMessageECRECOVER(message, r, s, v); 487 | } 488 | } 489 | -------------------------------------------------------------------------------- /contracts/ChallengeLib.sol: -------------------------------------------------------------------------------- 1 | /* solhint-disable func-order */ 2 | 3 | pragma solidity ^0.4.24; 4 | 5 | import "./BimodalLib.sol"; 6 | import "./MerkleVerifier.sol"; 7 | import "./SafeMath/SafeMathLib32.sol"; 8 | import "./SafeMath/SafeMathLib256.sol"; 9 | 10 | /** 11 | * This library contains the challenge-response implementations of NOCUST. 12 | */ 13 | library ChallengeLib { 14 | using SafeMathLib256 for uint256; 15 | using SafeMathLib32 for uint32; 16 | using BimodalLib for BimodalLib.Ledger; 17 | // EVENTS 18 | event ChallengeIssued(address indexed token, address indexed recipient, address indexed sender); 19 | 20 | event StateUpdate( 21 | address indexed token, 22 | address indexed account, 23 | uint256 indexed eon, 24 | uint64 trail, 25 | bytes32[] allotmentChain, 26 | bytes32[] membershipChain, 27 | uint256[] values, 28 | uint256[2][3] lrDeltasPassiveMark, 29 | bytes32 activeStateChecksum, 30 | bytes32 passiveChecksum, 31 | bytes32 r, bytes32 s, uint8 v 32 | ); 33 | 34 | // Validation 35 | function verifyProofOfExclusiveAccountBalanceAllotment( 36 | BimodalLib.Ledger storage ledger, 37 | ERC20 token, 38 | address holder, 39 | bytes32[2] activeStateChecksum_passiveTransfersRoot, // solhint-disable func-param-name-mixedcase 40 | uint64 trail, 41 | uint256[3] eonPassiveMark, 42 | bytes32[] allotmentChain, 43 | bytes32[] membershipChain, 44 | uint256[] values, 45 | uint256[2] LR // solhint-disable func-param-name-mixedcase 46 | ) 47 | public 48 | view 49 | returns (bool) 50 | { 51 | BimodalLib.Checkpoint memory checkpoint = ledger.checkpoints[eonPassiveMark[0].mod(ledger.EONS_KEPT)]; 52 | require(eonPassiveMark[0] == checkpoint.eonNumber, 'r'); 53 | 54 | // activeStateChecksum is set to the account node. 55 | activeStateChecksum_passiveTransfersRoot[0] = keccak256(abi.encodePacked( 56 | keccak256(abi.encodePacked(address(this))), 57 | keccak256(abi.encodePacked(token)), 58 | keccak256(abi.encodePacked(holder)), 59 | keccak256(abi.encodePacked( 60 | activeStateChecksum_passiveTransfersRoot[1], // passiveTransfersRoot 61 | eonPassiveMark[1], 62 | eonPassiveMark[2])), 63 | activeStateChecksum_passiveTransfersRoot[0] // activeStateChecksum 64 | )); 65 | // the interval allotment is set to form the leaf 66 | activeStateChecksum_passiveTransfersRoot[0] = keccak256(abi.encodePacked( 67 | LR[0], activeStateChecksum_passiveTransfersRoot[0], LR[1] 68 | )); 69 | 70 | // This calls the merkle verification procedure, which returns the 71 | // checkpoint allotment size 72 | uint64 tokenTrail = ledger.tokenToTrail[token]; 73 | LR[0] = MerkleVerifier.verifyProofOfExclusiveBalanceAllotment( 74 | trail, 75 | tokenTrail, 76 | activeStateChecksum_passiveTransfersRoot[0], 77 | checkpoint.merkleRoot, 78 | allotmentChain, 79 | membershipChain, 80 | values, 81 | LR); 82 | 83 | // The previous allotment size of the target eon is reconstructed from the 84 | // deposits and withdrawals performed so far and the current balance. 85 | LR[1] = address(this).balance; 86 | 87 | if (token != address(this)) { 88 | require( 89 | tokenTrail != 0, 90 | 't'); 91 | LR[1] = token.balanceOf(this); 92 | } 93 | 94 | // Credit back confirmed withdrawals that were performed since target eon 95 | for (tokenTrail = 0; tokenTrail < ledger.EONS_KEPT; tokenTrail++) { 96 | if (ledger.confirmedWithdrawals[token][tokenTrail].eon >= eonPassiveMark[0]) { 97 | LR[1] = LR[1].add(ledger.confirmedWithdrawals[token][tokenTrail].amount); 98 | } 99 | } 100 | // Debit deposits performed since target eon 101 | for (tokenTrail = 0; tokenTrail < ledger.EONS_KEPT; tokenTrail++) { 102 | if (ledger.deposits[token][tokenTrail].eon >= eonPassiveMark[0]) { 103 | LR[1] = LR[1].sub(ledger.deposits[token][tokenTrail].amount); 104 | } 105 | } 106 | // Debit withdrawals pending since prior eon 107 | LR[1] = LR[1].sub(ledger.getPendingWithdrawalsAtEon(token, eonPassiveMark[0].sub(1))); 108 | // Require that the reconstructed allotment matches the proof allotment 109 | require( 110 | LR[0] <= LR[1], 111 | 'b'); 112 | 113 | return true; 114 | } 115 | 116 | function verifyProofOfActiveStateUpdateAgreement( 117 | ERC20 token, 118 | address holder, 119 | uint64 trail, 120 | uint256 eon, 121 | bytes32 txSetRoot, 122 | uint256[2] deltas, 123 | address attester, 124 | bytes32 r, 125 | bytes32 s, 126 | uint8 v 127 | ) 128 | public 129 | view 130 | returns (bytes32 checksum) 131 | { 132 | checksum = MerkleVerifier.activeStateUpdateChecksum(token, holder, trail, eon, txSetRoot, deltas); 133 | require(attester == BimodalLib.signedMessageECRECOVER(checksum, r, s, v), 'A'); 134 | } 135 | 136 | function verifyWithdrawalAuthorization( 137 | ERC20 token, 138 | address holder, 139 | uint256 expiry, 140 | uint256 amount, 141 | address attester, 142 | bytes32 r, 143 | bytes32 s, 144 | uint8 v 145 | ) 146 | public 147 | view 148 | returns (bool) 149 | { 150 | bytes32 checksum = keccak256(abi.encodePacked( 151 | keccak256(abi.encodePacked(address(this))), 152 | keccak256(abi.encodePacked(token)), 153 | keccak256(abi.encodePacked(holder)), 154 | expiry, 155 | amount)); 156 | require(attester == BimodalLib.signedMessageECRECOVER(checksum, r, s, v), 'a'); 157 | return true; 158 | } 159 | 160 | // Challenge Lifecycle Methods 161 | /** 162 | * This method increments the live challenge counter and emits and event 163 | * containing the challenge index. 164 | */ 165 | function markChallengeLive( 166 | BimodalLib.Ledger storage ledger, 167 | ERC20 token, 168 | address recipient, 169 | address sender 170 | ) 171 | private 172 | { 173 | require(ledger.currentEra() > ledger.BLOCKS_PER_EPOCH); 174 | 175 | uint256 eon = ledger.currentEon(); 176 | BimodalLib.Checkpoint storage checkpoint = ledger.getOrCreateCheckpoint(eon, eon); 177 | checkpoint.liveChallenges = checkpoint.liveChallenges.add(1); 178 | emit ChallengeIssued(token, recipient, sender); 179 | } 180 | 181 | /** 182 | * This method clears all the data in a Challenge structure and decrements the 183 | * live challenge counter. 184 | */ 185 | function clearChallenge( 186 | BimodalLib.Ledger storage ledger, 187 | BimodalLib.Challenge storage challenge 188 | ) 189 | private 190 | { 191 | BimodalLib.Checkpoint storage checkpoint = ledger.getOrCreateCheckpoint( 192 | challenge.initialStateEon.add(1), 193 | ledger.currentEon()); 194 | checkpoint.liveChallenges = checkpoint.liveChallenges.sub(1); 195 | 196 | challenge.challengeType = BimodalLib.ChallengeType.NONE; 197 | challenge.block = 0; 198 | // challenge.initialStateEon = 0; 199 | challenge.initialStateBalance = 0; 200 | challenge.deltaHighestSpendings = 0; 201 | challenge.deltaHighestGains = 0; 202 | challenge.finalStateBalance = 0; 203 | challenge.deliveredTxNonce = 0; 204 | challenge.trailIdentifier = 0; 205 | } 206 | 207 | /** 208 | * This method marks a challenge as having been successfully answered only if 209 | * the response was provided in time. 210 | */ 211 | function markChallengeAnswered( 212 | BimodalLib.Ledger storage ledger, 213 | BimodalLib.Challenge storage challenge 214 | ) 215 | private 216 | { 217 | uint256 eon = ledger.currentEon(); 218 | 219 | require( 220 | challenge.challengeType != BimodalLib.ChallengeType.NONE && 221 | block.number.sub(challenge.block) < ledger.BLOCKS_PER_EPOCH && 222 | ( 223 | challenge.initialStateEon == eon.sub(1) || 224 | (challenge.initialStateEon == eon.sub(2) && ledger.currentEra() < ledger.BLOCKS_PER_EPOCH) 225 | ) 226 | ); 227 | 228 | clearChallenge(ledger, challenge); 229 | } 230 | 231 | // ======================================================================== 232 | // ======================================================================== 233 | // ======================================================================== 234 | // ==================================== STATE UPDATE Challenge 235 | // ======================================================================== 236 | // ======================================================================== 237 | // ======================================================================== 238 | /** 239 | * This method initiates the fields of the Challenge struct to hold a state 240 | * update challenge. 241 | */ 242 | function initStateUpdateChallenge( 243 | BimodalLib.Ledger storage ledger, 244 | ERC20 token, 245 | uint256 owed, 246 | uint256[2] spentGained, 247 | uint64 trail 248 | ) 249 | private 250 | { 251 | BimodalLib.Challenge storage challengeEntry = ledger.challengeBook[token][msg.sender][msg.sender]; 252 | require(challengeEntry.challengeType == BimodalLib.ChallengeType.NONE); 253 | require(challengeEntry.initialStateEon < ledger.currentEon().sub(1)); 254 | 255 | challengeEntry.initialStateEon = ledger.currentEon().sub(1); 256 | challengeEntry.initialStateBalance = owed; 257 | challengeEntry.deltaHighestSpendings = spentGained[0]; 258 | challengeEntry.deltaHighestGains = spentGained[1]; 259 | challengeEntry.trailIdentifier = trail; 260 | 261 | challengeEntry.challengeType = BimodalLib.ChallengeType.STATE_UPDATE; 262 | challengeEntry.block = block.number; 263 | 264 | markChallengeLive(ledger, token, msg.sender, msg.sender); 265 | } 266 | 267 | /** 268 | * This method checks that the updated balance is at least as much as the 269 | * expected balance. 270 | */ 271 | function checkStateUpdateBalance( 272 | BimodalLib.Ledger storage ledger, 273 | ERC20 token, 274 | BimodalLib.Challenge storage challenge, 275 | uint256[2] LR, // solhint-disable func-param-name-mixedcase 276 | uint256[2] spentGained, 277 | uint256 passivelyReceived 278 | ) 279 | private 280 | view 281 | { 282 | (uint256 deposits, uint256 withdrawals) = ledger.getCurrentEonDepositsWithdrawals(token, msg.sender); 283 | uint256 incoming = spentGained[1] // actively received in commit chain 284 | .add(deposits) 285 | .add(passivelyReceived); 286 | uint256 outgoing = spentGained[0] // actively spent in commit chain 287 | .add(withdrawals); 288 | // This verification is modified to permit underflow of expected balance 289 | // since a client can choose to zero the `challenge.initialStateBalance` 290 | require( 291 | challenge.initialStateBalance 292 | .add(incoming) 293 | <= 294 | LR[1].sub(LR[0]) // final balance allotment 295 | .add(outgoing) 296 | , 297 | 'B'); 298 | } 299 | 300 | function challengeStateUpdateWithProofOfExclusiveBalanceAllotment( 301 | BimodalLib.Ledger storage ledger, 302 | ERC20 token, 303 | bytes32[2] checksums, 304 | uint64 trail, 305 | bytes32[] allotmentChain, 306 | bytes32[] membershipChain, 307 | uint256[] value, 308 | uint256[2][3] lrDeltasPassiveMark, 309 | bytes32[3] rsTxSetRoot, 310 | uint8 v 311 | ) 312 | public 313 | /* payable */ 314 | /* onlyWithFairReimbursement(ledger) */ 315 | { 316 | uint256 previousEon = ledger.currentEon().sub(1); 317 | address operator = ledger.operator; 318 | 319 | // The hub must have committed to this state update 320 | if (lrDeltasPassiveMark[1][0] != 0 || lrDeltasPassiveMark[1][1] != 0) { 321 | verifyProofOfActiveStateUpdateAgreement( 322 | token, 323 | msg.sender, 324 | trail, 325 | previousEon, 326 | rsTxSetRoot[2], 327 | lrDeltasPassiveMark[1], 328 | operator, 329 | rsTxSetRoot[0], rsTxSetRoot[1], v); 330 | } 331 | 332 | initStateUpdateChallenge( 333 | ledger, 334 | token, 335 | lrDeltasPassiveMark[0][1].sub(lrDeltasPassiveMark[0][0]), 336 | lrDeltasPassiveMark[1], 337 | trail); 338 | 339 | // The initial state must have been ratified in the commitment 340 | require(verifyProofOfExclusiveAccountBalanceAllotment( 341 | ledger, 342 | token, 343 | msg.sender, 344 | checksums, 345 | trail, 346 | [previousEon, lrDeltasPassiveMark[2][0], lrDeltasPassiveMark[2][1]], 347 | allotmentChain, 348 | membershipChain, 349 | value, 350 | lrDeltasPassiveMark[0])); 351 | } 352 | 353 | function challengeStateUpdateWithProofOfActiveStateUpdateAgreement( 354 | BimodalLib.Ledger storage ledger, 355 | ERC20 token, 356 | bytes32 txSetRoot, 357 | uint64 trail, 358 | uint256[2] deltas, 359 | bytes32 r, 360 | bytes32 s, 361 | uint8 v 362 | ) 363 | public 364 | /* payable */ 365 | /* TODO calculate exact addition */ 366 | /* onlyWithSkewedReimbursement(ledger, 25) */ 367 | { 368 | // The hub must have committed to this transition 369 | verifyProofOfActiveStateUpdateAgreement( 370 | token, 371 | msg.sender, 372 | trail, 373 | ledger.currentEon().sub(1), 374 | txSetRoot, 375 | deltas, 376 | ledger.operator, 377 | r, s, v); 378 | 379 | initStateUpdateChallenge(ledger, token, 0, deltas, trail); 380 | } 381 | 382 | function answerStateUpdateChallenge( 383 | BimodalLib.Ledger storage ledger, 384 | ERC20 token, 385 | address issuer, 386 | bytes32[] allotmentChain, 387 | bytes32[] membershipChain, 388 | uint256[] values, 389 | uint256[2][3] lrDeltasPassiveMark, // [ [L, R], Deltas ] 390 | bytes32[6] rSrStxSetRootChecksum, 391 | uint8[2] v 392 | ) 393 | public 394 | { 395 | BimodalLib.Challenge storage challenge = ledger.challengeBook[token][issuer][issuer]; 396 | require(challenge.challengeType == BimodalLib.ChallengeType.STATE_UPDATE); 397 | 398 | // Transition must have been approved by issuer 399 | if (lrDeltasPassiveMark[1][0] != 0 || lrDeltasPassiveMark[1][1] != 0) { 400 | rSrStxSetRootChecksum[0] = verifyProofOfActiveStateUpdateAgreement( 401 | token, 402 | issuer, 403 | challenge.trailIdentifier, 404 | challenge.initialStateEon, 405 | rSrStxSetRootChecksum[4], // txSetRoot 406 | lrDeltasPassiveMark[1], // deltas 407 | issuer, 408 | rSrStxSetRootChecksum[0], // R[0] 409 | rSrStxSetRootChecksum[1], // S[0] 410 | v[0]); 411 | address operator = ledger.operator; 412 | rSrStxSetRootChecksum[1] = verifyProofOfActiveStateUpdateAgreement( 413 | token, 414 | issuer, 415 | challenge.trailIdentifier, 416 | challenge.initialStateEon, 417 | rSrStxSetRootChecksum[4], // txSetRoot 418 | lrDeltasPassiveMark[1], // deltas 419 | operator, 420 | rSrStxSetRootChecksum[2], // R[1] 421 | rSrStxSetRootChecksum[3], // S[1] 422 | v[1]); 423 | require(rSrStxSetRootChecksum[0] == rSrStxSetRootChecksum[1], 'u'); 424 | } else { 425 | rSrStxSetRootChecksum[0] = bytes32(0); 426 | } 427 | 428 | // Transition has to be at least as recent as submitted one 429 | require( 430 | lrDeltasPassiveMark[1][0] >= challenge.deltaHighestSpendings && 431 | lrDeltasPassiveMark[1][1] >= challenge.deltaHighestGains, 432 | 'x'); 433 | 434 | // Transition has to have been properly applied 435 | checkStateUpdateBalance( 436 | ledger, 437 | token, 438 | challenge, 439 | lrDeltasPassiveMark[0], // LR 440 | lrDeltasPassiveMark[1], // deltas 441 | lrDeltasPassiveMark[2][0]); // passive amount 442 | 443 | // Truffle crashes when trying to interpret this event in some cases. 444 | emit StateUpdate( 445 | token, 446 | issuer, 447 | challenge.initialStateEon.add(1), 448 | challenge.trailIdentifier, 449 | allotmentChain, 450 | membershipChain, 451 | values, 452 | lrDeltasPassiveMark, 453 | rSrStxSetRootChecksum[0], // activeStateChecksum 454 | rSrStxSetRootChecksum[5], // passiveAcceptChecksum 455 | rSrStxSetRootChecksum[2], // R[1] 456 | rSrStxSetRootChecksum[3], // S[1] 457 | v[1]); 458 | 459 | // Proof of stake must be ratified in the checkpoint 460 | require(verifyProofOfExclusiveAccountBalanceAllotment( 461 | ledger, 462 | token, 463 | issuer, 464 | [rSrStxSetRootChecksum[0], rSrStxSetRootChecksum[5]], // activeStateChecksum, passiveAcceptChecksum 465 | challenge.trailIdentifier, 466 | [ 467 | challenge.initialStateEon.add(1), // eonNumber 468 | lrDeltasPassiveMark[2][0], // passiveAmount 469 | lrDeltasPassiveMark[2][1] 470 | ], 471 | allotmentChain, 472 | membershipChain, 473 | values, 474 | lrDeltasPassiveMark[0]), // LR 475 | 'c'); 476 | 477 | markChallengeAnswered(ledger, challenge); 478 | } 479 | 480 | // ======================================================================== 481 | // ======================================================================== 482 | // ======================================================================== 483 | // ==================================== ACTIVE DELIVERY Challenge 484 | // ======================================================================== 485 | // ======================================================================== 486 | // ======================================================================== 487 | function initTransferDeliveryChallenge( 488 | BimodalLib.Ledger storage ledger, 489 | ERC20 token, 490 | address sender, 491 | address recipient, 492 | uint256 amount, 493 | uint256 txNonce, 494 | uint64 trail 495 | ) 496 | private 497 | { 498 | BimodalLib.Challenge storage challenge = ledger.challengeBook[token][recipient][sender]; 499 | require(challenge.challengeType == BimodalLib.ChallengeType.NONE); 500 | require(challenge.initialStateEon < ledger.currentEon().sub(1)); 501 | 502 | challenge.challengeType = BimodalLib.ChallengeType.TRANSFER_DELIVERY; 503 | challenge.initialStateEon = ledger.currentEon().sub(1); 504 | challenge.deliveredTxNonce = txNonce; 505 | challenge.block = block.number; 506 | challenge.trailIdentifier = trail; 507 | challenge.finalStateBalance = amount; 508 | 509 | markChallengeLive( 510 | ledger, 511 | token, 512 | recipient, 513 | sender); 514 | } 515 | 516 | function challengeTransferDeliveryWithProofOfActiveStateUpdateAgreement( 517 | BimodalLib.Ledger storage ledger, 518 | ERC20 token, 519 | address[2] SR, // solhint-disable func-param-name-mixedcase 520 | uint256[2] nonceAmount, 521 | uint64[3] trails, 522 | bytes32[] chain, 523 | uint256[2] deltas, 524 | bytes32[3] rsTxSetRoot, 525 | uint8 v 526 | ) 527 | public 528 | /* payable */ 529 | /* onlyWithFairReimbursement() */ 530 | { 531 | require(msg.sender == SR[0] || msg.sender == SR[1], 'd'); 532 | 533 | // Require hub to have committed to transition 534 | verifyProofOfActiveStateUpdateAgreement( 535 | token, 536 | SR[0], 537 | trails[0], 538 | ledger.currentEon().sub(1), 539 | rsTxSetRoot[2], 540 | deltas, 541 | ledger.operator, 542 | rsTxSetRoot[0], rsTxSetRoot[1], v); 543 | 544 | rsTxSetRoot[0] = MerkleVerifier.transferChecksum( 545 | SR[1], 546 | nonceAmount[1], // amount 547 | trails[2], 548 | nonceAmount[0]); // nonce 549 | 550 | // Require tx to exist in transition 551 | require(MerkleVerifier.verifyProofOfMembership( 552 | trails[1], 553 | chain, 554 | rsTxSetRoot[0], // transferChecksum 555 | rsTxSetRoot[2]), // txSetRoot 556 | 'e'); 557 | 558 | initTransferDeliveryChallenge( 559 | ledger, 560 | token, 561 | SR[0], // senderAddress 562 | SR[1], // recipientAddress 563 | nonceAmount[1], // amount 564 | nonceAmount[0], // nonce 565 | trails[2]); // recipientTrail 566 | } 567 | 568 | function answerTransferDeliveryChallengeWithProofOfActiveStateUpdateAgreement( 569 | BimodalLib.Ledger storage ledger, 570 | ERC20 token, 571 | address[2] SR, // solhint-disable func-param-name-mixedcase 572 | uint64 transferMembershipTrail, 573 | bytes32[] allotmentChain, 574 | bytes32[] membershipChain, 575 | uint256[] values, 576 | uint256[2][3] lrDeltasPassiveMark, 577 | bytes32[2] txSetRootChecksum, 578 | bytes32[] txChain 579 | ) 580 | public 581 | { 582 | BimodalLib.Challenge storage challenge = ledger.challengeBook[token][SR[1]][SR[0]]; 583 | require(challenge.challengeType == BimodalLib.ChallengeType.TRANSFER_DELIVERY); 584 | 585 | // Assert that the challenged transaction belongs to the transfer set 586 | require(MerkleVerifier.verifyProofOfMembership( 587 | transferMembershipTrail, 588 | txChain, 589 | MerkleVerifier.transferChecksum( 590 | SR[0], 591 | challenge.finalStateBalance, // amount 592 | challenge.trailIdentifier, // recipient trail 593 | challenge.deliveredTxNonce), 594 | txSetRootChecksum[0])); // txSetRoot 595 | 596 | // Require committed transition to include transfer 597 | txSetRootChecksum[0] = MerkleVerifier.activeStateUpdateChecksum( 598 | token, 599 | SR[1], 600 | challenge.trailIdentifier, 601 | challenge.initialStateEon, 602 | txSetRootChecksum[0], // txSetRoot 603 | lrDeltasPassiveMark[1]); // Deltas 604 | 605 | // Assert that this transition was used to update the recipient's stake 606 | require(verifyProofOfExclusiveAccountBalanceAllotment( 607 | ledger, 608 | token, 609 | SR[1], // recipient 610 | txSetRootChecksum, // [activeStateChecksum, passiveChecksum] 611 | challenge.trailIdentifier, 612 | [ 613 | challenge.initialStateEon.add(1), // eonNumber 614 | lrDeltasPassiveMark[2][0], // passiveAmount 615 | lrDeltasPassiveMark[2][1] // passiveMark 616 | ], 617 | allotmentChain, 618 | membershipChain, 619 | values, 620 | lrDeltasPassiveMark[0])); // LR 621 | 622 | markChallengeAnswered(ledger, challenge); 623 | } 624 | 625 | // ======================================================================== 626 | // ======================================================================== 627 | // ======================================================================== 628 | // ==================================== PASSIVE DELIVERY Challenge 629 | // ======================================================================== 630 | // ======================================================================== 631 | // ======================================================================== 632 | function challengeTransferDeliveryWithProofOfPassiveStateUpdate( 633 | BimodalLib.Ledger storage ledger, 634 | ERC20 token, 635 | address[2] SR, // solhint-disable func-param-name-mixedcase 636 | bytes32[2] txSetRootChecksum, 637 | uint64[3] senderTransferRecipientTrails, 638 | bytes32[] allotmentChain, 639 | bytes32[] membershipChain, 640 | uint256[] values, 641 | uint256[2][4] lrDeltasPassiveMarkDummyAmount, 642 | bytes32[] transferMembershipChain 643 | ) 644 | public 645 | /* payable */ 646 | /* onlyWithFairReimbursement() */ 647 | { 648 | require(msg.sender == SR[0] || msg.sender == SR[1], 'd'); 649 | lrDeltasPassiveMarkDummyAmount[3][0] = ledger.currentEon().sub(1); // previousEon 650 | 651 | // Assert that the challenged transaction ends the transfer set 652 | require(MerkleVerifier.verifyProofOfMembership( 653 | senderTransferRecipientTrails[1], // transferMembershipTrail 654 | transferMembershipChain, 655 | MerkleVerifier.transferChecksum( 656 | SR[1], // recipientAddress 657 | lrDeltasPassiveMarkDummyAmount[3][1], // amount 658 | senderTransferRecipientTrails[2], // recipientTrail 659 | 2 ** 256 - 1), // nonce 660 | txSetRootChecksum[0]), // txSetRoot 661 | 'e'); 662 | 663 | // Require committed transition to include transfer 664 | txSetRootChecksum[0] = MerkleVerifier.activeStateUpdateChecksum( 665 | token, 666 | SR[0], // senderAddress 667 | senderTransferRecipientTrails[0], // senderTrail 668 | lrDeltasPassiveMarkDummyAmount[3][0], // previousEon 669 | txSetRootChecksum[0], // txSetRoot 670 | lrDeltasPassiveMarkDummyAmount[1]); // Deltas 671 | 672 | // Assert that this transition was used to update the sender's stake 673 | require(verifyProofOfExclusiveAccountBalanceAllotment( 674 | ledger, 675 | token, 676 | SR[0], // senderAddress 677 | txSetRootChecksum, // [activeStateChecksum, passiveChecksum] 678 | senderTransferRecipientTrails[0], // senderTrail 679 | [ 680 | lrDeltasPassiveMarkDummyAmount[3][0].add(1), // eonNumber 681 | lrDeltasPassiveMarkDummyAmount[2][0], // passiveAmount 682 | lrDeltasPassiveMarkDummyAmount[2][1] // passiveMark 683 | ], 684 | allotmentChain, 685 | membershipChain, 686 | values, 687 | lrDeltasPassiveMarkDummyAmount[0])); // LR 688 | 689 | initTransferDeliveryChallenge( 690 | ledger, 691 | token, 692 | SR[0], // sender 693 | SR[1], // recipient 694 | lrDeltasPassiveMarkDummyAmount[3][1], // amount 695 | uint256(keccak256(abi.encodePacked(lrDeltasPassiveMarkDummyAmount[2][1], uint256(2 ** 256 - 1)))), // mark (nonce) 696 | senderTransferRecipientTrails[2]); // recipientTrail 697 | } 698 | 699 | function answerTransferDeliveryChallengeWithProofOfPassiveStateUpdate( 700 | BimodalLib.Ledger storage ledger, 701 | ERC20 token, 702 | address[2] SR, // solhint-disable func-param-name-mixedcase 703 | uint64 transferMembershipTrail, 704 | bytes32[] allotmentChain, 705 | bytes32[] membershipChain, 706 | uint256[] values, 707 | uint256[2][3] lrPassiveMarkPositionNonce, 708 | bytes32[2] checksums, 709 | bytes32[] txChainValues 710 | ) 711 | public 712 | { 713 | BimodalLib.Challenge storage challenge = ledger.challengeBook[token][SR[1]][SR[0]]; 714 | require(challenge.challengeType == BimodalLib.ChallengeType.TRANSFER_DELIVERY); 715 | require( 716 | challenge.deliveredTxNonce == 717 | uint256(keccak256(abi.encodePacked(lrPassiveMarkPositionNonce[2][0], lrPassiveMarkPositionNonce[2][1]))) 718 | ); 719 | 720 | // Assert that the challenged transaction belongs to the passively delivered set 721 | require( 722 | MerkleVerifier.verifyProofOfPassiveDelivery( 723 | transferMembershipTrail, 724 | MerkleVerifier.transferChecksum( // node 725 | SR[0], // sender 726 | challenge.finalStateBalance, // amount 727 | challenge.trailIdentifier, // recipient trail 728 | challenge.deliveredTxNonce), 729 | checksums[1], // passiveChecksum 730 | txChainValues, 731 | [lrPassiveMarkPositionNonce[2][0], lrPassiveMarkPositionNonce[2][0].add(challenge.finalStateBalance)]) 732 | <= 733 | lrPassiveMarkPositionNonce[1][0]); 734 | 735 | // Assert that this transition was used to update the recipient's stake 736 | require(verifyProofOfExclusiveAccountBalanceAllotment( 737 | ledger, 738 | token, 739 | SR[1], // recipient 740 | checksums, // [activeStateChecksum, passiveChecksum] 741 | challenge.trailIdentifier, // recipientTrail 742 | [ 743 | challenge.initialStateEon.add(1), // eonNumber 744 | lrPassiveMarkPositionNonce[1][0], // passiveAmount 745 | lrPassiveMarkPositionNonce[1][1] // passiveMark 746 | ], 747 | allotmentChain, 748 | membershipChain, 749 | values, 750 | lrPassiveMarkPositionNonce[0])); // LR 751 | 752 | markChallengeAnswered(ledger, challenge); 753 | } 754 | 755 | // ======================================================================== 756 | // ======================================================================== 757 | // ======================================================================== 758 | // ==================================== SWAP Challenge 759 | // ======================================================================== 760 | // ======================================================================== 761 | // ======================================================================== 762 | function initSwapEnactmentChallenge( 763 | BimodalLib.Ledger storage ledger, 764 | ERC20[2] tokens, 765 | uint256[4] updatedSpentGainedPassive, 766 | uint256[4] sellBuyBalanceNonce, 767 | uint64 recipientTrail 768 | ) 769 | private 770 | { 771 | ERC20 conduit = ERC20(address(keccak256(abi.encodePacked(tokens[0], tokens[1])))); 772 | BimodalLib.Challenge storage challenge = ledger.challengeBook[conduit][msg.sender][msg.sender]; 773 | require(challenge.challengeType == BimodalLib.ChallengeType.NONE); 774 | require(challenge.initialStateEon < ledger.currentEon().sub(1)); 775 | 776 | challenge.initialStateEon = ledger.currentEon().sub(1); 777 | challenge.deliveredTxNonce = sellBuyBalanceNonce[3]; 778 | challenge.challengeType = BimodalLib.ChallengeType.SWAP_ENACTMENT; 779 | challenge.block = block.number; 780 | challenge.trailIdentifier = recipientTrail; 781 | challenge.deltaHighestSpendings = sellBuyBalanceNonce[0]; 782 | challenge.deltaHighestGains = sellBuyBalanceNonce[1]; 783 | 784 | (uint256 deposits, uint256 withdrawals) = ledger.getCurrentEonDepositsWithdrawals(tokens[0], msg.sender); 785 | 786 | challenge.initialStateBalance = 787 | sellBuyBalanceNonce[2] // allotment from eon e - 1 788 | .add(updatedSpentGainedPassive[2]) // gained 789 | .add(updatedSpentGainedPassive[3]) // passively delivered 790 | .add(deposits) 791 | .sub(updatedSpentGainedPassive[1]) // spent 792 | .sub(withdrawals); 793 | challenge.finalStateBalance = updatedSpentGainedPassive[0]; 794 | 795 | require(challenge.finalStateBalance >= challenge.initialStateBalance, 'd'); 796 | 797 | markChallengeLive(ledger, conduit, msg.sender, msg.sender); 798 | } 799 | 800 | function challengeSwapEnactmentWithProofOfActiveStateUpdateAgreement( 801 | BimodalLib.Ledger storage ledger, 802 | ERC20[2] tokens, 803 | uint64[3] senderTransferRecipientTrails, // senderTransferRecipientTrails 804 | bytes32[] allotmentChain, 805 | bytes32[] membershipChain, 806 | bytes32[] txChain, 807 | uint256[] values, 808 | uint256[2][3] lrDeltasPassiveMark, 809 | uint256[4] sellBuyBalanceNonce, 810 | bytes32[3] txSetRootChecksumDummy 811 | ) 812 | public 813 | /* payable */ 814 | /* onlyWithFairReimbursement() */ 815 | { 816 | // Require swap to exist in transition 817 | txSetRootChecksumDummy[2] = MerkleVerifier.swapOrderChecksum( 818 | tokens, 819 | senderTransferRecipientTrails[2], 820 | sellBuyBalanceNonce[0], // sell 821 | sellBuyBalanceNonce[1], // buy 822 | sellBuyBalanceNonce[2], // balance 823 | sellBuyBalanceNonce[3]); // nonce 824 | 825 | require(MerkleVerifier.verifyProofOfMembership( 826 | senderTransferRecipientTrails[1], 827 | txChain, 828 | txSetRootChecksumDummy[2], // swapOrderChecksum 829 | txSetRootChecksumDummy[0]), // txSetRoot 830 | 'e'); 831 | 832 | uint256 previousEon = ledger.currentEon().sub(1); 833 | 834 | // Require committed transition to include swap 835 | txSetRootChecksumDummy[2] = MerkleVerifier.activeStateUpdateChecksum( 836 | tokens[0], 837 | msg.sender, 838 | senderTransferRecipientTrails[0], 839 | previousEon, 840 | txSetRootChecksumDummy[0], 841 | lrDeltasPassiveMark[1]); // deltas 842 | 843 | uint256 updatedBalance = lrDeltasPassiveMark[0][1].sub(lrDeltasPassiveMark[0][0]); 844 | // The state must have been ratified in the commitment 845 | require(verifyProofOfExclusiveAccountBalanceAllotment( 846 | ledger, 847 | tokens[0], 848 | msg.sender, 849 | [txSetRootChecksumDummy[2], txSetRootChecksumDummy[1]], // [activeStateChecksum, passiveChecksum] 850 | senderTransferRecipientTrails[0], 851 | [ 852 | previousEon.add(1), // eonNumber 853 | lrDeltasPassiveMark[2][0], // passiveAmount 854 | lrDeltasPassiveMark[2][1] // passiveMark 855 | ], 856 | allotmentChain, 857 | membershipChain, 858 | values, 859 | lrDeltasPassiveMark[0])); // LR 860 | 861 | initSwapEnactmentChallenge( 862 | ledger, 863 | tokens, 864 | [ 865 | updatedBalance, // updated 866 | lrDeltasPassiveMark[1][0], // spent 867 | lrDeltasPassiveMark[1][1], // gained 868 | lrDeltasPassiveMark[2][0]], // passiveAmount 869 | sellBuyBalanceNonce, 870 | senderTransferRecipientTrails[2]); 871 | } 872 | 873 | /** 874 | * This method just calculates the total expected balance. 875 | */ 876 | function calculateSwapConsistencyBalance( 877 | BimodalLib.Ledger storage ledger, 878 | ERC20 token, 879 | uint256[2] deltas, 880 | uint256 passiveAmount, 881 | uint256 balance 882 | ) 883 | private 884 | view 885 | returns (uint256) 886 | { 887 | (uint256 deposits, uint256 withdrawals) = ledger.getCurrentEonDepositsWithdrawals(token, msg.sender); 888 | 889 | return balance 890 | .add(deltas[1]) // gained 891 | .add(passiveAmount) // passively delivered 892 | .add(deposits) 893 | .sub(withdrawals) 894 | .sub(deltas[0]); // spent 895 | } 896 | 897 | /** 898 | * This method calculates the balance expected to be credited in return for that 899 | * debited in another token according to the swapping price and is adjusted to 900 | * ignore numerical errors up to 2 decimal places. 901 | */ 902 | function verifySwapConsistency( 903 | BimodalLib.Ledger storage ledger, 904 | ERC20[2] tokens, 905 | BimodalLib.Challenge challenge, 906 | uint256[2] LR, // solhint-disable func-param-name-mixedcase 907 | uint256[2] deltas, 908 | uint256 passiveAmount, 909 | uint256 balance 910 | ) 911 | private 912 | view 913 | returns (bool) 914 | { 915 | balance = calculateSwapConsistencyBalance(ledger, tokens[1], deltas, passiveAmount, balance); 916 | 917 | require(LR[1].sub(LR[0]) >= balance); 918 | 919 | uint256 taken = challenge.deltaHighestSpendings // sell amount 920 | .sub(challenge.finalStateBalance.sub(challenge.initialStateBalance)); // refund 921 | uint256 given = LR[1].sub(LR[0]) // recipient allotment 922 | .sub(balance); // authorized allotment 923 | 924 | return taken.mul(challenge.deltaHighestGains).div(100) >= challenge.deltaHighestSpendings.mul(given).div(100); 925 | } 926 | 927 | function answerSwapChallengeWithProofOfExclusiveBalanceAllotment( 928 | BimodalLib.Ledger storage ledger, 929 | ERC20[2] tokens, 930 | address issuer, 931 | uint64 transferMembershipTrail, 932 | bytes32[] allotmentChain, 933 | bytes32[] membershipChain, 934 | bytes32[] txChain, 935 | uint256[] values, 936 | uint256[2][3] lrDeltasPassiveMark, 937 | uint256 balance, 938 | bytes32[3] txSetRootChecksumDummy 939 | ) 940 | public 941 | { 942 | ERC20 conduit = ERC20(address(keccak256(abi.encodePacked(tokens[0], tokens[1])))); 943 | BimodalLib.Challenge storage challenge = ledger.challengeBook[conduit][issuer][issuer]; 944 | require(challenge.challengeType == BimodalLib.ChallengeType.SWAP_ENACTMENT); 945 | 946 | // Assert that the challenged swap belongs to the transition 947 | txSetRootChecksumDummy[2] = MerkleVerifier.swapOrderChecksum( 948 | tokens, 949 | challenge.trailIdentifier, // recipient trail 950 | challenge.deltaHighestSpendings, // sell amount 951 | challenge.deltaHighestGains, // buy amount 952 | balance, // starting balance 953 | challenge.deliveredTxNonce); 954 | 955 | require(MerkleVerifier.verifyProofOfMembership( 956 | transferMembershipTrail, 957 | txChain, 958 | txSetRootChecksumDummy[2], // order checksum 959 | txSetRootChecksumDummy[0]), 'M'); // txSetRoot 960 | 961 | // Require committed transition to include swap 962 | txSetRootChecksumDummy[2] = MerkleVerifier.activeStateUpdateChecksum( 963 | tokens[1], 964 | issuer, 965 | challenge.trailIdentifier, 966 | challenge.initialStateEon, 967 | txSetRootChecksumDummy[0], // txSetRoot 968 | lrDeltasPassiveMark[1]); // deltas 969 | 970 | if (balance != 2 ** 256 - 1) { 971 | require(verifySwapConsistency( 972 | ledger, 973 | tokens, 974 | challenge, 975 | lrDeltasPassiveMark[0], 976 | lrDeltasPassiveMark[1], 977 | lrDeltasPassiveMark[2][0], 978 | balance), 979 | 'v'); 980 | } 981 | 982 | // Assert that this transition was used to update the recipient's stake 983 | require(verifyProofOfExclusiveAccountBalanceAllotment( 984 | ledger, 985 | tokens[1], 986 | issuer, 987 | [txSetRootChecksumDummy[2], txSetRootChecksumDummy[1]], // activeStateChecksum, passiveChecksum 988 | challenge.trailIdentifier, 989 | [challenge.initialStateEon.add(1), lrDeltasPassiveMark[2][0], lrDeltasPassiveMark[2][1]], 990 | allotmentChain, 991 | membershipChain, 992 | values, 993 | lrDeltasPassiveMark[0]), // LR 994 | 's'); 995 | 996 | markChallengeAnswered(ledger, challenge); 997 | } 998 | 999 | // ======================================================================== 1000 | // ======================================================================== 1001 | // ======================================================================== 1002 | // ==================================== WITHDRAWAL Challenge 1003 | // ======================================================================== 1004 | // ======================================================================== 1005 | // ======================================================================== 1006 | function slashWithdrawalWithProofOfMinimumAvailableBalance( 1007 | BimodalLib.Ledger storage ledger, 1008 | ERC20 token, 1009 | address withdrawer, 1010 | uint256[2] markerEonAvailable, 1011 | bytes32[2] rs, 1012 | uint8 v 1013 | ) 1014 | public 1015 | returns (uint256[2] amounts) 1016 | { 1017 | uint256 latestEon = ledger.currentEon(); 1018 | require( 1019 | latestEon < markerEonAvailable[0].add(3), 1020 | 'm'); 1021 | 1022 | bytes32 checksum = keccak256(abi.encodePacked( 1023 | keccak256(abi.encodePacked(address(this))), 1024 | keccak256(abi.encodePacked(token)), 1025 | keccak256(abi.encodePacked(withdrawer)), 1026 | markerEonAvailable[0], 1027 | markerEonAvailable[1] 1028 | )); 1029 | 1030 | require(withdrawer == BimodalLib.signedMessageECRECOVER(checksum, rs[0], rs[1], v)); 1031 | 1032 | BimodalLib.Wallet storage entry = ledger.walletBook[token][withdrawer]; 1033 | BimodalLib.Withdrawal[] storage withdrawals = entry.withdrawals; 1034 | 1035 | for (uint32 i = 1; i <= withdrawals.length; i++) { 1036 | BimodalLib.Withdrawal storage withdrawal = withdrawals[withdrawals.length.sub(i)]; 1037 | 1038 | if (withdrawal.eon.add(1) < latestEon) { 1039 | break; 1040 | } else if (withdrawal.eon == latestEon.sub(1)) { 1041 | amounts[0] = amounts[0].add(withdrawal.amount); 1042 | } else if (withdrawal.eon == latestEon) { 1043 | amounts[1] = amounts[1].add(withdrawal.amount); 1044 | } 1045 | } 1046 | 1047 | require(amounts[0].add(amounts[1]) > markerEonAvailable[1]); 1048 | 1049 | withdrawals.length = withdrawals.length.sub(i.sub(1)); // i >= 1 1050 | 1051 | if (amounts[1] > 0) { 1052 | ledger.deductFromRunningPendingWithdrawals(token, latestEon, latestEon, amounts[1]); 1053 | ledger.appendOperationToEonAccumulator( 1054 | latestEon, 1055 | token, 1056 | withdrawer, 1057 | BimodalLib.Operation.CANCELLATION, 1058 | amounts[1]); 1059 | } 1060 | 1061 | if (amounts[0] > 0) { 1062 | ledger.deductFromRunningPendingWithdrawals(token, latestEon.sub(1), latestEon, amounts[0]); 1063 | ledger.appendOperationToEonAccumulator( 1064 | latestEon.sub(1), 1065 | token, withdrawer, 1066 | BimodalLib.Operation.CANCELLATION, 1067 | amounts[0]); 1068 | } 1069 | } 1070 | } 1071 | -------------------------------------------------------------------------------- /contracts/ChallengeProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./BimodalProxy.sol"; 4 | import "./ERC20.sol"; 5 | import "./BimodalLib.sol"; 6 | import "./MerkleVerifier.sol"; 7 | import "./ChallengeLib.sol"; 8 | import "./SafeMath/SafeMathLib256.sol"; 9 | 10 | contract ChallengeProxy is BimodalProxy { 11 | using SafeMathLib256 for uint256; 12 | 13 | modifier onlyWithFairReimbursement() { 14 | uint256 gas = gasleft(); 15 | _; 16 | gas = gas.sub(gasleft()); 17 | require( 18 | msg.value >= gas.mul(ledger.MIN_CHALLENGE_GAS_COST) && 19 | msg.value >= gas.mul(tx.gasprice), 20 | 'r'); 21 | ledger.operator.transfer(msg.value); 22 | } 23 | 24 | modifier onlyWithSkewedReimbursement(uint256 extra) { 25 | uint256 gas = gasleft(); 26 | _; 27 | gas = gas.sub(gasleft()); 28 | require( 29 | msg.value >= gas.add(extra).mul(ledger.MIN_CHALLENGE_GAS_COST) && 30 | msg.value >= gas.add(extra).mul(tx.gasprice), 31 | 'r'); 32 | ledger.operator.transfer(msg.value); 33 | } 34 | 35 | // ========================================================================= 36 | function verifyProofOfExclusiveAccountBalanceAllotment( 37 | ERC20 token, 38 | address holder, 39 | bytes32[2] activeStateChecksum_passiveTransfersRoot, // solhint-disable func-param-name-mixedcase 40 | uint64 trail, 41 | uint256[3] eonPassiveMark, 42 | bytes32[] allotmentChain, 43 | bytes32[] membershipChain, 44 | uint256[] values, 45 | uint256[2] LR // solhint-disable-line func-param-name-mixedcase 46 | ) 47 | public 48 | view 49 | returns (bool) 50 | { 51 | return ChallengeLib.verifyProofOfExclusiveAccountBalanceAllotment( 52 | ledger, 53 | token, 54 | holder, 55 | activeStateChecksum_passiveTransfersRoot, 56 | trail, 57 | eonPassiveMark, 58 | allotmentChain, 59 | membershipChain, 60 | values, 61 | LR 62 | ); 63 | } 64 | 65 | function verifyProofOfActiveStateUpdateAgreement( 66 | ERC20 token, 67 | address holder, 68 | uint64 trail, 69 | uint256 eon, 70 | bytes32 txSetRoot, 71 | uint256[2] deltas, 72 | address attester, bytes32 r, bytes32 s, uint8 v 73 | ) 74 | public 75 | view 76 | returns (bytes32 checksum) 77 | { 78 | return ChallengeLib.verifyProofOfActiveStateUpdateAgreement( 79 | token, 80 | holder, 81 | trail, 82 | eon, 83 | txSetRoot, 84 | deltas, 85 | attester, 86 | r, 87 | s, 88 | v 89 | ); 90 | } 91 | 92 | function verifyWithdrawalAuthorization( 93 | ERC20 token, 94 | address holder, 95 | uint256 expiry, 96 | uint256 amount, 97 | address attester, 98 | bytes32 r, bytes32 s, uint8 v 99 | ) 100 | public 101 | view 102 | returns (bool) 103 | { 104 | return ChallengeLib.verifyWithdrawalAuthorization( 105 | token, 106 | holder, 107 | expiry, 108 | amount, 109 | attester, 110 | r, 111 | s, 112 | v 113 | ); 114 | } 115 | 116 | function verifyProofOfExclusiveBalanceAllotment( 117 | uint64 allotmentTrail, 118 | uint64 membershipTrail, 119 | bytes32 node, 120 | bytes32 root, 121 | bytes32[] allotmentChain, 122 | bytes32[] membershipChain, 123 | uint256[] value, 124 | uint256[2] LR // solhint-disable-line func-param-name-mixedcase 125 | ) 126 | public 127 | pure 128 | returns (uint256) 129 | { 130 | return MerkleVerifier.verifyProofOfExclusiveBalanceAllotment( 131 | allotmentTrail, 132 | membershipTrail, 133 | node, 134 | root, 135 | allotmentChain, 136 | membershipChain, 137 | value, 138 | LR 139 | ); 140 | } 141 | 142 | function verifyProofOfMembership( 143 | uint256 trail, 144 | bytes32[] chain, 145 | bytes32 node, 146 | bytes32 merkleRoot 147 | ) 148 | public 149 | pure 150 | returns (bool) 151 | { 152 | return MerkleVerifier.verifyProofOfMembership( 153 | trail, 154 | chain, 155 | node, 156 | merkleRoot 157 | ); 158 | } 159 | 160 | function verifyProofOfPassiveDelivery( 161 | uint64 allotmentTrail, 162 | bytes32 node, 163 | bytes32 root, 164 | bytes32[] chainValues, 165 | uint256[2] LR // solhint-disable-line func-param-name-mixedcase 166 | ) 167 | public 168 | pure 169 | returns (uint256) 170 | { 171 | return MerkleVerifier.verifyProofOfPassiveDelivery( 172 | allotmentTrail, 173 | node, 174 | root, 175 | chainValues, 176 | LR 177 | ); 178 | } 179 | 180 | // ========================================================================= 181 | function challengeStateUpdateWithProofOfExclusiveBalanceAllotment( 182 | ERC20 token, 183 | bytes32[2] checksums, 184 | uint64 trail, 185 | bytes32[] allotmentChain, 186 | bytes32[] membershipChain, 187 | uint256[] value, 188 | uint256[2][3] lrDeltasPassiveMark, 189 | bytes32[3] rsTxSetRoot, 190 | uint8 v 191 | ) 192 | public 193 | payable 194 | onlyWithFairReimbursement() 195 | { 196 | ChallengeLib.challengeStateUpdateWithProofOfExclusiveBalanceAllotment( 197 | ledger, 198 | token, 199 | checksums, 200 | trail, 201 | allotmentChain, 202 | membershipChain, 203 | value, 204 | lrDeltasPassiveMark, 205 | rsTxSetRoot, 206 | v 207 | ); 208 | } 209 | 210 | function challengeStateUpdateWithProofOfActiveStateUpdateAgreement( 211 | ERC20 token, 212 | bytes32 txSetRoot, 213 | uint64 trail, 214 | uint256[2] deltas, 215 | bytes32 r, bytes32 s, uint8 v 216 | ) 217 | public 218 | payable 219 | onlyWithSkewedReimbursement(25) /* TODO calculate exact addition */ 220 | { 221 | ChallengeLib.challengeStateUpdateWithProofOfActiveStateUpdateAgreement( 222 | ledger, 223 | token, 224 | txSetRoot, 225 | trail, 226 | deltas, 227 | r, 228 | s, 229 | v 230 | ); 231 | } 232 | 233 | function answerStateUpdateChallenge( 234 | ERC20 token, 235 | address issuer, 236 | bytes32[] allotmentChain, 237 | bytes32[] membershipChain, 238 | uint256[] values, 239 | uint256[2][3] lrDeltasPassiveMark, // [ [L, R], Deltas ] 240 | bytes32[6] rSrStxSetRootChecksum, 241 | uint8[2] v 242 | ) 243 | public 244 | { 245 | ChallengeLib.answerStateUpdateChallenge( 246 | ledger, 247 | token, 248 | issuer, 249 | allotmentChain, 250 | membershipChain, 251 | values, 252 | lrDeltasPassiveMark, 253 | rSrStxSetRootChecksum, 254 | v 255 | ); 256 | } 257 | 258 | // ========================================================================= 259 | function challengeTransferDeliveryWithProofOfActiveStateUpdateAgreement( 260 | ERC20 token, 261 | address[2] SR, // solhint-disable-line func-param-name-mixedcase 262 | uint256[2] nonceAmount, 263 | uint64[3] trails, 264 | bytes32[] chain, 265 | uint256[2] deltas, 266 | bytes32[3] rsTxSetRoot, 267 | uint8 v 268 | ) 269 | public 270 | payable 271 | onlyWithFairReimbursement() 272 | { 273 | ChallengeLib.challengeTransferDeliveryWithProofOfActiveStateUpdateAgreement( 274 | ledger, 275 | token, 276 | SR, 277 | nonceAmount, 278 | trails, 279 | chain, 280 | deltas, 281 | rsTxSetRoot, 282 | v 283 | ); 284 | } 285 | 286 | function answerTransferDeliveryChallengeWithProofOfActiveStateUpdateAgreement( 287 | ERC20 token, 288 | address[2] SR, // solhint-disable-line func-param-name-mixedcase 289 | uint64 transferMembershipTrail, 290 | bytes32[] allotmentChain, 291 | bytes32[] membershipChain, 292 | uint256[] values, 293 | uint256[2][3] lrDeltasPassiveMark, 294 | bytes32[2] txSetRootChecksum, 295 | bytes32[] txChain 296 | ) 297 | public 298 | { 299 | ChallengeLib.answerTransferDeliveryChallengeWithProofOfActiveStateUpdateAgreement( 300 | ledger, 301 | token, 302 | SR, 303 | transferMembershipTrail, 304 | allotmentChain, 305 | membershipChain, 306 | values, 307 | lrDeltasPassiveMark, 308 | txSetRootChecksum, 309 | txChain 310 | ); 311 | } 312 | 313 | // ========================================================================= 314 | function challengeTransferDeliveryWithProofOfPassiveStateUpdate( 315 | ERC20 token, 316 | address[2] SR, // solhint-disable-line func-param-name-mixedcase 317 | bytes32[2] txSetRootChecksum, 318 | uint64[3] senderTransferRecipientTrails, 319 | bytes32[] allotmentChain, 320 | bytes32[] membershipChain, 321 | uint256[] values, 322 | uint256[2][4] lrDeltasPassiveMarkDummyAmount, 323 | bytes32[] transferMembershipChain 324 | ) 325 | public 326 | payable 327 | onlyWithFairReimbursement() 328 | { 329 | ChallengeLib.challengeTransferDeliveryWithProofOfPassiveStateUpdate( 330 | ledger, 331 | token, 332 | SR, 333 | txSetRootChecksum, 334 | senderTransferRecipientTrails, 335 | allotmentChain, 336 | membershipChain, 337 | values, 338 | lrDeltasPassiveMarkDummyAmount, 339 | transferMembershipChain 340 | ); 341 | } 342 | 343 | function answerTransferDeliveryChallengeWithProofOfPassiveStateUpdate( 344 | ERC20 token, 345 | address[2] SR, // solhint-disable-line func-param-name-mixedcase 346 | uint64 transferMembershipTrail, 347 | bytes32[] allotmentChain, 348 | bytes32[] membershipChain, 349 | uint256[] values, 350 | uint256[2][3] lrPassiveMarkPositionNonce, 351 | bytes32[2] checksums, 352 | bytes32[] txChainValues 353 | ) 354 | public 355 | { 356 | ChallengeLib.answerTransferDeliveryChallengeWithProofOfPassiveStateUpdate( 357 | ledger, 358 | token, 359 | SR, 360 | transferMembershipTrail, 361 | allotmentChain, 362 | membershipChain, 363 | values, 364 | lrPassiveMarkPositionNonce, 365 | checksums, 366 | txChainValues 367 | ); 368 | } 369 | 370 | // ========================================================================= 371 | function challengeSwapEnactmentWithProofOfActiveStateUpdateAgreement( 372 | ERC20[2] tokens, 373 | uint64[3] senderTransferRecipientTrails, 374 | bytes32[] allotmentChain, 375 | bytes32[] membershipChain, 376 | bytes32[] txChain, 377 | uint256[] values, 378 | uint256[2][3] lrDeltasPassiveMark, 379 | uint256[4] sellBuyBalanceNonce, 380 | bytes32[3] txSetRootChecksumDummy 381 | ) 382 | public 383 | payable 384 | onlyWithFairReimbursement() 385 | { 386 | ChallengeLib.challengeSwapEnactmentWithProofOfActiveStateUpdateAgreement( 387 | ledger, 388 | tokens, 389 | senderTransferRecipientTrails, 390 | allotmentChain, 391 | membershipChain, 392 | txChain, 393 | values, 394 | lrDeltasPassiveMark, 395 | sellBuyBalanceNonce, 396 | txSetRootChecksumDummy 397 | ); 398 | } 399 | 400 | function answerSwapChallengeWithProofOfExclusiveBalanceAllotment( 401 | ERC20[2] tokens, 402 | address issuer, 403 | uint64 transferMembershipTrail, 404 | bytes32[] allotmentChain, 405 | bytes32[] membershipChain, 406 | bytes32[] txChain, 407 | uint256[] values, 408 | uint256[2][3] lrDeltasPassiveMark, 409 | uint256 balance, 410 | bytes32[3] txSetRootChecksumDummy 411 | ) 412 | public 413 | { 414 | ChallengeLib.answerSwapChallengeWithProofOfExclusiveBalanceAllotment( 415 | ledger, 416 | tokens, 417 | issuer, 418 | transferMembershipTrail, 419 | allotmentChain, 420 | membershipChain, 421 | txChain, 422 | values, 423 | lrDeltasPassiveMark, 424 | balance, 425 | txSetRootChecksumDummy 426 | ); 427 | } 428 | 429 | // ========================================================================= 430 | function slashWithdrawalWithProofOfMinimumAvailableBalance( 431 | ERC20 token, 432 | address withdrawer, 433 | uint256[2] markerEonAvailable, 434 | bytes32[2] rs, 435 | uint8 v 436 | ) 437 | public 438 | returns (uint256[2]) 439 | { 440 | return ChallengeLib.slashWithdrawalWithProofOfMinimumAvailableBalance( 441 | ledger, 442 | token, 443 | withdrawer, 444 | markerEonAvailable, 445 | rs, 446 | v 447 | ); 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /contracts/DepositLib.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./ERC20.sol"; 4 | import "./BimodalLib.sol"; 5 | import "./SafeMath/SafeMathLib256.sol"; 6 | 7 | /** 8 | * This library defines the secure deposit method. The relevant data is recorded 9 | * on the parent chain to ascertain that a registered wallet would always be able 10 | * to ensure its commit chain state update consistency with the parent chain. 11 | */ 12 | library DepositLib { 13 | using SafeMathLib256 for uint256; 14 | using BimodalLib for BimodalLib.Ledger; 15 | // EVENTS 16 | event Deposit(address indexed token, address indexed recipient, uint256 amount); 17 | 18 | function deposit( 19 | BimodalLib.Ledger storage ledger, 20 | ERC20 token, 21 | address beneficiary, 22 | uint256 amount 23 | ) 24 | public 25 | /* payable */ 26 | /* onlyWhenContractUnpunished() */ 27 | { 28 | uint256 eon = ledger.currentEon(); 29 | 30 | uint256 value = msg.value; 31 | if (token != address(this)) { 32 | require(ledger.tokenToTrail[token] != 0, 33 | 't'); 34 | require(msg.value == 0, 35 | 'm'); 36 | require(token.transferFrom(beneficiary, this, amount), 37 | 'f'); 38 | value = amount; 39 | } 40 | 41 | BimodalLib.Wallet storage entry = ledger.walletBook[token][beneficiary]; 42 | BimodalLib.AmountAggregate storage depositAggregate = entry.depositsKept[eon.mod(ledger.DEPOSITS_KEPT)]; 43 | BimodalLib.addToAggregate(depositAggregate, eon, value); 44 | 45 | BimodalLib.AmountAggregate storage eonDeposits = ledger.deposits[token][eon.mod(ledger.EONS_KEPT)]; 46 | BimodalLib.addToAggregate(eonDeposits, eon, value); 47 | 48 | ledger.appendOperationToEonAccumulator(eon, token, beneficiary, BimodalLib.Operation.DEPOSIT, value); 49 | 50 | emit Deposit(token, beneficiary, value); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /contracts/DepositProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./ERC20.sol"; 4 | import "./BimodalLib.sol"; 5 | import "./BimodalProxy.sol"; 6 | import "./DepositLib.sol"; 7 | import "./SafeMath/SafeMathLib256.sol"; 8 | 9 | contract DepositProxy is BimodalProxy { 10 | using SafeMathLib256 for uint256; 11 | 12 | function() 13 | public 14 | payable 15 | {} 16 | 17 | function deposit( 18 | ERC20 token, 19 | address beneficiary, 20 | uint256 amount 21 | ) 22 | public 23 | payable 24 | onlyWhenContractUnpunished() 25 | { 26 | DepositLib.deposit( 27 | ledger, 28 | token, 29 | beneficiary, 30 | amount); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract ERC20Basic { 4 | function totalSupply() public view returns (uint256); 5 | function balanceOf(address who) public view returns (uint256); 6 | function transfer(address to, uint256 value) public returns (bool); 7 | event Transfer(address indexed from, address indexed to, uint256 value); 8 | } 9 | 10 | contract ERC20 is ERC20Basic { 11 | function allowance(address owner, address spender) 12 | public view returns (uint256); 13 | 14 | function transferFrom(address from, address to, uint256 value) 15 | public returns (bool); 16 | 17 | function approve(address spender, uint256 value) public returns (bool); 18 | 19 | event Approval( 20 | address indexed owner, 21 | address indexed spender, 22 | uint256 value 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /contracts/ERC20TokenImplementation.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./ERC20.sol"; 4 | import "./SafeMath/SafeMathLib256.sol"; 5 | 6 | /* solhint-disable max-line-length */ 7 | 8 | /** 9 | * @title Basic token 10 | * @dev Basic version of StandardToken, with no allowances. 11 | * Source: https://github.com/OpenZeppelin/openzeppelin-solidity/blob/1200969eb6e0a066b1e52fb2e76a786a486706ff/contracts/token/ERC20/BasicToken.sol 12 | */ 13 | contract BasicToken is ERC20Basic { 14 | using SafeMathLib256 for uint256; 15 | 16 | mapping(address => uint256) internal balances; 17 | 18 | uint256 internal totalSupply_; 19 | 20 | /** 21 | * @dev Total number of tokens in existence 22 | */ 23 | function totalSupply() public view returns (uint256) { 24 | return totalSupply_; 25 | } 26 | 27 | /** 28 | * @dev Transfer token for a specified address 29 | * @param _to The address to transfer to. 30 | * @param _value The amount to be transferred. 31 | */ 32 | function transfer(address _to, uint256 _value) public returns (bool) { 33 | require(_value <= balances[msg.sender]); 34 | require(_to != address(0)); 35 | 36 | balances[msg.sender] = balances[msg.sender].sub(_value); 37 | balances[_to] = balances[_to].add(_value); 38 | emit Transfer(msg.sender, _to, _value); 39 | return true; 40 | } 41 | 42 | /** 43 | * @dev Gets the balance of the specified address. 44 | * @param _owner The address to query the the balance of. 45 | * @return An uint256 representing the amount owned by the passed address. 46 | */ 47 | function balanceOf(address _owner) public view returns (uint256) { 48 | return balances[_owner]; 49 | } 50 | 51 | } 52 | 53 | /** 54 | * @title Standard ERC20 token 55 | * 56 | * @dev Implementation of the basic standard token. 57 | * https://github.com/ethereum/EIPs/issues/20 58 | * Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol 59 | * Source: https://github.com/OpenZeppelin/openzeppelin-solidity/blob/1200969eb6e0a066b1e52fb2e76a786a486706ff/contracts/token/ERC20/StandardToken.sol 60 | */ 61 | contract StandardToken is ERC20, BasicToken { 62 | using SafeMathLib256 for uint256; 63 | 64 | mapping (address => mapping (address => uint256)) internal allowed; 65 | 66 | /** 67 | * @dev Transfer tokens from one address to another 68 | * @param _from address The address which you want to send tokens from 69 | * @param _to address The address which you want to transfer to 70 | * @param _value uint256 the amount of tokens to be transferred 71 | */ 72 | function transferFrom( 73 | address _from, 74 | address _to, 75 | uint256 _value 76 | ) 77 | public 78 | returns (bool) 79 | { 80 | require(_value <= balances[_from]); 81 | require(_value <= allowed[_from][msg.sender]); 82 | require(_to != address(0)); 83 | 84 | balances[_from] = balances[_from].sub(_value); 85 | balances[_to] = balances[_to].add(_value); 86 | allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); 87 | emit Transfer(_from, _to, _value); 88 | return true; 89 | } 90 | 91 | /** 92 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 93 | * Beware that changing an allowance with this method brings the risk that someone may use both the old 94 | * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 95 | * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 96 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 97 | * @param _spender The address which will spend the funds. 98 | * @param _value The amount of tokens to be spent. 99 | */ 100 | function approve(address _spender, uint256 _value) public returns (bool) { 101 | allowed[msg.sender][_spender] = _value; 102 | emit Approval(msg.sender, _spender, _value); 103 | return true; 104 | } 105 | 106 | /** 107 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 108 | * @param _owner address The address which owns the funds. 109 | * @param _spender address The address which will spend the funds. 110 | * @return A uint256 specifying the amount of tokens still available for the spender. 111 | */ 112 | function allowance( 113 | address _owner, 114 | address _spender 115 | ) 116 | public 117 | view 118 | returns (uint256) 119 | { 120 | return allowed[_owner][_spender]; 121 | } 122 | 123 | /** 124 | * @dev Increase the amount of tokens that an owner allowed to a spender. 125 | * approve should be called when allowed[_spender] == 0. To increment 126 | * allowed value is better to use this function to avoid 2 calls (and wait until 127 | * the first transaction is mined) 128 | * From MonolithDAO Token.sol 129 | * @param _spender The address which will spend the funds. 130 | * @param _addedValue The amount of tokens to increase the allowance by. 131 | */ 132 | function increaseApproval( 133 | address _spender, 134 | uint256 _addedValue 135 | ) 136 | public 137 | returns (bool) 138 | { 139 | allowed[msg.sender][_spender] = ( 140 | allowed[msg.sender][_spender].add(_addedValue)); 141 | emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); 142 | return true; 143 | } 144 | 145 | /** 146 | * @dev Decrease the amount of tokens that an owner allowed to a spender. 147 | * approve should be called when allowed[_spender] == 0. To decrement 148 | * allowed value is better to use this function to avoid 2 calls (and wait until 149 | * the first transaction is mined) 150 | * From MonolithDAO Token.sol 151 | * @param _spender The address which will spend the funds. 152 | * @param _subtractedValue The amount of tokens to decrease the allowance by. 153 | */ 154 | function decreaseApproval( 155 | address _spender, 156 | uint256 _subtractedValue 157 | ) 158 | public 159 | returns (bool) 160 | { 161 | uint256 oldValue = allowed[msg.sender][_spender]; 162 | if (_subtractedValue >= oldValue) { 163 | allowed[msg.sender][_spender] = 0; 164 | } else { 165 | allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); 166 | } 167 | emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); 168 | return true; 169 | } 170 | } 171 | 172 | /** 173 | * @title DetailedERC20 token 174 | * @dev The decimals are only for visualization purposes. 175 | * All the operations are done using the smallest and indivisible token unit, 176 | * just as on Ethereum all the operations are done in wei. 177 | * Source: https://github.com/OpenZeppelin/openzeppelin-solidity/blob/1200969eb6e0a066b1e52fb2e76a786a486706ff/contracts/token/ERC20/DetailedERC20.sol 178 | */ 179 | contract DetailedERC20 is ERC20 { 180 | using SafeMathLib256 for uint256; 181 | 182 | string public name; 183 | string public symbol; 184 | uint8 public decimals; 185 | 186 | constructor(string _name, string _symbol, uint8 _decimals) public { 187 | name = _name; 188 | symbol = _symbol; 189 | decimals = _decimals; 190 | } 191 | } 192 | 193 | interface ApprovalSpender { 194 | function receiveApproval(address from, uint256 value, address token, bytes data) external; 195 | } 196 | 197 | /** 198 | * @title Liquidity.Network Fungible Token Contract 199 | * Built on OpenZeppelin-Solidity Contracts https://github.com/OpenZeppelin/openzeppelin-solidity/tree/1200969eb6e0a066b1e52fb2e76a786a486706ff 200 | */ 201 | contract ERC20TokenImplementation is StandardToken, DetailedERC20 { 202 | using SafeMathLib256 for uint256; 203 | 204 | constructor() 205 | public 206 | DetailedERC20("Liquidity.Network Token", "LQD", 18) 207 | { 208 | totalSupply_ = 100000000 * (10 ** uint256(decimals)); 209 | balances[msg.sender] = totalSupply_; 210 | emit Transfer(0x0, msg.sender, totalSupply_); 211 | } 212 | 213 | function approveAndCall(ApprovalSpender recipientContract, uint256 value, bytes data) public returns (bool) { 214 | if (approve(recipientContract, value)) { 215 | recipientContract.receiveApproval(msg.sender, value, address(this), data); 216 | return true; 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /contracts/MerkleVerifier.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./ERC20.sol"; 4 | import "./SafeMath/SafeMathLib32.sol"; 5 | import "./SafeMath/SafeMathLib64.sol"; 6 | import "./SafeMath/SafeMathLib256.sol"; 7 | 8 | /* 9 | This library defines a collection of different checksumming procedures for 10 | membership, exclusive allotment and data. 11 | */ 12 | library MerkleVerifier { 13 | using SafeMathLib32 for uint32; 14 | using SafeMathLib64 for uint64; 15 | using SafeMathLib256 for uint256; 16 | 17 | /** 18 | * Calculate a vanilla merkle root from a chain of hashes with a fixed height 19 | * starting from the leaf node. 20 | */ 21 | function calculateMerkleRoot( 22 | uint256 trail, 23 | bytes32[] chain, 24 | bytes32 node 25 | ) 26 | public 27 | pure 28 | returns (bytes32) 29 | { 30 | for (uint32 i = 0; i < chain.length; i++) { 31 | bool linkLeft = false; 32 | if (trail > 0) { 33 | linkLeft = trail.mod(2) == 1; 34 | trail = trail.div(2); 35 | } 36 | node = keccak256(abi.encodePacked( 37 | i, 38 | linkLeft ? chain[i] : node, 39 | linkLeft ? node : chain[i] 40 | )); 41 | } 42 | return node; 43 | } 44 | 45 | function verifyProofOfMembership( 46 | uint256 trail, 47 | bytes32[] chain, 48 | bytes32 node, 49 | bytes32 merkleRoot 50 | ) 51 | public 52 | pure 53 | returns (bool) 54 | { 55 | return calculateMerkleRoot(trail, chain, node) == merkleRoot; 56 | } 57 | 58 | /** 59 | * Calculate an annotated merkle tree root from a chain of hashes and sibling 60 | * values with a fixed height starting from the leaf node. 61 | * @return the allotment of the root node. 62 | */ 63 | function verifyProofOfExclusiveBalanceAllotment( 64 | uint64 allotmentTrail, 65 | uint64 membershipTrail, 66 | bytes32 node, 67 | bytes32 root, 68 | bytes32[] allotmentChain, 69 | bytes32[] membershipChain, 70 | uint256[] value, 71 | uint256[2] LR // solhint-disable-line func-param-name-mixedcase 72 | ) 73 | public 74 | pure 75 | returns (uint256) 76 | { 77 | require( 78 | value.length == allotmentChain.length, 79 | 'p'); 80 | 81 | require( 82 | LR[1] >= LR[0], 83 | 's'); 84 | for (uint32 i = 0; i < value.length; i++) { 85 | bool linkLeft = false; // is the current chain link on the left of this node 86 | if (allotmentTrail > 0) { 87 | linkLeft = allotmentTrail.mod(2) == 1; 88 | allotmentTrail = allotmentTrail.div(2); 89 | } 90 | 91 | node = keccak256(abi.encodePacked( 92 | i, 93 | linkLeft ? value[i] : LR[0], // leftmost value 94 | keccak256(abi.encodePacked( 95 | linkLeft ? allotmentChain[i] : node, // left node 96 | linkLeft ? LR[0] : LR[1], // middle value 97 | linkLeft ? node : allotmentChain[i] // right node 98 | )), 99 | linkLeft ? LR[1] : value[i] // rightmost value 100 | )); 101 | 102 | require( 103 | linkLeft ? value[i] <= LR[0] : LR[1] <= value[i], 104 | 'x'); 105 | 106 | LR[0] = linkLeft ? value[i] : LR[0]; 107 | LR[1] = linkLeft ? LR[1] : value[i]; 108 | 109 | require( 110 | LR[1] >= LR[0], 111 | 't'); 112 | } 113 | require( 114 | LR[0] == 0, 115 | 'l'); 116 | 117 | node = keccak256(abi.encodePacked( 118 | LR[0], node, LR[1] 119 | )); 120 | 121 | require( 122 | verifyProofOfMembership(membershipTrail, membershipChain, node, root), 123 | 'm'); 124 | 125 | return LR[1]; 126 | } 127 | 128 | /** 129 | * Calculate an annotated merkle tree root from a combined array containing 130 | * the chain of hashes and sibling values with a fixed height starting from the 131 | * leaf node. 132 | */ 133 | function verifyProofOfPassiveDelivery( 134 | uint64 allotmentTrail, 135 | bytes32 node, 136 | bytes32 root, 137 | bytes32[] chainValues, 138 | uint256[2] LR 139 | ) 140 | public 141 | pure 142 | returns (uint256) 143 | { 144 | require( 145 | chainValues.length.mod(2) == 0, 146 | 'p'); 147 | 148 | require( 149 | LR[1] >= LR[0], 150 | 's'); 151 | uint32 v = uint32(chainValues.length.div(2)); 152 | for (uint32 i = 0; i < v; i++) { 153 | bool linkLeft = false; // is the current chain link on the left of this node 154 | if (allotmentTrail > 0) { 155 | linkLeft = allotmentTrail.mod(2) == 1; 156 | allotmentTrail = allotmentTrail.div(2); 157 | } 158 | 159 | node = keccak256(abi.encodePacked( 160 | i, 161 | linkLeft ? uint256(chainValues[i.add(v)]) : LR[0], // leftmost value 162 | keccak256(abi.encodePacked( 163 | linkLeft ? chainValues[i] : node, // left node 164 | linkLeft ? LR[0] : LR[1], // middle value 165 | linkLeft ? node : chainValues[i] // right node 166 | )), 167 | linkLeft ? LR[1] : uint256(chainValues[i.add(v)]) // rightmost value 168 | )); 169 | 170 | require( 171 | linkLeft ? uint256(chainValues[i.add(v)]) <= LR[0] : LR[1] <= uint256(chainValues[i.add(v)]), 172 | 'x'); 173 | 174 | LR[0] = linkLeft ? uint256(chainValues[i.add(v)]) : LR[0]; 175 | LR[1] = linkLeft ? LR[1] : uint256(chainValues[i.add(v)]); 176 | 177 | require( 178 | LR[1] >= LR[0], 179 | 't'); 180 | } 181 | require( 182 | LR[0] == 0, 183 | 'l'); 184 | 185 | require( 186 | node == root, 187 | 'n'); 188 | 189 | return LR[1]; 190 | } 191 | 192 | function transferChecksum( 193 | address counterparty, 194 | uint256 amount, 195 | uint64 recipientTrail, 196 | uint256 nonce 197 | ) 198 | public 199 | pure 200 | returns (bytes32) 201 | { 202 | return keccak256(abi.encodePacked( 203 | keccak256(abi.encodePacked(counterparty)), 204 | amount, 205 | recipientTrail, 206 | nonce)); 207 | } 208 | 209 | function swapOrderChecksum( 210 | ERC20[2] tokens, 211 | uint64 recipientTrail, 212 | uint256 sellAmount, 213 | uint256 buyAmount, 214 | uint256 startBalance, 215 | uint256 nonce 216 | ) 217 | public 218 | pure 219 | returns (bytes32) 220 | { 221 | return keccak256(abi.encodePacked( 222 | keccak256(abi.encodePacked(tokens[0])), 223 | keccak256(abi.encodePacked(tokens[1])), 224 | recipientTrail, 225 | sellAmount, 226 | buyAmount, 227 | startBalance, 228 | nonce)); 229 | } 230 | 231 | function activeStateUpdateChecksum( 232 | ERC20 token, 233 | address holder, 234 | uint64 trail, 235 | uint256 eon, 236 | bytes32 txSetRoot, 237 | uint256[2] deltas 238 | ) 239 | public 240 | view 241 | returns (bytes32) 242 | { 243 | return keccak256(abi.encodePacked( 244 | keccak256(abi.encodePacked(address(this))), 245 | keccak256(abi.encodePacked(token)), 246 | keccak256(abi.encodePacked(holder)), 247 | trail, 248 | eon, 249 | txSetRoot, 250 | deltas[0], 251 | deltas[1] 252 | )); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /contracts/MerkleVerifierProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./ERC20.sol"; 4 | import "./MerkleVerifier.sol"; 5 | 6 | contract MerkleVerifierProxy { 7 | function calculateMerkleRoot( 8 | uint256 trail, 9 | bytes32[] chain, 10 | bytes32 node 11 | ) 12 | public 13 | pure 14 | returns (bytes32) 15 | { 16 | return MerkleVerifier.calculateMerkleRoot(trail, chain, node); 17 | } 18 | 19 | function verifyProofOfExclusiveBalanceAllotment( 20 | uint64 allotmentTrail, 21 | uint64 membershipTrail, 22 | bytes32 node, 23 | bytes32 root, 24 | bytes32[] allotmentChain, 25 | bytes32[] membershipChain, 26 | uint256[] value, 27 | uint256[2] LR // solhint-disable-line func-param-name-mixedcase 28 | ) 29 | public 30 | pure 31 | returns (uint256) 32 | { 33 | return MerkleVerifier.verifyProofOfExclusiveBalanceAllotment( 34 | allotmentTrail, 35 | membershipTrail, 36 | node, 37 | root, 38 | allotmentChain, 39 | membershipChain, 40 | value, 41 | LR 42 | ); 43 | } 44 | 45 | function verifyProofOfPassiveDelivery( 46 | uint64 allotmentTrail, 47 | bytes32 node, 48 | bytes32 root, 49 | bytes32[] chainValues, 50 | uint256[2] LR // solhint-disable-line func-param-name-mixedcase 51 | ) 52 | public 53 | pure 54 | returns (uint256) 55 | { 56 | MerkleVerifier.verifyProofOfPassiveDelivery( 57 | allotmentTrail, 58 | node, 59 | root, 60 | chainValues, 61 | LR 62 | ); 63 | } 64 | 65 | function transferChecksum( 66 | address counterparty, 67 | uint256 amount, 68 | uint64 recipientTrail, 69 | uint256 nonce 70 | ) 71 | public 72 | pure 73 | returns (bytes32) 74 | { 75 | return MerkleVerifier.transferChecksum( 76 | counterparty, 77 | amount, 78 | recipientTrail, 79 | nonce 80 | ); 81 | } 82 | 83 | function swapOrderChecksum( 84 | ERC20[2] tokens, 85 | uint64 recipientTrail, 86 | uint256 sellAmount, 87 | uint256 buyAmount, 88 | uint256 startBalance, 89 | uint256 nonce 90 | ) 91 | public 92 | pure 93 | returns (bytes32) 94 | { 95 | return MerkleVerifier.swapOrderChecksum( 96 | tokens, 97 | recipientTrail, 98 | sellAmount, 99 | buyAmount, 100 | startBalance, 101 | nonce 102 | ); 103 | } 104 | 105 | function activeStateUpdateChecksum( 106 | ERC20 token, 107 | address holder, 108 | uint64 trail, 109 | uint256 eon, 110 | bytes32 txSetRoot, 111 | uint256[2] deltas 112 | ) 113 | public 114 | view 115 | returns (bytes32) 116 | { 117 | return MerkleVerifier.activeStateUpdateChecksum( 118 | token, 119 | holder, 120 | trail, 121 | eon, 122 | txSetRoot, 123 | deltas 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint256 public lastCompletedMigration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() 12 | public 13 | { 14 | owner = msg.sender; 15 | } 16 | 17 | function setCompleted(uint256 completed) 18 | public 19 | restricted 20 | { 21 | lastCompletedMigration = completed; 22 | } 23 | 24 | function upgrade(address newAddress) 25 | public 26 | restricted 27 | { 28 | Migrations upgraded = Migrations(newAddress); 29 | upgraded.setCompleted(lastCompletedMigration); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/NOCUSTCommitChain.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./BimodalProxy.sol"; 4 | import "./DepositProxy.sol"; 5 | import "./WithdrawalProxy.sol"; 6 | import "./ChallengeProxy.sol"; 7 | import "./RecoveryProxy.sol"; 8 | import "./SafeMath/SafeMathLib256.sol"; 9 | 10 | /** 11 | * This is the main Parent-chain Verifier contract. It inherits all of the proxy 12 | * contracts and provides a single address to interact with all the moderated 13 | * balance pools. Proxies define the exposed methods, events and enforce the 14 | * modifiers of library methods. 15 | */ 16 | contract NOCUSTCommitChain is 17 | BimodalProxy, 18 | DepositProxy, 19 | WithdrawalProxy, 20 | ChallengeProxy, 21 | RecoveryProxy { 22 | using SafeMathLib256 for uint256; 23 | 24 | /** 25 | * This is the main constructor. 26 | * @param blocksPerEon - The number of blocks per eon. 27 | * @param operator - The IMMUTABLE address of the operator. 28 | */ 29 | constructor(uint256 blocksPerEon, address operator) 30 | public 31 | BimodalProxy(blocksPerEon, operator) 32 | { 33 | // Support ETH by default 34 | ledger.trailToToken.push(address(this)); 35 | ledger.tokenToTrail[address(this)] = 0; 36 | } 37 | 38 | /** 39 | * This allows the operator to register the existence of another ERC20 token 40 | * @param token - ERC20 token address 41 | */ 42 | function registerERC20( 43 | ERC20 token 44 | ) 45 | public 46 | onlyOperator() 47 | { 48 | require(ledger.tokenToTrail[token] == 0); 49 | ledger.tokenToTrail[token] = uint64(ledger.trailToToken.length); 50 | ledger.trailToToken.push(token); 51 | } 52 | 53 | /** 54 | * This method allows the operator to submit one checkpoint per eon that 55 | * synchronizes the commit-chain ledger with the parent chain. 56 | * @param accumulator - The accumulator of the previous eon under which this checkpoint is calculated. 57 | * @param merkleRoot - The checkpoint merkle root. 58 | */ 59 | function submitCheckpoint( 60 | bytes32 accumulator, 61 | bytes32 merkleRoot 62 | ) 63 | public 64 | onlyOperator() 65 | onlyWhenContractUnpunished() 66 | { 67 | uint256 eon = ledger.currentEon(); 68 | require( 69 | ledger.parentChainAccumulator[eon.sub(1).mod(ledger.EONS_KEPT)] == accumulator, 70 | 'b'); 71 | require( 72 | ledger.getLiveChallenges(eon.sub(1)) == 0, 73 | 'c'); 74 | require( 75 | eon > ledger.lastSubmissionEon, 76 | 'd'); 77 | 78 | ledger.lastSubmissionEon = eon; 79 | 80 | BimodalLib.Checkpoint storage checkpoint = ledger.getOrCreateCheckpoint(eon, eon); 81 | checkpoint.merkleRoot = merkleRoot; 82 | 83 | emit CheckpointSubmission(eon, merkleRoot); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /contracts/RecoveryLib.sol: -------------------------------------------------------------------------------- 1 | /* solhint-disable func-order */ 2 | 3 | pragma solidity ^0.4.24; 4 | 5 | import "./ERC20.sol"; 6 | import "./BimodalLib.sol"; 7 | import "./ChallengeLib.sol"; 8 | import "./SafeMath/SafeMathLib256.sol"; 9 | 10 | /** 11 | * This library contains the implementation for the secure commit-chain recovery 12 | * procedure that can be used when the operator of the commit chain is halted by 13 | * the main verifier contract. The methods in this library are only relevant for 14 | * recovering the last confirmed balances of the accounts in the commit chain. 15 | */ 16 | library RecoveryLib { 17 | using SafeMathLib256 for uint256; 18 | using BimodalLib for BimodalLib.Ledger; 19 | 20 | function reclaimUncommittedDeposits( 21 | BimodalLib.Ledger storage ledger, 22 | BimodalLib.Wallet storage wallet 23 | ) 24 | private 25 | returns (uint256 amount) 26 | { 27 | for (uint8 i = 0; i < ledger.DEPOSITS_KEPT; i++) { 28 | BimodalLib.AmountAggregate storage depositAggregate = wallet.depositsKept[i]; 29 | // depositAggregate.eon < ledger.lastSubmissionEon.sub(1) 30 | if (depositAggregate.eon.add(1) < ledger.lastSubmissionEon) { 31 | continue; 32 | } 33 | amount = amount.add(depositAggregate.amount); 34 | BimodalLib.clearAggregate(depositAggregate); 35 | } 36 | } 37 | 38 | function reclaimFinalizedWithdrawal( 39 | BimodalLib.Ledger storage ledger, 40 | BimodalLib.Wallet storage wallet 41 | ) 42 | private 43 | returns (uint256 amount) 44 | { 45 | BimodalLib.Withdrawal[] storage withdrawals = wallet.withdrawals; 46 | for (uint32 i = 0; i < withdrawals.length; i++) { 47 | BimodalLib.Withdrawal storage withdrawal = withdrawals[i]; 48 | 49 | if (withdrawal.eon.add(2) > ledger.lastSubmissionEon) { 50 | break; 51 | } 52 | 53 | amount = amount.add(withdrawal.amount); 54 | delete withdrawals[i]; 55 | } 56 | } 57 | 58 | /* 59 | * This method can be called without an accompanying proof of exclusive allotment 60 | * to claim only the funds pending in the parent chain. 61 | */ 62 | function recoverOnlyParentChainFunds( 63 | BimodalLib.Ledger storage ledger, 64 | ERC20 token, 65 | address holder 66 | ) 67 | public 68 | /* onlyWhenContractPunished() */ 69 | returns (uint256 reclaimed) 70 | { 71 | BimodalLib.Wallet storage wallet = ledger.walletBook[token][holder]; 72 | 73 | reclaimed = reclaimUncommittedDeposits(ledger, wallet) 74 | .add(reclaimFinalizedWithdrawal(ledger, wallet)); 75 | 76 | if (ledger.lastSubmissionEon > 0) { 77 | BimodalLib.AmountAggregate storage eonWithdrawals = 78 | ledger.confirmedWithdrawals[token][ledger.lastSubmissionEon.sub(1).mod(ledger.EONS_KEPT)]; 79 | BimodalLib.addToAggregate(eonWithdrawals, ledger.lastSubmissionEon.sub(1), reclaimed); 80 | } 81 | 82 | if (token != address(this)) { 83 | require( 84 | ledger.tokenToTrail[token] != 0, 85 | 't'); 86 | require( 87 | token.transfer(holder, reclaimed), 88 | 'f'); 89 | } else { 90 | holder.transfer(reclaimed); 91 | } 92 | } 93 | 94 | /** 95 | * This method requires an accompanying proof of exclusive allotment to claim 96 | *the funds pending in the parent chain along with those exclusively allotted 97 | * in the commit chain. 98 | */ 99 | function recoverAllFunds( 100 | BimodalLib.Ledger storage ledger, 101 | ERC20 token, 102 | address holder, 103 | bytes32[2] checksums, 104 | uint64 trail, 105 | bytes32[] allotmentChain, 106 | bytes32[] membershipChain, 107 | uint256[] values, 108 | uint256[2] LR, // solhint-disable func-param-name-mixedcase 109 | uint256[3] dummyPassiveMark 110 | ) 111 | public 112 | /* onlyWhenContractPunished() */ 113 | returns (uint256 recovered) 114 | { 115 | BimodalLib.Wallet storage wallet = ledger.walletBook[token][holder]; 116 | require( 117 | !wallet.recovered, 118 | 'a'); 119 | wallet.recovered = true; 120 | 121 | recovered = LR[1].sub(LR[0]); // excluslive allotment 122 | recovered = recovered.add(reclaimUncommittedDeposits(ledger, wallet)); // unallotted parent chain deposits 123 | recovered = recovered.add(reclaimFinalizedWithdrawal(ledger, wallet)); // confirmed parent chain withdrawal 124 | 125 | if (ledger.lastSubmissionEon > 0) { 126 | dummyPassiveMark[0] = ledger.lastSubmissionEon.sub(1); // confirmedEon 127 | } else { 128 | dummyPassiveMark[0] = 0; 129 | } 130 | 131 | require(ChallengeLib.verifyProofOfExclusiveAccountBalanceAllotment( 132 | ledger, 133 | token, 134 | holder, 135 | checksums, 136 | trail, 137 | dummyPassiveMark, // [confirmedEon, passiveAmount, passiveMark] 138 | allotmentChain, 139 | membershipChain, 140 | values, 141 | LR), 142 | 'p'); 143 | 144 | BimodalLib.AmountAggregate storage eonWithdrawals = 145 | ledger.confirmedWithdrawals[token][dummyPassiveMark[0].mod(ledger.EONS_KEPT)]; 146 | BimodalLib.addToAggregate(eonWithdrawals, dummyPassiveMark[0], recovered); 147 | 148 | if (token != address(this)) { 149 | require( 150 | ledger.tokenToTrail[token] != 0, 151 | 't'); 152 | require( 153 | token.transfer(holder, recovered), 154 | 'f'); 155 | } else { 156 | holder.transfer(recovered); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /contracts/RecoveryProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./BimodalProxy.sol"; 4 | import "./ERC20.sol"; 5 | import "./RecoveryLib.sol"; 6 | import "./SafeMath/SafeMathLib256.sol"; 7 | 8 | contract RecoveryProxy is BimodalProxy { 9 | using SafeMathLib256 for uint256; 10 | 11 | modifier onlyWhenContractPunished() { 12 | require( 13 | hasOutstandingChallenges() || hasMissedCheckpointSubmission(), 14 | 'f'); 15 | _; 16 | } 17 | 18 | // ========================================================================= 19 | function recoverOnlyParentChainFunds( 20 | ERC20 token, 21 | address holder 22 | ) 23 | public 24 | onlyWhenContractPunished() 25 | returns (uint256 reclaimed) 26 | { 27 | reclaimed = RecoveryLib.recoverOnlyParentChainFunds( 28 | ledger, 29 | token, 30 | holder); 31 | } 32 | 33 | function recoverAllFunds( 34 | ERC20 token, 35 | address holder, 36 | bytes32[2] checksums, 37 | uint64 trail, 38 | bytes32[] allotmentChain, 39 | bytes32[] membershipChain, 40 | uint256[] values, 41 | uint256[2] LR, // solhint-disable-line func-param-name-mixedcase 42 | uint256[3] dummyPassiveMark 43 | ) 44 | public 45 | onlyWhenContractPunished() 46 | returns (uint256 recovered) 47 | { 48 | recovered = RecoveryLib.recoverAllFunds( 49 | ledger, 50 | token, 51 | holder, 52 | checksums, 53 | trail, 54 | allotmentChain, 55 | membershipChain, 56 | values, 57 | LR, 58 | dummyPassiveMark); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/SafeMath/SafeMathLib256.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /* Overflow safety library */ 4 | library SafeMathLib256 { 5 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 6 | uint256 c = a + b; 7 | require(c >= a, '+'); 8 | 9 | return c; 10 | } 11 | 12 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 13 | require(b <= a, '-'); 14 | return a - b; 15 | } 16 | 17 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 18 | if (a == 0) { 19 | return 0; 20 | } 21 | 22 | uint256 c = a * b; 23 | require(c / a == b, '*'); 24 | 25 | return c; 26 | } 27 | 28 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 29 | require(b > 0, '/'); 30 | return a / b; 31 | } 32 | 33 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 34 | require(b > 0, '%'); 35 | return a % b; 36 | } 37 | } -------------------------------------------------------------------------------- /contracts/SafeMath/SafeMathLib32.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | library SafeMathLib32 { 4 | function add(uint32 a, uint32 b) internal pure returns (uint32) { 5 | uint32 c = a + b; 6 | require(c >= a, '+'); 7 | 8 | return c; 9 | } 10 | 11 | function sub(uint32 a, uint32 b) internal pure returns (uint32) { 12 | require(b <= a, '-'); 13 | return a - b; 14 | } 15 | 16 | function mul(uint32 a, uint32 b) internal pure returns (uint32) { 17 | if (a == 0) { 18 | return 0; 19 | } 20 | 21 | uint32 c = a * b; 22 | require(c / a == b, '*'); 23 | 24 | return c; 25 | } 26 | 27 | function div(uint32 a, uint32 b) internal pure returns (uint32) { 28 | require(b > 0, '/'); 29 | return a / b; 30 | } 31 | 32 | function mod(uint32 a, uint32 b) internal pure returns (uint32) { 33 | require(b > 0, '%'); 34 | return a % b; 35 | } 36 | } -------------------------------------------------------------------------------- /contracts/SafeMath/SafeMathLib64.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | library SafeMathLib64 { 4 | function add(uint64 a, uint64 b) internal pure returns (uint64) { 5 | uint64 c = a + b; 6 | require(c >= a, '+'); 7 | 8 | return c; 9 | } 10 | 11 | function sub(uint64 a, uint64 b) internal pure returns (uint64) { 12 | require(b <= a, '-'); 13 | return a - b; 14 | } 15 | 16 | function mul(uint64 a, uint64 b) internal pure returns (uint64) { 17 | if (a == 0) { 18 | return 0; 19 | } 20 | 21 | uint64 c = a * b; 22 | require(c / a == b, '*'); 23 | 24 | return c; 25 | } 26 | 27 | function div(uint64 a, uint64 b) internal pure returns (uint64) { 28 | require(b > 0, '/'); 29 | return a / b; 30 | } 31 | 32 | function mod(uint64 a, uint64 b) internal pure returns (uint64) { 33 | require(b > 0, '%'); 34 | return a % b; 35 | } 36 | } -------------------------------------------------------------------------------- /contracts/WithdrawalLib.sol: -------------------------------------------------------------------------------- 1 | /* solhint-disable func-order */ 2 | 3 | pragma solidity ^0.4.24; 4 | 5 | import "./BimodalLib.sol"; 6 | import "./ChallengeLib.sol"; 7 | import "./SafeMath/SafeMathLib256.sol"; 8 | 9 | /* 10 | This library contains the implementations of the first NOCUST withdrawal 11 | procedures. 12 | */ 13 | library WithdrawalLib { 14 | using SafeMathLib256 for uint256; 15 | using BimodalLib for BimodalLib.Ledger; 16 | // EVENTS 17 | event WithdrawalRequest(address indexed token, address indexed requestor, uint256 amount); 18 | 19 | event WithdrawalConfirmation(address indexed token, address indexed requestor, uint256 amount); 20 | 21 | function initWithdrawal( 22 | BimodalLib.Ledger storage ledger, 23 | ERC20 token, 24 | address holder, 25 | uint256 eon, 26 | uint256 withdrawalAmount 27 | ) 28 | private 29 | /* onlyWhenContractUnpunished() */ 30 | { 31 | BimodalLib.Wallet storage entry = ledger.walletBook[token][holder]; 32 | 33 | uint256 balance = 0; 34 | if (token != address(this)) { 35 | require(ledger.tokenToTrail[token] != 0, 36 | 't'); 37 | balance = token.balanceOf(this); 38 | } else { 39 | balance = address(this).balance; 40 | } 41 | 42 | require(ledger.getPendingWithdrawalsAtEon(token, eon).add(withdrawalAmount) <= balance, 43 | 'b'); 44 | 45 | entry.withdrawals.push(BimodalLib.Withdrawal(eon, withdrawalAmount)); 46 | 47 | ledger.addToRunningPendingWithdrawals(token, eon, withdrawalAmount); 48 | 49 | ledger.appendOperationToEonAccumulator(eon, token, holder, BimodalLib.Operation.WITHDRAWAL, withdrawalAmount); 50 | 51 | emit WithdrawalRequest(token, holder, withdrawalAmount); 52 | } 53 | 54 | /** 55 | * This method can be called freely by a client to initiate a withdrawal in the 56 | * parent-chain that will take 2 eons to be confirmable only if the client can 57 | * provide a satisfying proof of exclusive allotment. 58 | */ 59 | function requestWithdrawal( 60 | BimodalLib.Ledger storage ledger, 61 | ERC20 token, 62 | bytes32[2] checksums, 63 | uint64 trail, 64 | bytes32[] allotmentChain, 65 | bytes32[] membershipChain, 66 | uint256[] values, 67 | uint256[2][2] lrPassiveMark, // Left, Right 68 | uint256 withdrawalAmount 69 | ) 70 | public 71 | /* payable */ 72 | /* onlyWithConstantReimbursement(100100) */ 73 | { 74 | uint256 available = lrPassiveMark[0][1].sub(lrPassiveMark[0][0]); 75 | uint256 eon = ledger.currentEon(); 76 | 77 | uint256 pending = ledger.getWalletPendingWithdrawalAmountAtEon(token, msg.sender, eon); 78 | require(available >= withdrawalAmount.add(pending), 79 | 'b'); 80 | 81 | require(ChallengeLib.verifyProofOfExclusiveAccountBalanceAllotment( 82 | ledger, 83 | token, 84 | msg.sender, 85 | checksums, 86 | trail, 87 | [eon.sub(1), lrPassiveMark[1][0], lrPassiveMark[1][1]], 88 | allotmentChain, 89 | membershipChain, 90 | values, 91 | lrPassiveMark[0]), 92 | 'p'); 93 | 94 | initWithdrawal(ledger, token, msg.sender, eon, withdrawalAmount); 95 | } 96 | 97 | /** 98 | * This method can be called by a client to initiate a withdrawal in the 99 | * parent-chain that will take 2 eons to be confirmable only if the client 100 | * provides a signature from the operator authorizing the initialization of this 101 | * withdrawal. 102 | */ 103 | function requestAuthorizedWithdrawal( 104 | BimodalLib.Ledger storage ledger, 105 | ERC20 token, 106 | uint256 withdrawalAmount, 107 | uint256 expiry, 108 | bytes32 r, bytes32 s, uint8 v 109 | ) 110 | public 111 | { 112 | requestDelegatedWithdrawal(ledger, token, msg.sender, withdrawalAmount, expiry, r, s, v); 113 | } 114 | 115 | /** 116 | * This method can be called by the operator to initiate a withdrawal in the 117 | * parent-chain that will take 2 eons to be confirmable only if the operator 118 | * provides a signature from the client authorizing the delegation of this 119 | * withdrawal initialization. 120 | */ 121 | function requestDelegatedWithdrawal( 122 | BimodalLib.Ledger storage ledger, 123 | ERC20 token, 124 | address holder, 125 | uint256 withdrawalAmount, 126 | uint256 expiry, 127 | bytes32 r, bytes32 s, uint8 v 128 | ) 129 | public 130 | /* onlyOperator() */ 131 | { 132 | require(block.number <= expiry); 133 | 134 | uint256 eon = ledger.currentEon(); 135 | uint256 pending = ledger.getWalletPendingWithdrawalAmountAtEon(token, holder, eon); 136 | 137 | require(ChallengeLib.verifyWithdrawalAuthorization( 138 | token, 139 | holder, 140 | expiry, 141 | withdrawalAmount.add(pending), 142 | holder, 143 | r, s, v)); 144 | 145 | initWithdrawal(ledger, token, holder, eon, withdrawalAmount); 146 | } 147 | 148 | /** 149 | * This method can be called to confirm the withdrawals of a recipient that have 150 | * been pending for at least two eons when the operator is not halted. 151 | */ 152 | function confirmWithdrawal( 153 | BimodalLib.Ledger storage ledger, 154 | ERC20 token, 155 | address recipient 156 | ) 157 | public 158 | /* onlyWhenContractUnpunished() */ 159 | returns (uint256 amount) 160 | { 161 | BimodalLib.Wallet storage entry = ledger.walletBook[token][recipient]; 162 | BimodalLib.Withdrawal[] storage withdrawals = entry.withdrawals; 163 | 164 | uint256 eon = ledger.currentEon(); 165 | amount = 0; 166 | 167 | /** 168 | * after the loop, i is set to the index of the first pending withdrawals 169 | * amount is the amount to be sent to the recipient 170 | */ 171 | uint32 i = 0; 172 | for (i = 0; i < withdrawals.length; i++) { 173 | BimodalLib.Withdrawal storage withdrawal = withdrawals[i]; 174 | if (withdrawal.eon.add(1) >= eon) { 175 | break; 176 | } else if (withdrawal.eon.add(2) == eon && ledger.currentEra() < ledger.EXTENDED_BLOCKS_PER_EPOCH) { 177 | break; 178 | } 179 | 180 | amount = amount.add(withdrawal.amount); 181 | } 182 | 183 | // set withdrawals to contain only pending withdrawal requests 184 | for (uint32 j = 0; j < i && i < withdrawals.length; j++) { 185 | withdrawals[j] = withdrawals[i]; 186 | i++; 187 | } 188 | withdrawals.length = withdrawals.length.sub(j); 189 | 190 | ledger.deductFromRunningPendingWithdrawals(token, eon, eon, amount); 191 | 192 | BimodalLib.AmountAggregate storage eonWithdrawals = ledger.confirmedWithdrawals[token][eon.mod(ledger.EONS_KEPT)]; 193 | BimodalLib.addToAggregate(eonWithdrawals, eon, amount); 194 | 195 | emit WithdrawalConfirmation(token, recipient, amount); 196 | 197 | // if token is not chain native asset 198 | if (token != address(this)) { 199 | require(ledger.tokenToTrail[token] != 0); 200 | require(token.transfer(recipient, amount)); 201 | } else { 202 | recipient.transfer(amount); 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /contracts/WithdrawalProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./BimodalProxy.sol"; 4 | import "./ERC20.sol"; 5 | import "./WithdrawalLib.sol"; 6 | import "./SafeMath/SafeMathLib256.sol"; 7 | 8 | contract WithdrawalProxy is BimodalProxy { 9 | using SafeMathLib256 for uint256; 10 | 11 | modifier onlyWithConstantReimbursement(uint256 responseGas) { 12 | require( 13 | msg.value >= responseGas.mul(ledger.MIN_CHALLENGE_GAS_COST) && 14 | msg.value >= responseGas.mul(tx.gasprice), 15 | 'r'); 16 | ledger.operator.transfer(msg.value); 17 | _; 18 | } 19 | 20 | // ========================================================================= 21 | function requestWithdrawal( 22 | ERC20 token, 23 | bytes32[2] checksums, 24 | uint64 trail, 25 | bytes32[] allotmentChain, 26 | bytes32[] membershipChain, 27 | uint256[] values, 28 | uint256[2][2] lrPassiveMark, 29 | uint256 withdrawalAmount 30 | ) 31 | public 32 | payable 33 | onlyWithConstantReimbursement(100100) 34 | onlyWhenContractUnpunished() 35 | { 36 | WithdrawalLib.requestWithdrawal( 37 | ledger, 38 | token, 39 | checksums, 40 | trail, 41 | allotmentChain, 42 | membershipChain, 43 | values, 44 | lrPassiveMark, 45 | withdrawalAmount); 46 | } 47 | 48 | function requestAuthorizedWithdrawal( 49 | ERC20 token, 50 | uint256 withdrawalAmount, 51 | uint256 expiry, 52 | bytes32 r, bytes32 s, uint8 v 53 | ) 54 | public 55 | onlyWhenContractUnpunished() 56 | { 57 | WithdrawalLib.requestAuthorizedWithdrawal( 58 | ledger, 59 | token, 60 | withdrawalAmount, 61 | expiry, 62 | r, 63 | s, 64 | v); 65 | } 66 | 67 | function requestDelegatedWithdrawal( 68 | ERC20 token, 69 | address holder, 70 | uint256 withdrawalAmount, 71 | uint256 expiry, 72 | bytes32 r, bytes32 s, uint8 v 73 | ) 74 | public 75 | onlyOperator() 76 | onlyWhenContractUnpunished() 77 | { 78 | WithdrawalLib.requestDelegatedWithdrawal( 79 | ledger, 80 | token, 81 | holder, 82 | withdrawalAmount, 83 | expiry, 84 | r, 85 | s, 86 | v); 87 | } 88 | 89 | function confirmWithdrawal( 90 | ERC20 token, 91 | address recipient 92 | ) 93 | public 94 | onlyWhenContractUnpunished() 95 | returns (uint256) 96 | { 97 | return WithdrawalLib.confirmWithdrawal( 98 | ledger, 99 | token, 100 | recipient); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_bimodal.js: -------------------------------------------------------------------------------- 1 | var BimodalLib = artifacts.require("BimodalLib"); 2 | 3 | module.exports = async function(deployer, network, accounts) { 4 | deployer.deploy(BimodalLib); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/3_deploy_verifier.js: -------------------------------------------------------------------------------- 1 | var MerkleVerifier = artifacts.require("MerkleVerifier"); 2 | var MerkleVerifierProxy = artifacts.require("MerkleVerifierProxy"); 3 | 4 | module.exports = async function(deployer, network, accounts) { 5 | await deployer.deploy(MerkleVerifier); 6 | deployer.link(MerkleVerifier, MerkleVerifierProxy); 7 | 8 | deployer.deploy(MerkleVerifierProxy); 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/4_deploy_challenges.js: -------------------------------------------------------------------------------- 1 | var BimodalLib = artifacts.require("BimodalLib"); 2 | var MerkleVerifier = artifacts.require("MerkleVerifier"); 3 | var ChallengeLib = artifacts.require("ChallengeLib"); 4 | 5 | module.exports = async function(deployer, network, accounts) { 6 | deployer.link(BimodalLib, ChallengeLib); 7 | deployer.link(MerkleVerifier, ChallengeLib); 8 | deployer.deploy(ChallengeLib); 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/5_deploy_withdrawals.js: -------------------------------------------------------------------------------- 1 | var BimodalLib = artifacts.require("BimodalLib"); 2 | var ChallengeLib = artifacts.require("ChallengeLib"); 3 | var WithdrawalLib = artifacts.require("WithdrawalLib"); 4 | 5 | module.exports = async function(deployer, network, accounts) { 6 | deployer.link(BimodalLib, WithdrawalLib); 7 | deployer.link(ChallengeLib, WithdrawalLib); 8 | deployer.deploy(WithdrawalLib); 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/6_deploy_recovery.js: -------------------------------------------------------------------------------- 1 | var BimodalLib = artifacts.require("BimodalLib"); 2 | var ChallengeLib = artifacts.require("ChallengeLib"); 3 | var RecoveryLib = artifacts.require("RecoveryLib"); 4 | 5 | module.exports = async function(deployer, network, accounts) { 6 | deployer.link(BimodalLib, RecoveryLib); 7 | deployer.link(ChallengeLib, RecoveryLib); 8 | deployer.deploy(RecoveryLib); 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/7_deploy_deposits.js: -------------------------------------------------------------------------------- 1 | var BimodalLib = artifacts.require("BimodalLib"); 2 | var DepositLib = artifacts.require("DepositLib"); 3 | 4 | module.exports = async function(deployer, network, accounts) { 5 | deployer.link(BimodalLib, DepositLib); 6 | deployer.deploy(DepositLib); 7 | }; 8 | -------------------------------------------------------------------------------- /migrations/8_deploy_paymenthub.js: -------------------------------------------------------------------------------- 1 | var BimodalLib = artifacts.require("BimodalLib"); 2 | var MerkleVerifier = artifacts.require("MerkleVerifier"); 3 | var ChallengeLib = artifacts.require("ChallengeLib"); 4 | var WithdrawalLib = artifacts.require("WithdrawalLib"); 5 | var RecoveryLib = artifacts.require("RecoveryLib"); 6 | var DepositLib = artifacts.require("DepositLib"); 7 | var NOCUSTCommitChain = artifacts.require("NOCUSTCommitChain"); 8 | 9 | module.exports = async function(deployer, network, accounts) { 10 | let hubAccount = accounts[0]; 11 | let blocksPerEon = -1; 12 | if (network === 'development') { 13 | blocksPerEon = 180; 14 | } else if (network === 'ropsten') { 15 | blocksPerEon = 180; 16 | } else if (network === 'live') { 17 | blocksPerEon = 4320; 18 | } 19 | 20 | if (blocksPerEon === -1) { 21 | console.log("Unkown Network.") 22 | return; 23 | } 24 | 25 | console.log('================') 26 | console.log(`Using ${network} deployment configuration..`) 27 | console.log(`BLOCKS_PER_EON: ${blocksPerEon}`) 28 | console.log('================') 29 | 30 | // ganache-cli -d --allowUnlimitedContractSize --gasLimit 8000000 31 | deployer.link(BimodalLib, NOCUSTCommitChain); 32 | deployer.link(MerkleVerifier, NOCUSTCommitChain); 33 | deployer.link(ChallengeLib, NOCUSTCommitChain); 34 | deployer.link(WithdrawalLib, NOCUSTCommitChain); 35 | deployer.link(RecoveryLib, NOCUSTCommitChain); 36 | deployer.link(DepositLib, NOCUSTCommitChain); 37 | 38 | deployer.deploy( 39 | NOCUSTCommitChain, 40 | blocksPerEon, 41 | hubAccount); 42 | }; 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nocust-contracts-solidity", 3 | "version": "0.8.0", 4 | "description": "", 5 | "main": "truffle.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "clean": "rm -rf coverage", 11 | "compile": "truffle compile", 12 | "console": "truffle console", 13 | "coverage": "npm run clean && MODE=coverage truffle test; istanbul report html", 14 | "documentation": "soldoc --in contracts -o docs", 15 | "lint": "solhint --config .solhint.json contracts/*.sol contracts/**/*.sol", 16 | "test": "truffle test", 17 | "trace": "MODE=trace truffle test" 18 | }, 19 | "author": "Rami Khalil ", 20 | "license": "GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007", 21 | "dependencies": { 22 | "truffle": "^5.0.8", 23 | "web3": "^1.0.0-beta.48", 24 | "web3-eth-accounts": "^1.0.0-beta.46", 25 | "web3-utils": "^1.0.0-beta.35" 26 | }, 27 | "devDependencies": { 28 | "@0x/sol-coverage": "^3.0.3", 29 | "@0x/sol-profiler": "^3.1.3", 30 | "@0x/sol-trace": "^2.0.9", 31 | "@0x/subproviders": "^4.0.5", 32 | "@soldoc/soldoc": "^0.4.3", 33 | "istanbul": "^0.4.5", 34 | "solc": "^0.4.24", 35 | "solhint": "^2.0.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "localhost", 5 | port: 8545, 6 | network_id: "*" // Match any network id 7 | }, 8 | ropsten: { 9 | host: "localhost", 10 | port: 8545, 11 | network_id: 3 12 | }, 13 | live: { 14 | host: "localhost", 15 | port: 8545, 16 | network_id: 1 17 | } 18 | }, 19 | compilers: { 20 | solc: { 21 | version: "0.4.24" 22 | } 23 | }, 24 | solc: { 25 | optimizer: { 26 | enabled: true, 27 | runs: 200 28 | } 29 | } 30 | }; 31 | --------------------------------------------------------------------------------