├── .gitattributes ├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .solcover.js ├── .soliumignore ├── .soliumrc.json ├── README.md ├── contracts ├── Deployer.sol ├── DepositManager.sol ├── FraudProof.sol ├── Governance.sol ├── IncrementalTree.sol ├── MerkleTreeUtils.sol ├── Migrations.sol ├── NameRegistry.sol ├── POB.sol ├── TestToken.sol ├── TokenRegistry.sol ├── interfaces │ ├── IERC20.sol │ ├── IFraudProof.sol │ └── ITokenRegistry.sol ├── libs │ ├── ECVerify.sol │ ├── ParamManager.sol │ ├── RollupUtils.sol │ ├── Tx.sol │ └── Types.sol ├── logger.sol ├── rollup.sol ├── test │ └── TestTx.sol └── withdrawManager.sol ├── migrations ├── 1_initial_migration.js └── 2_deploy.js ├── package-lock.json ├── package.json ├── scripts ├── FillDepositSubtree.js └── helpers │ ├── interfaces.ts │ ├── utils.ts │ └── wallet.ts ├── test-blockchain ├── clean.sh ├── config.toml ├── keys │ └── PrivatePoA │ │ ├── UTC--2018-02-19T10-26-21.638675000Z--9fb29aac15b9a4b7f17c3385939b007540f4d791 │ │ ├── UTC--2018-02-19T10-26-22.638675000Z--96c42c56fdb78294f96b0cfa33c92bed7d75f96a │ │ ├── UTC--2019-06-14T06-56-42Z--fcb0cece-1d59-82de-cea0-4e7ba2da6a86 │ │ └── address_book.json ├── node.password ├── poa-spec.json ├── start.sh └── stop.sh ├── test ├── Deposit.spec.ts ├── Rollup.spec.ts ├── fixtures │ ├── Fill-Deposit-Subtree.spec.ts │ └── Single-Deposit.spec.ts ├── merkalization │ ├── IncrementalTree.spec.ts │ └── MerkleTree.spec.ts ├── tx.ts └── utils │ └── rollup-utils.spec.ts ├── truffle-config.js └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | 3 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [10.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm ci 28 | - run: npm run lint 29 | - run: npm run generate 30 | - run: npm run actions 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *node_modules/* 2 | logs 3 | contractAddresses.json 4 | types 5 | build 6 | test-blockchain/data 7 | .env 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | test-blockchain/ 2 | types/ 3 | .github/ 4 | .solcover.js 5 | truffle-config.js 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": ["*.sol", "*.ts", "*.js"], 5 | "options": { 6 | "printWidth": 80, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "singleQuote": false, 10 | "bracketSpacing": true, 11 | "explicitTypes": "always" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | providerOptions: { 3 | port: 8555, 4 | gasPrice: 0, 5 | blockTime: 1, 6 | mnemonic: 'clock radar mass judge dismiss just intact mind resemble fringe diary casino', 7 | total_accounts: 10 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | contracts/Migrations.sol 3 | -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:recommended", 3 | "plugins": [ 4 | "security" 5 | ], 6 | "rules": { 7 | "quotes": [ 8 | "error", 9 | "double" 10 | ], 11 | "indentation": [ 12 | "error", 13 | 4 14 | ], 15 | "linebreak-style": [ 16 | "error", 17 | "unix" 18 | ] 19 | } 20 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hubble-Project 2 | 3 | > Watch your assets move at light speed! 4 | 5 | The hubble project is a framework to create programmable rollups with mass migration capabilities. 6 | 7 | ## Compile 8 | 9 | ```bash 10 | $ npm run compile 11 | ``` 12 | 13 | ## Migrate 14 | 15 | ```bash 16 | $ npm run migrate 17 | ``` 18 | 19 | Post running migrations you can see a `contractAddresses.json` file with all the addresses for all deployed contracts. 20 | 21 | ## Run tests 22 | 23 | ```bash 24 | $ npm run generate 25 | $ npm run ganache 26 | $ npm run test 27 | ``` 28 | 29 | NOTE: Please use node v10.13.0 30 | -------------------------------------------------------------------------------- /contracts/Deployer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | import { ParamManager } from "./libs/ParamManager.sol"; 3 | import { NameRegistry as Registry } from "./NameRegistry.sol"; 4 | import { IncrementalTree } from "./IncrementalTree.sol"; 5 | import { DepositManager } from "./DepositManager.sol"; 6 | import { TestToken } from "./TestToken.sol"; 7 | import { Rollup } from "./rollup.sol"; 8 | import { TokenRegistry } from "./TokenRegistry.sol"; 9 | import { Logger } from "./logger.sol"; 10 | import { MerkleTreeUtils as MTUtils } from "./MerkleTreeUtils.sol"; 11 | import { Governance } from "./Governance.sol"; 12 | 13 | // Deployer is supposed to deploy new set of contracts while setting up all the utilities 14 | // libraries and other auxiallry contracts like registry 15 | contract Deployer { 16 | constructor( 17 | address nameregistry, 18 | uint256 maxDepth, 19 | uint256 maxDepositSubTree 20 | ) public { 21 | deployContracts(nameregistry, maxDepth, maxDepositSubTree); 22 | } 23 | 24 | function deployContracts( 25 | address nameRegistryAddr, 26 | uint256 maxDepth, 27 | uint256 maxDepositSubTree 28 | ) public returns (address) { 29 | Registry registry = Registry(nameRegistryAddr); 30 | address governance = address( 31 | new Governance(maxDepth, maxDepositSubTree) 32 | ); 33 | require( 34 | registry.registerName(ParamManager.Governance(), governance), 35 | "Could not register governance" 36 | ); 37 | address mtUtils = address(new MTUtils(nameRegistryAddr)); 38 | require( 39 | registry.registerName(ParamManager.MERKLE_UTILS(), mtUtils), 40 | "Could not register merkle utils tree" 41 | ); 42 | 43 | address logger = address(new Logger()); 44 | require( 45 | registry.registerName(ParamManager.LOGGER(), logger), 46 | "Cannot register logger" 47 | ); 48 | 49 | address tokenRegistry = address(new TokenRegistry(nameRegistryAddr)); 50 | require( 51 | registry.registerName(ParamManager.TOKEN_REGISTRY(), tokenRegistry), 52 | "Cannot register token registry" 53 | ); 54 | 55 | return nameRegistryAddr; 56 | 57 | // deploy accounts tree 58 | // address accountsTree = address(new IncrementalTree(nameRegistryAddr)); 59 | // require( 60 | // registry.registerName(ParamManager.ACCOUNTS_TREE(), accountsTree), 61 | // "Could not register accounts tree" 62 | // ); 63 | 64 | // deposit manager 65 | // address depositManager = address(new DepositManager(nameRegistryAddr)); 66 | // require( 67 | // registry.registerName( 68 | // ParamManager.DEPOSIT_MANAGER(), 69 | // depositManager 70 | // ), 71 | // "Cannot register deposit manager" 72 | // ); 73 | 74 | // // deploy core rollup contract 75 | // address rollup = address(new Rollup(nameRegistryAddr)); 76 | // require( 77 | // registry.registerName(ParamManager.ROLLUP_CORE(), rollup), 78 | // "Cannot register core rollup" 79 | // ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /contracts/DepositManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | pragma experimental ABIEncoderV2; 3 | import { IncrementalTree } from "./IncrementalTree.sol"; 4 | import { Types } from "./libs/Types.sol"; 5 | import { Logger } from "./logger.sol"; 6 | import { RollupUtils } from "./libs/RollupUtils.sol"; 7 | import { MerkleTreeUtils as MTUtils } from "./MerkleTreeUtils.sol"; 8 | import { NameRegistry as Registry } from "./NameRegistry.sol"; 9 | import { ITokenRegistry } from "./interfaces/ITokenRegistry.sol"; 10 | import { IERC20 } from "./interfaces/IERC20.sol"; 11 | import { ParamManager } from "./libs/ParamManager.sol"; 12 | import { POB } from "./POB.sol"; 13 | import { Governance } from "./Governance.sol"; 14 | import { Rollup } from "./rollup.sol"; 15 | 16 | contract DepositManager { 17 | MTUtils public merkleUtils; 18 | Registry public nameRegistry; 19 | bytes32[] public pendingDeposits; 20 | mapping(uint256 => bytes32) pendingFilledSubtrees; 21 | uint256 public firstElement = 1; 22 | uint256 public lastElement = 0; 23 | bytes32 24 | public constant ZERO_BYTES32 = 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563; 25 | uint256 public depositSubTreesPackaged = 0; 26 | 27 | function enqueue(bytes32 newDepositSubtree) public { 28 | lastElement += 1; 29 | pendingFilledSubtrees[lastElement] = newDepositSubtree; 30 | depositSubTreesPackaged++; 31 | } 32 | 33 | function dequeue() public returns (bytes32 depositSubtreeRoot) { 34 | require(lastElement >= firstElement); // non-empty queue 35 | depositSubtreeRoot = pendingFilledSubtrees[firstElement]; 36 | delete pendingFilledSubtrees[firstElement]; 37 | firstElement += 1; 38 | depositSubTreesPackaged--; 39 | } 40 | 41 | uint256 public queueNumber; 42 | uint256 public depositSubtreeHeight; 43 | Governance public governance; 44 | Logger public logger; 45 | ITokenRegistry public tokenRegistry; 46 | IERC20 public tokenContract; 47 | IncrementalTree public accountsTree; 48 | modifier onlyCoordinator() { 49 | POB pobContract = POB( 50 | nameRegistry.getContractDetails(ParamManager.POB()) 51 | ); 52 | assert(msg.sender == pobContract.getCoordinator()); 53 | _; 54 | } 55 | 56 | modifier onlyRollup() { 57 | assert( 58 | msg.sender == 59 | nameRegistry.getContractDetails(ParamManager.ROLLUP_CORE()) 60 | ); 61 | _; 62 | } 63 | 64 | constructor(address _registryAddr) public { 65 | nameRegistry = Registry(_registryAddr); 66 | governance = Governance( 67 | nameRegistry.getContractDetails(ParamManager.Governance()) 68 | ); 69 | merkleUtils = MTUtils( 70 | nameRegistry.getContractDetails(ParamManager.MERKLE_UTILS()) 71 | ); 72 | tokenRegistry = ITokenRegistry( 73 | nameRegistry.getContractDetails(ParamManager.TOKEN_REGISTRY()) 74 | ); 75 | logger = Logger(nameRegistry.getContractDetails(ParamManager.LOGGER())); 76 | accountsTree = IncrementalTree( 77 | nameRegistry.getContractDetails(ParamManager.ACCOUNTS_TREE()) 78 | ); 79 | 80 | AddCoordinatorLeaves(); 81 | } 82 | 83 | function AddCoordinatorLeaves() internal { 84 | // first 2 leaves belong to coordinator 85 | accountsTree.appendLeaf(ZERO_BYTES32); 86 | accountsTree.appendLeaf(ZERO_BYTES32); 87 | } 88 | 89 | /** 90 | * @notice Adds a deposit for the msg.sender to the deposit queue 91 | * @param _amount Number of tokens that user wants to deposit 92 | * @param _tokenType Type of token user is depositing 93 | */ 94 | function deposit( 95 | uint256 _amount, 96 | uint256 _tokenType, 97 | bytes memory _pubkey 98 | ) public { 99 | depositFor(msg.sender, _amount, _tokenType, _pubkey); 100 | } 101 | 102 | /** 103 | * @notice Adds a deposit for an address to the deposit queue 104 | * @param _destination Address for which we are depositing 105 | * @param _amount Number of tokens that user wants to deposit 106 | * @param _tokenType Type of token user is depositing 107 | */ 108 | function depositFor( 109 | address _destination, 110 | uint256 _amount, 111 | uint256 _tokenType, 112 | bytes memory _pubkey 113 | ) public { 114 | // check amount is greater than 0 115 | require(_amount > 0, "token deposit must be greater than 0"); 116 | 117 | // ensure public matches the destination address 118 | require( 119 | _destination == RollupUtils.calculateAddress(_pubkey), 120 | "public key and address don't match" 121 | ); 122 | 123 | // check token type exists 124 | address tokenContractAddress = tokenRegistry.registeredTokens( 125 | _tokenType 126 | ); 127 | tokenContract = IERC20(tokenContractAddress); 128 | 129 | // transfer from msg.sender to this contract 130 | require( 131 | tokenContract.transferFrom(msg.sender, address(this), _amount), 132 | "token transfer not approved" 133 | ); 134 | 135 | // returns leaf index upon successfull append 136 | uint256 accID = accountsTree.appendDataBlock(_pubkey); 137 | 138 | // create a new account 139 | Types.UserAccount memory newAccount; 140 | newAccount.balance = _amount; 141 | newAccount.tokenType = _tokenType; 142 | newAccount.nonce = 0; 143 | newAccount.ID = accID; 144 | 145 | // get new account hash 146 | bytes memory accountBytes = RollupUtils.BytesFromAccount(newAccount); 147 | 148 | // queue the deposit 149 | pendingDeposits.push(keccak256(accountBytes)); 150 | 151 | // emit the event 152 | logger.logDepositQueued(accID, _pubkey, accountBytes); 153 | 154 | queueNumber++; 155 | uint256 tmpDepositSubtreeHeight = 0; 156 | uint256 tmp = queueNumber; 157 | while (tmp % 2 == 0) { 158 | bytes32[] memory deposits = new bytes32[](2); 159 | deposits[0] = pendingDeposits[pendingDeposits.length - 2]; 160 | deposits[1] = pendingDeposits[pendingDeposits.length - 1]; 161 | 162 | pendingDeposits[pendingDeposits.length - 2] = merkleUtils.getParent( 163 | deposits[0], 164 | deposits[1] 165 | ); 166 | 167 | // remove 1 deposit from the pending deposit queue 168 | removeDeposit(pendingDeposits.length - 1); 169 | tmp = tmp / 2; 170 | 171 | // update the temp deposit subtree height 172 | tmpDepositSubtreeHeight++; 173 | 174 | // thow event for the coordinator 175 | logger.logDepositLeafMerged( 176 | deposits[0], 177 | deposits[1], 178 | pendingDeposits[0] 179 | ); 180 | } 181 | 182 | if (tmpDepositSubtreeHeight > depositSubtreeHeight) { 183 | depositSubtreeHeight = tmpDepositSubtreeHeight; 184 | } 185 | 186 | if (depositSubtreeHeight == governance.MAX_DEPOSIT_SUBTREE()) { 187 | // start adding deposits to prepackaged deposit subtree root queue 188 | enqueue(pendingDeposits[0]); 189 | 190 | // emit an event to signal that a package is ready 191 | // isnt really important for anyone tho 192 | logger.logDepositSubTreeReady(pendingDeposits[0]); 193 | 194 | // update the number of items in pendingDeposits 195 | queueNumber = queueNumber - 2**depositSubtreeHeight; 196 | 197 | // empty the pending deposits queue 198 | removeDeposit(0); 199 | 200 | // reset deposit subtree height 201 | depositSubtreeHeight = 0; 202 | } 203 | } 204 | 205 | /** 206 | * @notice Merges the deposit tree with the balance tree by 207 | * superimposing the deposit subtree on the balance tree 208 | * @param _subTreeDepth Deposit tree depth or depth of subtree that is being deposited 209 | * @param _zero_account_mp Merkle proof proving the node at which we are inserting the deposit subtree consists of all empty leaves 210 | * @return Updates in-state merkle tree root 211 | */ 212 | function finaliseDeposits( 213 | uint256 _subTreeDepth, 214 | Types.AccountMerkleProof memory _zero_account_mp, 215 | bytes32 latestBalanceTree 216 | ) public onlyRollup returns (bytes32) { 217 | bytes32 emptySubtreeRoot = merkleUtils.getRoot(_subTreeDepth); 218 | 219 | // from mt proof we find the root of the tree 220 | // we match the root to the balance tree root on-chain 221 | bool isValid = merkleUtils.verifyLeaf( 222 | latestBalanceTree, 223 | emptySubtreeRoot, 224 | _zero_account_mp.accountIP.pathToAccount, 225 | _zero_account_mp.siblings 226 | ); 227 | 228 | require(isValid, "proof invalid"); 229 | 230 | // just dequeue from the pre package deposit subtrees 231 | bytes32 depositsSubTreeRoot = dequeue(); 232 | 233 | // emit the event 234 | logger.logDepositFinalised( 235 | depositsSubTreeRoot, 236 | _zero_account_mp.accountIP.pathToAccount 237 | ); 238 | 239 | // return the updated merkle tree root 240 | return (depositsSubTreeRoot); 241 | } 242 | 243 | /** 244 | * @notice Removes a deposit from the pendingDeposits queue and shifts the queue 245 | * @param _index Index of the element to remove 246 | * @return Remaining elements of the array 247 | */ 248 | function removeDeposit(uint256 _index) internal { 249 | require( 250 | _index < pendingDeposits.length, 251 | "array index is out of bounds" 252 | ); 253 | 254 | // if we want to nuke the queue 255 | if (_index == 0) { 256 | uint256 numberOfDeposits = pendingDeposits.length; 257 | for (uint256 i = 0; i < numberOfDeposits; i++) { 258 | delete pendingDeposits[i]; 259 | } 260 | pendingDeposits.length = 0; 261 | return; 262 | } 263 | 264 | if (_index == pendingDeposits.length - 1) { 265 | delete pendingDeposits[pendingDeposits.length - 1]; 266 | pendingDeposits.length--; 267 | return; 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /contracts/FraudProof.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; 5 | 6 | import { IERC20 } from "./interfaces/IERC20.sol"; 7 | import { ITokenRegistry } from "./interfaces/ITokenRegistry.sol"; 8 | 9 | import { Types } from "./libs/Types.sol"; 10 | import { RollupUtils } from "./libs/RollupUtils.sol"; 11 | import { ParamManager } from "./libs/ParamManager.sol"; 12 | import { ECVerify } from "./libs/ECVerify.sol"; 13 | 14 | import { MerkleTreeUtils as MTUtils } from "./MerkleTreeUtils.sol"; 15 | import { Governance } from "./Governance.sol"; 16 | import { NameRegistry as Registry } from "./NameRegistry.sol"; 17 | 18 | contract FraudProofSetup { 19 | using SafeMath for uint256; 20 | using ECVerify for bytes32; 21 | 22 | MTUtils public merkleUtils; 23 | ITokenRegistry public tokenRegistry; 24 | Registry public nameRegistry; 25 | 26 | bytes32 27 | public constant ZERO_BYTES32 = 0x0000000000000000000000000000000000000000000000000000000000000000; 28 | 29 | Governance public governance; 30 | } 31 | 32 | contract FraudProofHelpers is FraudProofSetup { 33 | function ValidatePubkeyAvailability( 34 | bytes32 _accountsRoot, 35 | Types.PDAMerkleProof memory _from_pda_proof, 36 | uint256 from_index 37 | ) public view { 38 | // verify from account pubkey exists in PDA tree 39 | // NOTE: We dont need to prove that to address has the pubkey available 40 | Types.PDALeaf memory fromPDA = Types.PDALeaf({ 41 | pubkey: _from_pda_proof._pda.pubkey_leaf.pubkey 42 | }); 43 | 44 | require( 45 | merkleUtils.verifyLeaf( 46 | _accountsRoot, 47 | RollupUtils.PDALeafToHash(fromPDA), 48 | _from_pda_proof._pda.pathToPubkey, 49 | _from_pda_proof.siblings 50 | ), 51 | "From PDA proof is incorrect" 52 | ); 53 | 54 | // convert pubkey path to ID 55 | uint256 computedID = merkleUtils.pathToIndex( 56 | _from_pda_proof._pda.pathToPubkey, 57 | governance.MAX_DEPTH() 58 | ); 59 | 60 | // make sure the ID in transaction is the same account for which account proof was provided 61 | require( 62 | computedID == from_index, 63 | "Pubkey not related to the from account in the transaction" 64 | ); 65 | } 66 | 67 | function ValidateAccountMP( 68 | bytes32 root, 69 | Types.AccountMerkleProof memory merkle_proof 70 | ) public view { 71 | bytes32 accountLeaf = RollupUtils.getAccountHash( 72 | merkle_proof.accountIP.account.ID, 73 | merkle_proof.accountIP.account.balance, 74 | merkle_proof.accountIP.account.nonce, 75 | merkle_proof.accountIP.account.tokenType 76 | ); 77 | 78 | // verify from leaf exists in the balance tree 79 | require( 80 | merkleUtils.verifyLeaf( 81 | root, 82 | accountLeaf, 83 | merkle_proof.accountIP.pathToAccount, 84 | merkle_proof.siblings 85 | ), 86 | "Merkle Proof is incorrect" 87 | ); 88 | } 89 | 90 | function validateTxBasic( 91 | Types.Transaction memory _tx, 92 | Types.UserAccount memory _from_account 93 | ) public view returns (Types.ErrorCode) { 94 | // verify that tokens are registered 95 | if (tokenRegistry.registeredTokens(_tx.tokenType) == address(0)) { 96 | // invalid state transition 97 | // to be slashed because the submitted transaction 98 | // had invalid token type 99 | return Types.ErrorCode.InvalidTokenAddress; 100 | } 101 | 102 | if (_tx.amount == 0) { 103 | // invalid state transition 104 | // needs to be slashed because the submitted transaction 105 | // had 0 amount. 106 | return Types.ErrorCode.InvalidTokenAmount; 107 | } 108 | 109 | // check from leaf has enough balance 110 | if (_from_account.balance < _tx.amount) { 111 | // invalid state transition 112 | // needs to be slashed because the account doesnt have enough balance 113 | // for the transfer 114 | return Types.ErrorCode.NotEnoughTokenBalance; 115 | } 116 | 117 | return Types.ErrorCode.NoError; 118 | } 119 | 120 | function RemoveTokensFromAccount( 121 | Types.UserAccount memory account, 122 | uint256 numOfTokens 123 | ) public pure returns (Types.UserAccount memory updatedAccount) { 124 | return ( 125 | RollupUtils.UpdateBalanceInAccount( 126 | account, 127 | RollupUtils.BalanceFromAccount(account).sub(numOfTokens) 128 | ) 129 | ); 130 | } 131 | 132 | /** 133 | * @notice ApplyTx applies the transaction on the account. This is where 134 | * people need to define the logic for the application 135 | * @param _merkle_proof contains the siblings and path to the account 136 | * @param transaction is the transaction that needs to be applied 137 | * @return returns updated account and updated state root 138 | * */ 139 | function ApplyTx( 140 | Types.AccountMerkleProof memory _merkle_proof, 141 | Types.Transaction memory transaction 142 | ) public view returns (bytes memory updatedAccount, bytes32 newRoot) { 143 | Types.UserAccount memory account = _merkle_proof.accountIP.account; 144 | if (transaction.fromIndex == account.ID) { 145 | account = RemoveTokensFromAccount(account, transaction.amount); 146 | account.nonce++; 147 | } 148 | 149 | if (transaction.toIndex == account.ID) { 150 | account = AddTokensToAccount(account, transaction.amount); 151 | } 152 | 153 | newRoot = UpdateAccountWithSiblings(account, _merkle_proof); 154 | 155 | return (RollupUtils.BytesFromAccount(account), newRoot); 156 | } 157 | 158 | function AddTokensToAccount( 159 | Types.UserAccount memory account, 160 | uint256 numOfTokens 161 | ) public pure returns (Types.UserAccount memory updatedAccount) { 162 | return ( 163 | RollupUtils.UpdateBalanceInAccount( 164 | account, 165 | RollupUtils.BalanceFromAccount(account).add(numOfTokens) 166 | ) 167 | ); 168 | } 169 | 170 | /** 171 | * @notice Returns the updated root and balance 172 | */ 173 | function UpdateAccountWithSiblings( 174 | Types.UserAccount memory new_account, 175 | Types.AccountMerkleProof memory _merkle_proof 176 | ) public view returns (bytes32) { 177 | bytes32 newRoot = merkleUtils.updateLeafWithSiblings( 178 | keccak256(RollupUtils.BytesFromAccount(new_account)), 179 | _merkle_proof.accountIP.pathToAccount, 180 | _merkle_proof.siblings 181 | ); 182 | return (newRoot); 183 | } 184 | 185 | function ValidateSignature( 186 | Types.Transaction memory _tx, 187 | Types.PDAMerkleProof memory _from_pda_proof 188 | ) public pure returns (bool) { 189 | require( 190 | RollupUtils.calculateAddress( 191 | _from_pda_proof._pda.pubkey_leaf.pubkey 192 | ) == 193 | RollupUtils 194 | .getTxSignBytes( 195 | _tx 196 | .fromIndex, 197 | _tx 198 | .toIndex, 199 | _tx 200 | .tokenType, 201 | _tx 202 | .txType, 203 | _tx 204 | .nonce, 205 | _tx 206 | .amount 207 | ) 208 | .ecrecovery(_tx.signature), 209 | "Signature is incorrect" 210 | ); 211 | } 212 | } 213 | 214 | contract FraudProof is FraudProofHelpers { 215 | /********************* 216 | * Constructor * 217 | ********************/ 218 | constructor(address _registryAddr) public { 219 | nameRegistry = Registry(_registryAddr); 220 | 221 | governance = Governance( 222 | nameRegistry.getContractDetails(ParamManager.Governance()) 223 | ); 224 | 225 | merkleUtils = MTUtils( 226 | nameRegistry.getContractDetails(ParamManager.MERKLE_UTILS()) 227 | ); 228 | 229 | tokenRegistry = ITokenRegistry( 230 | nameRegistry.getContractDetails(ParamManager.TOKEN_REGISTRY()) 231 | ); 232 | } 233 | 234 | function generateTxRoot(Types.Transaction[] memory _txs) 235 | public 236 | view 237 | returns (bytes32 txRoot) 238 | { 239 | // generate merkle tree from the txs provided by user 240 | bytes[] memory txs = new bytes[](_txs.length); 241 | for (uint256 i = 0; i < _txs.length; i++) { 242 | txs[i] = RollupUtils.CompressTx(_txs[i]); 243 | } 244 | txRoot = merkleUtils.getMerkleRoot(txs); 245 | return txRoot; 246 | } 247 | 248 | /** 249 | * @notice processBatch processes a whole batch 250 | * @return returns updatedRoot, txRoot and if the batch is valid or not 251 | * */ 252 | function processBatch( 253 | bytes32 stateRoot, 254 | bytes32 accountsRoot, 255 | Types.Transaction[] memory _txs, 256 | Types.BatchValidationProofs memory batchProofs, 257 | bytes32 expectedTxRoot 258 | ) 259 | public 260 | view 261 | returns ( 262 | bytes32, 263 | bytes32, 264 | bool 265 | ) 266 | { 267 | bytes32 actualTxRoot = generateTxRoot(_txs); 268 | // if there is an expectation set, revert if it's not met 269 | if (expectedTxRoot == ZERO_BYTES32) { 270 | // if tx root while submission doesnt match tx root of given txs 271 | // dispute is unsuccessful 272 | require( 273 | actualTxRoot == expectedTxRoot, 274 | "Invalid dispute, tx root doesn't match" 275 | ); 276 | } 277 | 278 | bool isTxValid; 279 | { 280 | for (uint256 i = 0; i < _txs.length; i++) { 281 | // call process tx update for every transaction to check if any 282 | // tx evaluates correctly 283 | (stateRoot, , , , isTxValid) = processTx( 284 | stateRoot, 285 | accountsRoot, 286 | _txs[i], 287 | batchProofs.pdaProof[i], 288 | batchProofs.accountProofs[i] 289 | ); 290 | 291 | if (!isTxValid) { 292 | break; 293 | } 294 | } 295 | } 296 | return (stateRoot, actualTxRoot, !isTxValid); 297 | } 298 | 299 | /** 300 | * @notice processTx processes a transactions and returns the updated balance tree 301 | * and the updated leaves 302 | * conditions in require mean that the dispute be declared invalid 303 | * if conditons evaluate if the coordinator was at fault 304 | * @return Total number of batches submitted onchain 305 | */ 306 | function processTx( 307 | bytes32 _balanceRoot, 308 | bytes32 _accountsRoot, 309 | Types.Transaction memory _tx, 310 | Types.PDAMerkleProof memory _from_pda_proof, 311 | Types.AccountProofs memory accountProofs 312 | ) 313 | public 314 | view 315 | returns ( 316 | bytes32, 317 | bytes memory, 318 | bytes memory, 319 | Types.ErrorCode, 320 | bool 321 | ) 322 | { 323 | // Step-1 Prove that from address's public keys are available 324 | ValidatePubkeyAvailability( 325 | _accountsRoot, 326 | _from_pda_proof, 327 | _tx.fromIndex 328 | ); 329 | 330 | // STEP:2 Ensure the transaction has been signed using the from public key 331 | // ValidateSignature(_tx, _from_pda_proof); 332 | 333 | // Validate the from account merkle proof 334 | ValidateAccountMP(_balanceRoot, accountProofs.from); 335 | 336 | Types.ErrorCode err_code = validateTxBasic( 337 | _tx, 338 | accountProofs.from.accountIP.account 339 | ); 340 | if (err_code != Types.ErrorCode.NoError) 341 | return (ZERO_BYTES32, "", "", err_code, false); 342 | 343 | // account holds the token type in the tx 344 | if (accountProofs.from.accountIP.account.tokenType != _tx.tokenType) 345 | // invalid state transition 346 | // needs to be slashed because the submitted transaction 347 | // had invalid token type 348 | return ( 349 | ZERO_BYTES32, 350 | "", 351 | "", 352 | Types.ErrorCode.BadFromTokenType, 353 | false 354 | ); 355 | 356 | bytes32 newRoot; 357 | bytes memory new_from_account; 358 | bytes memory new_to_account; 359 | 360 | (new_from_account, newRoot) = ApplyTx(accountProofs.from, _tx); 361 | 362 | // validate if leaf exists in the updated balance tree 363 | ValidateAccountMP(newRoot, accountProofs.to); 364 | 365 | // account holds the token type in the tx 366 | if (accountProofs.to.accountIP.account.tokenType != _tx.tokenType) 367 | // invalid state transition 368 | // needs to be slashed because the submitted transaction 369 | // had invalid token type 370 | return ( 371 | ZERO_BYTES32, 372 | "", 373 | "", 374 | Types.ErrorCode.BadToTokenType, 375 | false 376 | ); 377 | 378 | (new_to_account, newRoot) = ApplyTx(accountProofs.to, _tx); 379 | 380 | return ( 381 | newRoot, 382 | new_from_account, 383 | new_to_account, 384 | Types.ErrorCode.NoError, 385 | true 386 | ); 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /contracts/Governance.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | 3 | /* 4 | Governance contract handles all the proof of burn related functionality 5 | */ 6 | contract Governance { 7 | constructor(uint256 maxDepth, uint256 maxDepositSubTree) public { 8 | _MAX_DEPTH = maxDepth; 9 | _MAX_DEPOSIT_SUBTREE = maxDepositSubTree; 10 | } 11 | 12 | uint256 public _MAX_DEPTH = 4; 13 | 14 | function MAX_DEPTH() public view returns (uint256) { 15 | return _MAX_DEPTH; 16 | } 17 | 18 | uint256 public _MAX_DEPOSIT_SUBTREE = 2; 19 | 20 | function MAX_DEPOSIT_SUBTREE() public view returns (uint256) { 21 | return _MAX_DEPOSIT_SUBTREE; 22 | } 23 | 24 | // finalisation time is the number of blocks required by a batch to finalise 25 | // Delay period = 7 days. Block time = 15 seconds 26 | uint256 public _TIME_TO_FINALISE = 7 days; 27 | 28 | function TIME_TO_FINALISE() public view returns (uint256) { 29 | return _TIME_TO_FINALISE; 30 | } 31 | 32 | // min gas required before rollback pauses 33 | uint256 public _MIN_GAS_LIMIT_LEFT = 100000; 34 | 35 | function MIN_GAS_LIMIT_LEFT() public view returns (uint256) { 36 | return _MIN_GAS_LIMIT_LEFT; 37 | } 38 | 39 | uint256 public _MAX_TXS_PER_BATCH = 10; 40 | 41 | function MAX_TXS_PER_BATCH() public view returns (uint256) { 42 | return _MAX_TXS_PER_BATCH; 43 | } 44 | 45 | uint256 public _STAKE_AMOUNT = 32 ether; 46 | 47 | function STAKE_AMOUNT() public view returns (uint256) { 48 | return _STAKE_AMOUNT; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/IncrementalTree.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | 3 | import { MerkleTreeUtils as MTUtils } from "./MerkleTreeUtils.sol"; 4 | import { ParamManager } from "./libs/ParamManager.sol"; 5 | import { NameRegistry as Registry } from "./NameRegistry.sol"; 6 | import { Governance } from "./Governance.sol"; 7 | import { Logger } from "./logger.sol"; 8 | import { RollupUtils } from "./libs/RollupUtils.sol"; 9 | 10 | contract IncrementalTree { 11 | Registry public nameRegistry; 12 | MTUtils public merkleUtils; 13 | Governance public governance; 14 | MerkleTree public tree; 15 | Logger public logger; 16 | // Merkle Tree to store the whole tree 17 | struct MerkleTree { 18 | // Root of the tree 19 | bytes32 root; 20 | // current height of the tree 21 | uint256 height; 22 | // Allows you to compute the path to the element (but it's not the path to 23 | // the elements). Caching these values is essential to efficient appends. 24 | bytes32[] filledSubtrees; 25 | } 26 | 27 | // The number of inserted leaves 28 | uint256 public nextLeafIndex = 0; 29 | 30 | constructor(address _registryAddr) public { 31 | nameRegistry = Registry(_registryAddr); 32 | merkleUtils = MTUtils( 33 | nameRegistry.getContractDetails(ParamManager.MERKLE_UTILS()) 34 | ); 35 | governance = Governance( 36 | nameRegistry.getContractDetails(ParamManager.Governance()) 37 | ); 38 | 39 | logger = Logger(nameRegistry.getContractDetails(ParamManager.LOGGER())); 40 | tree.filledSubtrees = new bytes32[](governance.MAX_DEPTH()); 41 | setMerkleRootAndHeight( 42 | merkleUtils.getZeroRoot(), 43 | merkleUtils.getMaxTreeDepth() 44 | ); 45 | bytes32 zero = merkleUtils.getDefaultHashAtLevel(0); 46 | for (uint8 i = 1; i < governance.MAX_DEPTH(); i++) { 47 | tree.filledSubtrees[i] = zero; 48 | } 49 | } 50 | 51 | function appendDataBlock(bytes memory datablock) public returns (uint256) { 52 | bytes32 _leaf = keccak256(abi.encode(datablock)); 53 | uint256 accID = appendLeaf(_leaf); 54 | logger.logNewPubkeyAdded(accID, datablock); 55 | return accID; 56 | } 57 | 58 | /** 59 | * @notice Append leaf will append a leaf to the end of the tree 60 | * @return The sibling nodes along the way. 61 | */ 62 | function appendLeaf(bytes32 _leaf) public returns (uint256) { 63 | uint256 currentIndex = nextLeafIndex; 64 | uint256 depth = uint256(tree.height); 65 | require( 66 | currentIndex < uint256(2)**depth, 67 | "IncrementalMerkleTree: tree is full" 68 | ); 69 | bytes32 currentLevelHash = _leaf; 70 | bytes32 left; 71 | bytes32 right; 72 | for (uint8 i = 0; i < tree.height; i++) { 73 | if (currentIndex % 2 == 0) { 74 | left = currentLevelHash; 75 | right = merkleUtils.getRoot(i); 76 | tree.filledSubtrees[i] = currentLevelHash; 77 | } else { 78 | left = tree.filledSubtrees[i]; 79 | right = currentLevelHash; 80 | } 81 | currentLevelHash = merkleUtils.getParent(left, right); 82 | currentIndex >>= 1; 83 | } 84 | tree.root = currentLevelHash; 85 | uint256 n; 86 | n = nextLeafIndex; 87 | nextLeafIndex += 1; 88 | return n; 89 | } 90 | 91 | /** 92 | * @notice Set the tree root and height of the stored tree 93 | * @param _root The merkle root of the tree 94 | * @param _height The height of the tree 95 | */ 96 | function setMerkleRootAndHeight(bytes32 _root, uint256 _height) public { 97 | tree.root = _root; 98 | tree.height = _height; 99 | } 100 | 101 | function getTreeRoot() external view returns (bytes32) { 102 | return tree.root; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /contracts/MerkleTreeUtils.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | pragma experimental ABIEncoderV2; 3 | import { ParamManager } from "./libs/ParamManager.sol"; 4 | import { Governance } from "./Governance.sol"; 5 | import { NameRegistry as Registry } from "./NameRegistry.sol"; 6 | 7 | contract MerkleTreeUtils { 8 | // The default hashes 9 | bytes32[] public defaultHashes; 10 | uint256 public MAX_DEPTH; 11 | Governance public governance; 12 | 13 | /** 14 | * @notice Initialize a new MerkleTree contract, computing the default hashes for the merkle tree (MT) 15 | */ 16 | constructor(address _registryAddr) public { 17 | Registry nameRegistry = Registry(_registryAddr); 18 | governance = Governance( 19 | nameRegistry.getContractDetails(ParamManager.Governance()) 20 | ); 21 | MAX_DEPTH = governance.MAX_DEPTH(); 22 | defaultHashes = new bytes32[](MAX_DEPTH); 23 | // Calculate & set the default hashes 24 | setDefaultHashes(MAX_DEPTH); 25 | } 26 | 27 | /* Methods */ 28 | 29 | /** 30 | * @notice Set default hashes 31 | */ 32 | function setDefaultHashes(uint256 depth) internal { 33 | // Set the initial default hash. 34 | defaultHashes[0] = keccak256(abi.encode(0)); 35 | for (uint256 i = 1; i < depth; i++) { 36 | defaultHashes[i] = keccak256( 37 | abi.encode(defaultHashes[i - 1], defaultHashes[i - 1]) 38 | ); 39 | } 40 | } 41 | 42 | function getZeroRoot() public view returns (bytes32) { 43 | return 44 | keccak256( 45 | abi.encode( 46 | defaultHashes[MAX_DEPTH - 1], 47 | defaultHashes[MAX_DEPTH - 1] 48 | ) 49 | ); 50 | } 51 | 52 | function getMaxTreeDepth() public view returns (uint256) { 53 | return MAX_DEPTH; 54 | } 55 | 56 | function getRoot(uint256 index) public view returns (bytes32) { 57 | return defaultHashes[index]; 58 | } 59 | 60 | function getDefaultHashAtLevel(uint256 index) 61 | public 62 | view 63 | returns (bytes32) 64 | { 65 | return defaultHashes[index]; 66 | } 67 | 68 | function keecakHash(bytes memory data) public pure returns (bytes32) { 69 | return keccak256(data); 70 | } 71 | 72 | /** 73 | * @notice Get the merkle root computed from some set of data blocks. 74 | * @param _dataBlocks The data being used to generate the tree. 75 | * @return the merkle tree root 76 | * NOTE: This is a stateless operation 77 | */ 78 | function getMerkleRoot(bytes[] calldata _dataBlocks) 79 | external 80 | view 81 | returns (bytes32) 82 | { 83 | uint256 nextLevelLength = _dataBlocks.length; 84 | uint256 currentLevel = 0; 85 | bytes32[] memory nodes = new bytes32[](nextLevelLength + 1); // Add one in case we have an odd number of leaves 86 | // Generate the leaves 87 | for (uint256 i = 0; i < _dataBlocks.length; i++) { 88 | nodes[i] = keccak256(_dataBlocks[i]); 89 | } 90 | if (_dataBlocks.length == 1) { 91 | return nodes[0]; 92 | } 93 | // Add a defaultNode if we've got an odd number of leaves 94 | if (nextLevelLength % 2 == 1) { 95 | nodes[nextLevelLength] = defaultHashes[currentLevel]; 96 | nextLevelLength += 1; 97 | } 98 | 99 | // Now generate each level 100 | while (nextLevelLength > 1) { 101 | currentLevel += 1; 102 | // Calculate the nodes for the currentLevel 103 | for (uint256 i = 0; i < nextLevelLength / 2; i++) { 104 | nodes[i] = getParent(nodes[i * 2], nodes[i * 2 + 1]); 105 | } 106 | nextLevelLength = nextLevelLength / 2; 107 | // Check if we will need to add an extra node 108 | if (nextLevelLength % 2 == 1 && nextLevelLength != 1) { 109 | nodes[nextLevelLength] = defaultHashes[currentLevel]; 110 | nextLevelLength += 1; 111 | } 112 | } 113 | // Alright! We should be left with a single node! Return it... 114 | return nodes[0]; 115 | } 116 | 117 | /** 118 | * @notice Get the merkle root computed from some set of data blocks. 119 | * @param nodes The data being used to generate the tree. 120 | * @return the merkle tree root 121 | * NOTE: This is a stateless operation 122 | */ 123 | function getMerkleRootFromLeaves(bytes32[] memory nodes) 124 | public 125 | view 126 | returns (bytes32) 127 | { 128 | uint256 nextLevelLength = nodes.length; 129 | uint256 currentLevel = 0; 130 | if (nodes.length == 1) { 131 | return nodes[0]; 132 | } 133 | 134 | // Add a defaultNode if we've got an odd number of leaves 135 | if (nextLevelLength % 2 == 1) { 136 | nodes[nextLevelLength] = defaultHashes[currentLevel]; 137 | nextLevelLength += 1; 138 | } 139 | 140 | // Now generate each level 141 | while (nextLevelLength > 1) { 142 | currentLevel += 1; 143 | 144 | // Calculate the nodes for the currentLevel 145 | for (uint256 i = 0; i < nextLevelLength / 2; i++) { 146 | nodes[i] = getParent(nodes[i * 2], nodes[i * 2 + 1]); 147 | } 148 | 149 | nextLevelLength = nextLevelLength / 2; 150 | // Check if we will need to add an extra node 151 | if (nextLevelLength % 2 == 1 && nextLevelLength != 1) { 152 | nodes[nextLevelLength] = defaultHashes[currentLevel]; 153 | nextLevelLength += 1; 154 | } 155 | } 156 | 157 | // Alright! We should be left with a single node! Return it... 158 | return nodes[0]; 159 | } 160 | 161 | /** 162 | * @notice Calculate root from an inclusion proof. 163 | * @param _dataBlock The data block we're calculating root for. 164 | * @param _path The path from the leaf to the root. 165 | * @param _siblings The sibling nodes along the way. 166 | * @return The next level of the tree 167 | * NOTE: This is a stateless operation 168 | */ 169 | function computeInclusionProofRoot( 170 | bytes memory _dataBlock, 171 | uint256 _path, 172 | bytes32[] memory _siblings 173 | ) public pure returns (bytes32) { 174 | // First compute the leaf node 175 | bytes32 computedNode = keccak256(_dataBlock); 176 | 177 | for (uint256 i = 0; i < _siblings.length; i++) { 178 | bytes32 sibling = _siblings[i]; 179 | uint8 isComputedRightSibling = getNthBitFromRight(_path, i); 180 | if (isComputedRightSibling == 0) { 181 | computedNode = getParent(computedNode, sibling); 182 | } else { 183 | computedNode = getParent(sibling, computedNode); 184 | } 185 | } 186 | // Check if the computed node (_root) is equal to the provided root 187 | return computedNode; 188 | } 189 | 190 | /** 191 | * @notice Calculate root from an inclusion proof. 192 | * @param _leaf The data block we're calculating root for. 193 | * @param _path The path from the leaf to the root. 194 | * @param _siblings The sibling nodes along the way. 195 | * @return The next level of the tree 196 | * NOTE: This is a stateless operation 197 | */ 198 | function computeInclusionProofRootWithLeaf( 199 | bytes32 _leaf, 200 | uint256 _path, 201 | bytes32[] memory _siblings 202 | ) public pure returns (bytes32) { 203 | // First compute the leaf node 204 | bytes32 computedNode = _leaf; 205 | for (uint256 i = 0; i < _siblings.length; i++) { 206 | bytes32 sibling = _siblings[i]; 207 | uint8 isComputedRightSibling = getNthBitFromRight(_path, i); 208 | if (isComputedRightSibling == 0) { 209 | computedNode = getParent(computedNode, sibling); 210 | } else { 211 | computedNode = getParent(sibling, computedNode); 212 | } 213 | } 214 | // Check if the computed node (_root) is equal to the provided root 215 | return computedNode; 216 | } 217 | 218 | /** 219 | * @notice Verify an inclusion proof. 220 | * @param _root The root of the tree we are verifying inclusion for. 221 | * @param _dataBlock The data block we're verifying inclusion for. 222 | * @param _path The path from the leaf to the root. 223 | * @param _siblings The sibling nodes along the way. 224 | * @return The next level of the tree 225 | * NOTE: This is a stateless operation 226 | */ 227 | function verify( 228 | bytes32 _root, 229 | bytes memory _dataBlock, 230 | uint256 _path, 231 | bytes32[] memory _siblings 232 | ) public pure returns (bool) { 233 | // First compute the leaf node 234 | bytes32 calculatedRoot = computeInclusionProofRoot( 235 | _dataBlock, 236 | _path, 237 | _siblings 238 | ); 239 | return calculatedRoot == _root; 240 | } 241 | 242 | /** 243 | * @notice Verify an inclusion proof. 244 | * @param _root The root of the tree we are verifying inclusion for. 245 | * @param _leaf The data block we're verifying inclusion for. 246 | * @param _path The path from the leaf to the root. 247 | * @param _siblings The sibling nodes along the way. 248 | * @return The next level of the tree 249 | * NOTE: This is a stateless operation 250 | */ 251 | function verifyLeaf( 252 | bytes32 _root, 253 | bytes32 _leaf, 254 | uint256 _path, 255 | bytes32[] memory _siblings 256 | ) public pure returns (bool) { 257 | bytes32 calculatedRoot = computeInclusionProofRootWithLeaf( 258 | _leaf, 259 | _path, 260 | _siblings 261 | ); 262 | return calculatedRoot == _root; 263 | } 264 | 265 | /** 266 | * @notice Update a leaf using siblings and root 267 | * This is a stateless operation 268 | * @param _leaf The leaf we're updating. 269 | * @param _path The path from the leaf to the root / the index of the leaf. 270 | * @param _siblings The sibling nodes along the way. 271 | * @return Updated root 272 | */ 273 | function updateLeafWithSiblings( 274 | bytes32 _leaf, 275 | uint256 _path, 276 | bytes32[] memory _siblings 277 | ) public pure returns (bytes32) { 278 | bytes32 computedNode = _leaf; 279 | for (uint256 i = 0; i < _siblings.length; i++) { 280 | bytes32 parent; 281 | bytes32 sibling = _siblings[i]; 282 | uint8 isComputedRightSibling = getNthBitFromRight(_path, i); 283 | if (isComputedRightSibling == 0) { 284 | parent = getParent(computedNode, sibling); 285 | } else { 286 | parent = getParent(sibling, computedNode); 287 | } 288 | computedNode = parent; 289 | } 290 | return computedNode; 291 | } 292 | 293 | /** 294 | * @notice Get the parent of two children nodes in the tree 295 | * @param _left The left child 296 | * @param _right The right child 297 | * @return The parent node 298 | */ 299 | function getParent(bytes32 _left, bytes32 _right) 300 | public 301 | pure 302 | returns (bytes32) 303 | { 304 | return keccak256(abi.encode(_left, _right)); 305 | } 306 | 307 | /** 308 | * @notice get the n'th bit in a uint. 309 | * For instance, if exampleUint=binary(11), getNth(exampleUint, 0) == 1, getNth(2, 1) == 1 310 | * @param _intVal The uint we are extracting a bit out of 311 | * @param _index The index of the bit we want to extract 312 | * @return The bit (1 or 0) in a uint8 313 | */ 314 | function getNthBitFromRight(uint256 _intVal, uint256 _index) 315 | public 316 | pure 317 | returns (uint8) 318 | { 319 | return uint8((_intVal >> _index) & 1); 320 | } 321 | 322 | /** 323 | * @notice Get the right sibling key. Note that these keys overwrite the first bit of the hash 324 | to signify if it is on the right side of the parent or on the left 325 | * @param _parent The parent node 326 | * @return the key for the left sibling (0 as the first bit) 327 | */ 328 | function getLeftSiblingKey(bytes32 _parent) public pure returns (bytes32) { 329 | return 330 | _parent & 331 | 0x0111111111111111111111111111111111111111111111111111111111111111; 332 | } 333 | 334 | /** 335 | * @notice Get the right sibling key. Note that these keys overwrite the first bit of the hash 336 | to signify if it is on the right side of the parent or on the left 337 | * @param _parent The parent node 338 | * @return the key for the right sibling (1 as the first bit) 339 | */ 340 | function getRightSiblingKey(bytes32 _parent) public pure returns (bytes32) { 341 | return 342 | _parent | 343 | 0x1000000000000000000000000000000000000000000000000000000000000000; 344 | } 345 | 346 | function pathToIndex(uint256 path, uint256 height) 347 | public 348 | pure 349 | returns (uint256) 350 | { 351 | uint256 result = 0; 352 | for (uint256 i = 0; i < height; i++) { 353 | uint8 temp = getNthBitFromRight(path, i); 354 | // UNSAFE FIX THIS 355 | result = result + (temp * (2**i)); 356 | } 357 | return result; 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint256 public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint256 completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/NameRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | 3 | contract NameRegistry { 4 | struct ContractDetails { 5 | // registered contract address 6 | address contractAddress; 7 | } 8 | event RegisteredNewContract(bytes32 name, address contractAddr); 9 | mapping(bytes32 => ContractDetails) registry; 10 | 11 | function registerName(bytes32 name, address addr) external returns (bool) { 12 | ContractDetails memory info = registry[name]; 13 | // create info if it doesn't exist in the registry 14 | if (info.contractAddress == address(0)) { 15 | info.contractAddress = addr; 16 | registry[name] = info; 17 | // added to registry 18 | return true; 19 | } else { 20 | // already was registered 21 | return false; 22 | } 23 | } 24 | 25 | function getContractDetails(bytes32 name) external view returns (address) { 26 | return (registry[name].contractAddress); 27 | } 28 | 29 | function updateContractDetails(bytes32 name, address addr) external { 30 | // TODO not sure if we should do this 31 | // If we do we need a plan on how to remove this 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/POB.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | 3 | /* 4 | POB contract handles all the proof of burn related functionality 5 | */ 6 | contract POB { 7 | address public coordinator; 8 | 9 | constructor() public { 10 | coordinator = msg.sender; 11 | } 12 | 13 | function getCoordinator() public view returns (address) { 14 | return coordinator; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/TestToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | 3 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 4 | import "@openzeppelin/contracts/ownership/Ownable.sol"; 5 | 6 | /** 7 | * @title TestToken is a basic ERC20 Token 8 | */ 9 | contract TestToken is ERC20, Ownable { 10 | /** 11 | * @dev assign totalSupply to account creating this contract */ 12 | constructor() public { 13 | _mint(msg.sender, 10000000000000000000000); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/TokenRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21; 2 | 3 | import { Logger } from "./logger.sol"; 4 | import { NameRegistry as Registry } from "./NameRegistry.sol"; 5 | import { ParamManager } from "./libs/ParamManager.sol"; 6 | import { POB } from "./POB.sol"; 7 | 8 | contract TokenRegistry { 9 | address public rollupNC; 10 | Logger public logger; 11 | mapping(address => bool) public pendingRegistrations; 12 | mapping(uint256 => address) public registeredTokens; 13 | 14 | uint256 public numTokens; 15 | 16 | modifier onlyCoordinator() { 17 | POB pobContract = POB( 18 | nameRegistry.getContractDetails(ParamManager.POB()) 19 | ); 20 | assert(msg.sender == pobContract.getCoordinator()); 21 | _; 22 | } 23 | Registry public nameRegistry; 24 | 25 | constructor(address _registryAddr) public { 26 | nameRegistry = Registry(_registryAddr); 27 | 28 | logger = Logger(nameRegistry.getContractDetails(ParamManager.LOGGER())); 29 | } 30 | 31 | /** 32 | * @notice Requests addition of a new token to the chain, can be called by anyone 33 | * @param tokenContract Address for the new token being added 34 | */ 35 | function requestTokenRegistration(address tokenContract) public { 36 | require( 37 | pendingRegistrations[tokenContract] == false, 38 | "Token already registered." 39 | ); 40 | pendingRegistrations[tokenContract] = true; 41 | logger.logRegistrationRequest(tokenContract); 42 | } 43 | 44 | /** 45 | * @notice Add new tokens to the rollup chain by assigning them an ID called tokenType from here on 46 | * @param tokenContract Deposit tree depth or depth of subtree that is being deposited 47 | * TODO: add a modifier to allow only coordinator 48 | */ 49 | function finaliseTokenRegistration(address tokenContract) public { 50 | require( 51 | pendingRegistrations[tokenContract], 52 | "Token was not registered" 53 | ); 54 | numTokens++; 55 | registeredTokens[numTokens] = tokenContract; // tokenType => token contract address 56 | logger.logRegisteredToken(numTokens, tokenContract); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | 3 | // ERC20 token interface 4 | contract IERC20 { 5 | function transferFrom( 6 | address from, 7 | address to, 8 | uint256 value 9 | ) public returns (bool) {} 10 | 11 | function transfer(address recipient, uint256 value) public returns (bool) {} 12 | } 13 | -------------------------------------------------------------------------------- /contracts/interfaces/IFraudProof.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import { Types } from "../libs/Types.sol"; 5 | 6 | interface IFraudProof { 7 | function processTx( 8 | bytes32 _balanceRoot, 9 | bytes32 _accountsRoot, 10 | Types.Transaction calldata _tx, 11 | Types.PDAMerkleProof calldata _from_pda_proof, 12 | Types.AccountProofs calldata accountProofs 13 | ) 14 | external 15 | view 16 | returns ( 17 | bytes32, 18 | bytes memory, 19 | bytes memory, 20 | Types.ErrorCode, 21 | bool 22 | ); 23 | 24 | function processBatch( 25 | bytes32 initialStateRoot, 26 | bytes32 accountsRoot, 27 | Types.Transaction[] calldata _txs, 28 | Types.BatchValidationProofs calldata batchProofs, 29 | bytes32 expectedTxRoot 30 | ) 31 | external 32 | view 33 | returns ( 34 | bytes32, 35 | bytes32, 36 | bool 37 | ); 38 | 39 | function ApplyTx( 40 | Types.AccountMerkleProof calldata _merkle_proof, 41 | Types.Transaction calldata transaction 42 | ) external view returns (bytes memory, bytes32 newRoot); 43 | } 44 | -------------------------------------------------------------------------------- /contracts/interfaces/ITokenRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | 3 | // token registry contract interface 4 | contract ITokenRegistry { 5 | uint256 public numTokens; 6 | mapping(address => bool) public pendingRegistrations; 7 | mapping(uint256 => address) public registeredTokens; 8 | 9 | function requestTokenRegistration(address tokenContract) public {} 10 | 11 | function finaliseTokenRegistration(address tokenContract) public {} 12 | } 13 | -------------------------------------------------------------------------------- /contracts/libs/ECVerify.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | 3 | library ECVerify { 4 | function ecrecovery(bytes32 hash, bytes memory sig) 5 | public 6 | pure 7 | returns (address) 8 | { 9 | bytes32 r; 10 | bytes32 s; 11 | uint8 v; 12 | 13 | if (sig.length != 65) { 14 | return address(0x0); 15 | } 16 | 17 | assembly { 18 | r := mload(add(sig, 32)) 19 | s := mload(add(sig, 64)) 20 | v := and(mload(add(sig, 65)), 255) 21 | } 22 | 23 | // https://github.com/ethereum/go-ethereum/issues/2053 24 | if (v < 27) { 25 | v += 27; 26 | } 27 | 28 | if (v != 27 && v != 28) { 29 | return address(0x0); 30 | } 31 | 32 | // get address out of hash and signature 33 | address result = ecrecover(hash, v, r, s); 34 | 35 | // ecrecover returns zero on error 36 | require(result != address(0x0)); 37 | 38 | return result; 39 | } 40 | 41 | function ecrecovery( 42 | bytes32 hash, 43 | uint8 v, 44 | bytes32 r, 45 | bytes32 s 46 | ) public pure returns (address) { 47 | // get address out of hash and signature 48 | address result = ecrecover(hash, v, r, s); 49 | 50 | // ecrecover returns zero on error 51 | require(result != address(0x0)); 52 | 53 | return result; 54 | } 55 | 56 | function ecverify( 57 | bytes32 hash, 58 | bytes memory sig, 59 | address signer 60 | ) public pure returns (bool) { 61 | return signer == ecrecovery(hash, sig); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /contracts/libs/ParamManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | 3 | library ParamManager { 4 | function DEPOSIT_MANAGER() public pure returns (bytes32) { 5 | return keccak256("deposit_manager"); 6 | } 7 | 8 | function WITHDRAW_MANAGER() public pure returns (bytes32) { 9 | return keccak256("withdraw_manager"); 10 | } 11 | 12 | function TEST_TOKEN() public pure returns (bytes32) { 13 | return keccak256("test_token"); 14 | } 15 | 16 | function POB() public pure returns (bytes32) { 17 | return keccak256("pob"); 18 | } 19 | 20 | function Governance() public pure returns (bytes32) { 21 | return keccak256("governance"); 22 | } 23 | 24 | function ROLLUP_CORE() public pure returns (bytes32) { 25 | return keccak256("rollup_core"); 26 | } 27 | 28 | function ACCOUNTS_TREE() public pure returns (bytes32) { 29 | return keccak256("accounts_tree"); 30 | } 31 | 32 | function LOGGER() public pure returns (bytes32) { 33 | return keccak256("logger"); 34 | } 35 | 36 | function MERKLE_UTILS() public pure returns (bytes32) { 37 | return keccak256("merkle_lib"); 38 | } 39 | 40 | function PARAM_MANAGER() public pure returns (bytes32) { 41 | return keccak256("param_manager"); 42 | } 43 | 44 | function TOKEN_REGISTRY() public pure returns (bytes32) { 45 | return keccak256("token_registry"); 46 | } 47 | 48 | function FRAUD_PROOF() public pure returns (bytes32) { 49 | return keccak256("fraud_proof"); 50 | } 51 | 52 | bytes32 public constant _CHAIN_ID = keccak256("opru-123"); 53 | 54 | function CHAIN_ID() public pure returns (bytes32) { 55 | return _CHAIN_ID; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /contracts/libs/RollupUtils.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import { Types } from "./Types.sol"; 5 | 6 | library RollupUtils { 7 | // ---------- Account Related Utils ------------------- 8 | function PDALeafToHash(Types.PDALeaf memory _PDA_Leaf) 9 | public 10 | pure 11 | returns (bytes32) 12 | { 13 | return keccak256(abi.encode(_PDA_Leaf.pubkey)); 14 | } 15 | 16 | // returns a new User Account with updated balance 17 | function UpdateBalanceInAccount( 18 | Types.UserAccount memory original_account, 19 | uint256 new_balance 20 | ) public pure returns (Types.UserAccount memory updated_account) { 21 | original_account.balance = new_balance; 22 | return original_account; 23 | } 24 | 25 | function BalanceFromAccount(Types.UserAccount memory account) 26 | public 27 | pure 28 | returns (uint256) 29 | { 30 | return account.balance; 31 | } 32 | 33 | // AccountFromBytes decodes the bytes to account 34 | function AccountFromBytes(bytes memory accountBytes) 35 | public 36 | pure 37 | returns ( 38 | uint256 ID, 39 | uint256 balance, 40 | uint256 nonce, 41 | uint256 tokenType 42 | ) 43 | { 44 | return abi.decode(accountBytes, (uint256, uint256, uint256, uint256)); 45 | } 46 | 47 | // 48 | // BytesFromAccount and BytesFromAccountDeconstructed do the same thing i.e encode account to bytes 49 | // 50 | function BytesFromAccount(Types.UserAccount memory account) 51 | public 52 | pure 53 | returns (bytes memory) 54 | { 55 | bytes memory data = abi.encodePacked( 56 | account.ID, 57 | account.balance, 58 | account.nonce, 59 | account.tokenType 60 | ); 61 | 62 | return data; 63 | } 64 | 65 | function BytesFromAccountDeconstructed( 66 | uint256 ID, 67 | uint256 balance, 68 | uint256 nonce, 69 | uint256 tokenType 70 | ) public pure returns (bytes memory) { 71 | return abi.encodePacked(ID, balance, nonce, tokenType); 72 | } 73 | 74 | // 75 | // HashFromAccount and getAccountHash do the same thing i.e hash account 76 | // 77 | function getAccountHash( 78 | uint256 id, 79 | uint256 balance, 80 | uint256 nonce, 81 | uint256 tokenType 82 | ) public pure returns (bytes32) { 83 | return 84 | keccak256( 85 | BytesFromAccountDeconstructed(id, balance, nonce, tokenType) 86 | ); 87 | } 88 | 89 | function HashFromAccount(Types.UserAccount memory account) 90 | public 91 | pure 92 | returns (bytes32) 93 | { 94 | return 95 | keccak256( 96 | BytesFromAccountDeconstructed( 97 | account.ID, 98 | account.balance, 99 | account.nonce, 100 | account.tokenType 101 | ) 102 | ); 103 | } 104 | 105 | // ---------- Tx Related Utils ------------------- 106 | function CompressTx(Types.Transaction memory _tx) 107 | public 108 | pure 109 | returns (bytes memory) 110 | { 111 | return 112 | abi.encode(_tx.fromIndex, _tx.toIndex, _tx.amount, _tx.signature); 113 | } 114 | 115 | function DecompressTx(bytes memory txBytes) 116 | public 117 | pure 118 | returns ( 119 | uint256 from, 120 | uint256 to, 121 | uint256 nonce, 122 | bytes memory sig 123 | ) 124 | { 125 | return abi.decode(txBytes, (uint256, uint256, uint256, bytes)); 126 | } 127 | 128 | function CompressTxWithMessage(bytes memory message, bytes memory sig) 129 | public 130 | pure 131 | returns (bytes memory) 132 | { 133 | Types.Transaction memory _tx = TxFromBytes(message); 134 | return abi.encode(_tx.fromIndex, _tx.toIndex, _tx.amount, sig); 135 | } 136 | 137 | // Decoding transaction from bytes 138 | function TxFromBytesDeconstructed(bytes memory txBytes) 139 | public 140 | pure 141 | returns ( 142 | uint256 from, 143 | uint256 to, 144 | uint256 tokenType, 145 | uint256 nonce, 146 | uint256 txType, 147 | uint256 amount 148 | ) 149 | { 150 | return 151 | abi.decode( 152 | txBytes, 153 | (uint256, uint256, uint256, uint256, uint256, uint256) 154 | ); 155 | } 156 | 157 | function TxFromBytes(bytes memory txBytes) 158 | public 159 | pure 160 | returns (Types.Transaction memory) 161 | { 162 | Types.Transaction memory transaction; 163 | ( 164 | transaction.fromIndex, 165 | transaction.toIndex, 166 | transaction.tokenType, 167 | transaction.nonce, 168 | transaction.txType, 169 | transaction.amount 170 | ) = abi.decode( 171 | txBytes, 172 | (uint256, uint256, uint256, uint256, uint256, uint256) 173 | ); 174 | return transaction; 175 | } 176 | 177 | // 178 | // BytesFromTx and BytesFromTxDeconstructed do the same thing i.e encode transaction to bytes 179 | // 180 | function BytesFromTx(Types.Transaction memory _tx) 181 | public 182 | pure 183 | returns (bytes memory) 184 | { 185 | return 186 | abi.encodePacked( 187 | _tx.fromIndex, 188 | _tx.toIndex, 189 | _tx.tokenType, 190 | _tx.nonce, 191 | _tx.txType, 192 | _tx.amount 193 | ); 194 | } 195 | 196 | function BytesFromTxDeconstructed( 197 | uint256 from, 198 | uint256 to, 199 | uint256 tokenType, 200 | uint256 nonce, 201 | uint256 txType, 202 | uint256 amount 203 | ) public pure returns (bytes memory) { 204 | return abi.encodePacked(from, to, tokenType, nonce, txType, amount); 205 | } 206 | 207 | // 208 | // HashFromTx and getTxSignBytes do the same thing i.e get the tx data to be signed 209 | // 210 | function HashFromTx(Types.Transaction memory _tx) 211 | public 212 | pure 213 | returns (bytes32) 214 | { 215 | return 216 | keccak256( 217 | BytesFromTxDeconstructed( 218 | _tx.fromIndex, 219 | _tx.toIndex, 220 | _tx.tokenType, 221 | _tx.nonce, 222 | _tx.txType, 223 | _tx.amount 224 | ) 225 | ); 226 | } 227 | 228 | function getTxSignBytes( 229 | uint256 fromIndex, 230 | uint256 toIndex, 231 | uint256 tokenType, 232 | uint256 txType, 233 | uint256 nonce, 234 | uint256 amount 235 | ) public pure returns (bytes32) { 236 | return 237 | keccak256( 238 | BytesFromTxDeconstructed( 239 | fromIndex, 240 | toIndex, 241 | tokenType, 242 | nonce, 243 | txType, 244 | amount 245 | ) 246 | ); 247 | } 248 | 249 | /** 250 | * @notice Calculates the address from the pubkey 251 | * @param pub is the pubkey 252 | * @return Returns the address that has been calculated from the pubkey 253 | */ 254 | function calculateAddress(bytes memory pub) 255 | public 256 | pure 257 | returns (address addr) 258 | { 259 | bytes32 hash = keccak256(pub); 260 | assembly { 261 | mstore(0, hash) 262 | addr := mload(0) 263 | } 264 | } 265 | 266 | function GetGenesisLeaves() public view returns (bytes32[2] memory leaves) { 267 | Types.UserAccount memory account1 = Types.UserAccount({ 268 | ID: 0, 269 | tokenType: 0, 270 | balance: 0, 271 | nonce: 0 272 | }); 273 | Types.UserAccount memory account2 = Types.UserAccount({ 274 | ID: 1, 275 | tokenType: 0, 276 | balance: 0, 277 | nonce: 0 278 | }); 279 | leaves[0] = HashFromAccount(account1); 280 | leaves[1] = HashFromAccount(account2); 281 | } 282 | 283 | function GetGenesisDataBlocks() 284 | public 285 | view 286 | returns (bytes[2] memory dataBlocks) 287 | { 288 | Types.UserAccount memory account1 = Types.UserAccount({ 289 | ID: 0, 290 | tokenType: 0, 291 | balance: 0, 292 | nonce: 0 293 | }); 294 | Types.UserAccount memory account2 = Types.UserAccount({ 295 | ID: 1, 296 | tokenType: 0, 297 | balance: 0, 298 | nonce: 0 299 | }); 300 | dataBlocks[0] = BytesFromAccount(account1); 301 | dataBlocks[1] = BytesFromAccount(account2); 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /contracts/libs/Tx.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | 3 | library Tx { 4 | // // transaction: 5 | // // [sender<4>|receiver<4>|amount<4>] 6 | // uint256 public constant TX_LEN = 12; 7 | // uint256 public constant MASK_TX = 0xffffffffffffffffffffffff; 8 | // uint256 public constant MASK_STATE_INDEX = 0xffffffff; 9 | // uint256 public constant MASK_AMOUNT = 0xffffffff; 10 | // // positions in bytes 11 | // uint256 public constant POSITION_SENDER = 4; 12 | // uint256 public constant POSITION_RECEIVER = 8; 13 | // uint256 public constant POSITION_AMOUNT = 12; 14 | 15 | // transaction: 16 | // [sender<4>|receiver<4>|amount<4>|signature<64>] 17 | uint256 public constant RAW_TX_LEN = 12; 18 | uint256 public constant TX_LEN = 76; 19 | uint256 public constant MASK_TX = 0xffffffffffffffffffffffff; 20 | uint256 public constant MASK_STATE_INDEX = 0xffffffff; 21 | uint256 public constant MASK_AMOUNT = 0xffffffff; 22 | // positions in bytes 23 | uint256 public constant POSITION_SENDER = 4; 24 | uint256 public constant POSITION_RECEIVER = 8; 25 | uint256 public constant POSITION_AMOUNT = 12; 26 | uint256 public constant POSITION_SIGNATURE_X = 44; 27 | uint256 public constant POSITION_SIGNATURE_Y = 76; 28 | 29 | function serialize( 30 | uint256[] memory senders, 31 | uint256[] memory receivers, 32 | uint256[] memory amounts, 33 | bytes[] memory signatures 34 | ) internal pure returns (bytes memory) { 35 | uint256 batchSize = signatures.length; 36 | require(senders.length == batchSize, "bad sender size"); 37 | require(receivers.length == batchSize, "bad receiver size"); 38 | require(amounts.length == batchSize, "bad amount size"); 39 | uint256 bound = 0x10000000000000000; 40 | // uint256 TX_LEN = 4 + 4 + 4 + 64; 41 | bytes memory serialized = new bytes(TX_LEN * batchSize); 42 | for (uint256 i = 0; i < batchSize; i++) { 43 | bytes memory signature = signatures[i]; 44 | uint256 sender = senders[i]; 45 | uint256 receiver = receivers[i]; 46 | uint256 amount = amounts[i]; 47 | require(signature.length == 64, "invalid signature"); 48 | require(sender < bound, "invalid sender index"); 49 | require(receiver < bound, "invalid receiver index"); 50 | require(amount < bound, "invalid amount"); 51 | bytes memory _tx = abi.encodePacked( 52 | uint32(sender), 53 | uint32(receiver), 54 | uint32(amount), 55 | signature 56 | ); 57 | uint256 off = i * TX_LEN; 58 | for (uint256 j = 0; j < TX_LEN; j++) { 59 | serialized[j + off] = _tx[j]; 60 | } 61 | } 62 | return serialized; 63 | } 64 | 65 | function hasExcessData(bytes memory txs) internal pure returns (bool) { 66 | uint256 txSize = txs.length / TX_LEN; 67 | return txSize * TX_LEN != txs.length; 68 | } 69 | 70 | function size(bytes memory txs) internal pure returns (uint256) { 71 | uint256 txSize = txs.length / TX_LEN; 72 | return txSize; 73 | } 74 | 75 | function amountOf(bytes memory txs, uint256 index) 76 | internal 77 | pure 78 | returns (uint256 amount) 79 | { 80 | // solium-disable-next-line security/no-inline-assembly 81 | assembly { 82 | let p_tx := add(txs, mul(index, TX_LEN)) 83 | amount := and(mload(add(p_tx, POSITION_AMOUNT)), MASK_AMOUNT) 84 | } 85 | return amount; 86 | } 87 | 88 | function senderOf(bytes memory txs, uint256 index) 89 | internal 90 | pure 91 | returns (uint256 sender) 92 | { 93 | // solium-disable-next-line security/no-inline-assembly 94 | assembly { 95 | let p_tx := add(txs, mul(index, TX_LEN)) 96 | sender := and(mload(add(p_tx, POSITION_SENDER)), MASK_STATE_INDEX) 97 | } 98 | } 99 | 100 | function signatureOf(bytes memory txs, uint256 index) 101 | internal 102 | pure 103 | returns (bytes memory) 104 | { 105 | bytes memory signature = new bytes(64); 106 | // solium-disable-next-line security/no-inline-assembly 107 | assembly { 108 | let p_tx := add(txs, mul(index, TX_LEN)) 109 | let x := mload(add(p_tx, POSITION_SIGNATURE_X)) 110 | let y := mload(add(p_tx, POSITION_SIGNATURE_Y)) 111 | mstore(add(signature, 64), x) 112 | mstore(add(signature, 96), y) 113 | } 114 | return signature; 115 | } 116 | 117 | function receiverOf(bytes memory txs, uint256 index) 118 | internal 119 | pure 120 | returns (uint256 receiver) 121 | { 122 | // solium-disable-next-line security/no-inline-assembly 123 | assembly { 124 | let p_tx := add(txs, mul(index, TX_LEN)) 125 | receiver := and( 126 | mload(add(p_tx, POSITION_RECEIVER)), 127 | MASK_STATE_INDEX 128 | ) 129 | } 130 | } 131 | 132 | function hashOf(bytes memory txs, uint256 index) 133 | internal 134 | pure 135 | returns (bytes32 result) 136 | { 137 | // solium-disable-next-line security/no-inline-assembly 138 | assembly { 139 | let p_tx := add(txs, add(mul(index, TX_LEN), 32)) 140 | result := keccak256(p_tx, RAW_TX_LEN) 141 | } 142 | } 143 | 144 | function toLeafs(bytes memory txs) 145 | internal 146 | pure 147 | returns (bytes32[] memory) 148 | { 149 | uint256 batchSize = size(txs); 150 | bytes32[] memory buf = new bytes32[](batchSize); 151 | for (uint256 i = 0; i < batchSize; i++) { 152 | buf[i] = hashOf(txs, i); 153 | } 154 | return buf; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /contracts/libs/Types.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | 3 | /** 4 | * @title DataTypes 5 | */ 6 | library Types { 7 | // We define Usage for a batch or for a tx 8 | // to check if the usage of a batch and all txs in it are the same 9 | enum Usage { 10 | Genesis, // The Genesis type is only applicable to batch but not tx 11 | Transfer, 12 | Deposit 13 | } 14 | // PDALeaf represents the leaf in 15 | // Pubkey DataAvailability Tree 16 | struct PDALeaf { 17 | bytes pubkey; 18 | } 19 | 20 | // Batch represents the batch submitted periodically to the ethereum chain 21 | struct Batch { 22 | bytes32 stateRoot; 23 | bytes32 accountRoot; 24 | bytes32 depositTree; 25 | address committer; 26 | bytes32 txRoot; 27 | uint256 stakeCommitted; 28 | uint256 finalisesOn; 29 | uint256 timestamp; 30 | Usage batchType; 31 | } 32 | 33 | // Transaction represents how each transaction looks like for 34 | // this rollup chain 35 | struct Transaction { 36 | uint256 fromIndex; 37 | uint256 toIndex; 38 | uint256 tokenType; 39 | uint256 nonce; 40 | uint256 txType; 41 | uint256 amount; 42 | bytes signature; 43 | } 44 | 45 | // AccountInclusionProof consists of the following fields 46 | // 1. Path to the account leaf from root in the balances tree 47 | // 2. Actual data stored in the leaf 48 | struct AccountInclusionProof { 49 | uint256 pathToAccount; 50 | UserAccount account; 51 | } 52 | 53 | struct TranasctionInclusionProof { 54 | uint256 pathToTx; 55 | Transaction data; 56 | } 57 | 58 | struct PDAInclusionProof { 59 | uint256 pathToPubkey; 60 | PDALeaf pubkey_leaf; 61 | } 62 | 63 | // UserAccount contains the actual data stored in the leaf of balance tree 64 | struct UserAccount { 65 | // ID is the path to the pubkey in the PDA tree 66 | uint256 ID; 67 | uint256 tokenType; 68 | uint256 balance; 69 | uint256 nonce; 70 | } 71 | 72 | struct AccountMerkleProof { 73 | AccountInclusionProof accountIP; 74 | bytes32[] siblings; 75 | } 76 | 77 | struct AccountProofs { 78 | AccountMerkleProof from; 79 | AccountMerkleProof to; 80 | } 81 | 82 | struct BatchValidationProofs { 83 | AccountProofs[] accountProofs; 84 | PDAMerkleProof[] pdaProof; 85 | } 86 | 87 | struct TransactionMerkleProof { 88 | TranasctionInclusionProof _tx; 89 | bytes32[] siblings; 90 | } 91 | 92 | struct PDAMerkleProof { 93 | PDAInclusionProof _pda; 94 | bytes32[] siblings; 95 | } 96 | 97 | enum ErrorCode { 98 | NoError, 99 | InvalidTokenAddress, 100 | InvalidTokenAmount, 101 | NotEnoughTokenBalance, 102 | BadFromTokenType, 103 | BadToTokenType 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /contracts/logger.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | 3 | import { Types } from "./libs/Types.sol"; 4 | 5 | contract Logger { 6 | /********************* 7 | * Rollup Contract * 8 | ********************/ 9 | event NewBatch( 10 | address committer, 11 | bytes32 txroot, 12 | bytes32 updatedRoot, 13 | uint256 index, 14 | Types.Usage batchType 15 | ); 16 | 17 | function logNewBatch( 18 | address committer, 19 | bytes32 txroot, 20 | bytes32 updatedRoot, 21 | uint256 index, 22 | Types.Usage batchType 23 | ) public { 24 | emit NewBatch(committer, txroot, updatedRoot, index, batchType); 25 | } 26 | 27 | event StakeWithdraw(address committed, uint256 amount, uint256 batch_id); 28 | 29 | function logStakeWithdraw( 30 | address committed, 31 | uint256 amount, 32 | uint256 batch_id 33 | ) public { 34 | emit StakeWithdraw(committed, amount, batch_id); 35 | } 36 | 37 | event BatchRollback( 38 | uint256 batch_id, 39 | address committer, 40 | bytes32 stateRoot, 41 | bytes32 txRoot, 42 | uint256 stakeSlashed 43 | ); 44 | 45 | function logBatchRollback( 46 | uint256 batch_id, 47 | address committer, 48 | bytes32 stateRoot, 49 | bytes32 txRoot, 50 | uint256 stakeSlashed 51 | ) public { 52 | emit BatchRollback( 53 | batch_id, 54 | committer, 55 | stateRoot, 56 | txRoot, 57 | stakeSlashed 58 | ); 59 | } 60 | 61 | event RollbackFinalisation(uint256 totalBatchesSlashed); 62 | 63 | function logRollbackFinalisation(uint256 totalBatchesSlashed) public { 64 | emit RollbackFinalisation(totalBatchesSlashed); 65 | } 66 | 67 | event RegisteredToken(uint256 tokenType, address tokenContract); 68 | 69 | function logRegisteredToken(uint256 tokenType, address tokenContract) 70 | public 71 | { 72 | emit RegisteredToken(tokenType, tokenContract); 73 | } 74 | 75 | event RegistrationRequest(address tokenContract); 76 | 77 | function logRegistrationRequest(address tokenContract) public { 78 | emit RegistrationRequest(tokenContract); 79 | } 80 | 81 | event NewPubkeyAdded(uint256 AccountID, bytes pubkey); 82 | 83 | function logNewPubkeyAdded(uint256 accountID, bytes memory pubkey) public { 84 | emit NewPubkeyAdded(accountID, pubkey); 85 | } 86 | 87 | event DepositQueued(uint256 AccountID, bytes pubkey, bytes data); 88 | 89 | function logDepositQueued( 90 | uint256 accountID, 91 | bytes memory pubkey, 92 | bytes memory data 93 | ) public { 94 | emit DepositQueued(accountID, pubkey, data); 95 | } 96 | 97 | event DepositLeafMerged(bytes32 left, bytes32 right, bytes32 newRoot); 98 | 99 | function logDepositLeafMerged( 100 | bytes32 left, 101 | bytes32 right, 102 | bytes32 newRoot 103 | ) public { 104 | emit DepositLeafMerged(left, right, newRoot); 105 | } 106 | 107 | event DepositSubTreeReady(bytes32 root); 108 | 109 | function logDepositSubTreeReady(bytes32 root) public { 110 | emit DepositSubTreeReady(root); 111 | } 112 | 113 | event DepositsFinalised(bytes32 depositSubTreeRoot, uint256 pathToSubTree); 114 | 115 | function logDepositFinalised( 116 | bytes32 depositSubTreeRoot, 117 | uint256 pathToSubTree 118 | ) public { 119 | emit DepositsFinalised(depositSubTreeRoot, pathToSubTree); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /contracts/rollup.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; 5 | import "solidity-bytes-utils/contracts/BytesLib.sol"; 6 | import { IERC20 } from "./interfaces/IERC20.sol"; 7 | import { ITokenRegistry } from "./interfaces/ITokenRegistry.sol"; 8 | import { IFraudProof } from "./interfaces/IFraudProof.sol"; 9 | import { ParamManager } from "./libs/ParamManager.sol"; 10 | import { Types } from "./libs/Types.sol"; 11 | import { RollupUtils } from "./libs/RollupUtils.sol"; 12 | import { ECVerify } from "./libs/ECVerify.sol"; 13 | import { IncrementalTree } from "./IncrementalTree.sol"; 14 | import { Logger } from "./logger.sol"; 15 | import { POB } from "./POB.sol"; 16 | import { MerkleTreeUtils as MTUtils } from "./MerkleTreeUtils.sol"; 17 | import { NameRegistry as Registry } from "./NameRegistry.sol"; 18 | import { Governance } from "./Governance.sol"; 19 | import { DepositManager } from "./DepositManager.sol"; 20 | 21 | contract RollupSetup { 22 | using SafeMath for uint256; 23 | using BytesLib for bytes; 24 | using ECVerify for bytes32; 25 | 26 | /********************* 27 | * Variable Declarations * 28 | ********************/ 29 | 30 | // External contracts 31 | DepositManager public depositManager; 32 | IncrementalTree public accountsTree; 33 | Logger public logger; 34 | ITokenRegistry public tokenRegistry; 35 | Registry public nameRegistry; 36 | Types.Batch[] public batches; 37 | MTUtils public merkleUtils; 38 | 39 | IFraudProof public fraudProof; 40 | 41 | bytes32 42 | public constant ZERO_BYTES32 = 0x0000000000000000000000000000000000000000000000000000000000000000; 43 | address payable constant BURN_ADDRESS = 0x0000000000000000000000000000000000000000; 44 | Governance public governance; 45 | 46 | // this variable will be greater than 0 if 47 | // there is rollback in progress 48 | // will be reset to 0 once rollback is completed 49 | uint256 public invalidBatchMarker; 50 | 51 | modifier onlyCoordinator() { 52 | POB pobContract = POB( 53 | nameRegistry.getContractDetails(ParamManager.POB()) 54 | ); 55 | assert(msg.sender == pobContract.getCoordinator()); 56 | _; 57 | } 58 | 59 | modifier isNotRollingBack() { 60 | assert(invalidBatchMarker == 0); 61 | _; 62 | } 63 | 64 | modifier isRollingBack() { 65 | assert(invalidBatchMarker > 0); 66 | _; 67 | } 68 | } 69 | 70 | contract RollupHelpers is RollupSetup { 71 | /** 72 | * @notice Returns the latest state root 73 | */ 74 | function getLatestBalanceTreeRoot() public view returns (bytes32) { 75 | return batches[batches.length - 1].stateRoot; 76 | } 77 | 78 | /** 79 | * @notice Returns the total number of batches submitted 80 | */ 81 | function numOfBatchesSubmitted() public view returns (uint256) { 82 | return batches.length; 83 | } 84 | 85 | function addNewBatch( 86 | bytes32 txRoot, 87 | bytes32 _updatedRoot, 88 | Types.Usage batchType 89 | ) internal { 90 | Types.Batch memory newBatch = Types.Batch({ 91 | stateRoot: _updatedRoot, 92 | accountRoot: accountsTree.getTreeRoot(), 93 | depositTree: ZERO_BYTES32, 94 | committer: msg.sender, 95 | txRoot: txRoot, 96 | stakeCommitted: msg.value, 97 | finalisesOn: block.number + governance.TIME_TO_FINALISE(), 98 | timestamp: now, 99 | batchType: batchType 100 | }); 101 | 102 | batches.push(newBatch); 103 | logger.logNewBatch( 104 | newBatch.committer, 105 | txRoot, 106 | _updatedRoot, 107 | batches.length - 1, 108 | batchType 109 | ); 110 | } 111 | 112 | function addNewBatchWithDeposit(bytes32 _updatedRoot, bytes32 depositRoot) 113 | internal 114 | { 115 | Types.Batch memory newBatch = Types.Batch({ 116 | stateRoot: _updatedRoot, 117 | accountRoot: accountsTree.getTreeRoot(), 118 | depositTree: depositRoot, 119 | committer: msg.sender, 120 | txRoot: ZERO_BYTES32, 121 | stakeCommitted: msg.value, 122 | finalisesOn: block.number + governance.TIME_TO_FINALISE(), 123 | timestamp: now, 124 | batchType: Types.Usage.Deposit 125 | }); 126 | 127 | batches.push(newBatch); 128 | logger.logNewBatch( 129 | newBatch.committer, 130 | ZERO_BYTES32, 131 | _updatedRoot, 132 | batches.length - 1, 133 | Types.Usage.Deposit 134 | ); 135 | } 136 | 137 | /** 138 | * @notice Returns the batch 139 | */ 140 | function getBatch(uint256 _batch_id) 141 | public 142 | view 143 | returns (Types.Batch memory batch) 144 | { 145 | require( 146 | batches.length - 1 >= _batch_id, 147 | "Batch id greater than total number of batches, invalid batch id" 148 | ); 149 | batch = batches[_batch_id]; 150 | } 151 | 152 | /** 153 | * @notice SlashAndRollback slashes all the coordinator's who have built on top of the invalid batch 154 | * and rewards challengers. Also deletes all the batches after invalid batch 155 | */ 156 | function SlashAndRollback() public isRollingBack { 157 | uint256 challengerRewards = 0; 158 | uint256 burnedAmount = 0; 159 | uint256 totalSlashings = 0; 160 | 161 | for (uint256 i = batches.length - 1; i >= invalidBatchMarker; i--) { 162 | // if gas left is low we would like to do all the transfers 163 | // and persist intermediate states so someone else can send another tx 164 | // and rollback remaining batches 165 | if (gasleft() <= governance.MIN_GAS_LIMIT_LEFT()) { 166 | // exit loop gracefully 167 | break; 168 | } 169 | 170 | // load batch 171 | Types.Batch memory batch = batches[i]; 172 | 173 | // calculate challeger's reward 174 | uint256 _challengerReward = (batch.stakeCommitted.mul(2)).div(3); 175 | challengerRewards += _challengerReward; 176 | burnedAmount += batch.stakeCommitted.sub(_challengerReward); 177 | 178 | batches[i].stakeCommitted = 0; 179 | 180 | // delete batch 181 | delete batches[i]; 182 | 183 | // queue deposits again 184 | depositManager.enqueue(batch.depositTree); 185 | 186 | totalSlashings++; 187 | 188 | logger.logBatchRollback( 189 | i, 190 | batch.committer, 191 | batch.stateRoot, 192 | batch.txRoot, 193 | batch.stakeCommitted 194 | ); 195 | if (i == invalidBatchMarker) { 196 | // we have completed rollback 197 | // update the marker 198 | invalidBatchMarker = 0; 199 | break; 200 | } 201 | } 202 | 203 | // transfer reward to challenger 204 | (msg.sender).transfer(challengerRewards); 205 | 206 | // burn the remaning amount 207 | (BURN_ADDRESS).transfer(burnedAmount); 208 | 209 | // resize batches length 210 | batches.length = batches.length.sub(totalSlashings); 211 | 212 | logger.logRollbackFinalisation(totalSlashings); 213 | } 214 | } 215 | 216 | contract Rollup is RollupHelpers { 217 | /********************* 218 | * Constructor * 219 | ********************/ 220 | constructor(address _registryAddr, bytes32 genesisStateRoot) public { 221 | nameRegistry = Registry(_registryAddr); 222 | 223 | logger = Logger(nameRegistry.getContractDetails(ParamManager.LOGGER())); 224 | depositManager = DepositManager( 225 | nameRegistry.getContractDetails(ParamManager.DEPOSIT_MANAGER()) 226 | ); 227 | 228 | governance = Governance( 229 | nameRegistry.getContractDetails(ParamManager.Governance()) 230 | ); 231 | merkleUtils = MTUtils( 232 | nameRegistry.getContractDetails(ParamManager.MERKLE_UTILS()) 233 | ); 234 | accountsTree = IncrementalTree( 235 | nameRegistry.getContractDetails(ParamManager.ACCOUNTS_TREE()) 236 | ); 237 | 238 | tokenRegistry = ITokenRegistry( 239 | nameRegistry.getContractDetails(ParamManager.TOKEN_REGISTRY()) 240 | ); 241 | 242 | fraudProof = IFraudProof( 243 | nameRegistry.getContractDetails(ParamManager.FRAUD_PROOF()) 244 | ); 245 | addNewBatch(ZERO_BYTES32, genesisStateRoot, Types.Usage.Genesis); 246 | } 247 | 248 | /** 249 | * @notice Submits a new batch to batches 250 | * @param _txs Compressed transactions . 251 | * @param _updatedRoot New balance tree root after processing all the transactions 252 | */ 253 | function submitBatch( 254 | bytes[] calldata _txs, 255 | bytes32 _updatedRoot, 256 | Types.Usage batchType 257 | ) external payable onlyCoordinator isNotRollingBack { 258 | require( 259 | msg.value >= governance.STAKE_AMOUNT(), 260 | "Not enough stake committed" 261 | ); 262 | 263 | require( 264 | _txs.length <= governance.MAX_TXS_PER_BATCH(), 265 | "Batch contains more transations than the limit" 266 | ); 267 | bytes32 txRoot = merkleUtils.getMerkleRoot(_txs); 268 | require( 269 | txRoot != ZERO_BYTES32, 270 | "Cannot submit a transaction with no transactions" 271 | ); 272 | addNewBatch(txRoot, _updatedRoot, batchType); 273 | } 274 | 275 | /** 276 | * @notice finalise deposits and submit batch 277 | */ 278 | function finaliseDepositsAndSubmitBatch( 279 | uint256 _subTreeDepth, 280 | Types.AccountMerkleProof calldata _zero_account_mp 281 | ) external payable onlyCoordinator isNotRollingBack { 282 | bytes32 depositSubTreeRoot = depositManager.finaliseDeposits( 283 | _subTreeDepth, 284 | _zero_account_mp, 285 | getLatestBalanceTreeRoot() 286 | ); 287 | // require( 288 | // msg.value >= governance.STAKE_AMOUNT(), 289 | // "Not enough stake committed" 290 | // ); 291 | 292 | bytes32 updatedRoot = merkleUtils.updateLeafWithSiblings( 293 | depositSubTreeRoot, 294 | _zero_account_mp.accountIP.pathToAccount, 295 | _zero_account_mp.siblings 296 | ); 297 | 298 | // add new batch 299 | addNewBatchWithDeposit(updatedRoot, depositSubTreeRoot); 300 | } 301 | 302 | /** 303 | * disputeBatch processes a transactions and returns the updated balance tree 304 | * and the updated leaves. 305 | * @notice Gives the number of batches submitted on-chain 306 | * @return Total number of batches submitted onchain 307 | */ 308 | function disputeBatch( 309 | uint256 _batch_id, 310 | Types.Transaction[] memory _txs, 311 | Types.BatchValidationProofs memory batchProofs 312 | ) public { 313 | { 314 | // load batch 315 | require( 316 | batches[_batch_id].stakeCommitted != 0, 317 | "Batch doesnt exist or is slashed already" 318 | ); 319 | 320 | // check if batch is disputable 321 | require( 322 | block.number < batches[_batch_id].finalisesOn, 323 | "Batch already finalised" 324 | ); 325 | 326 | require( 327 | (_batch_id < invalidBatchMarker || invalidBatchMarker == 0), 328 | "Already successfully disputed. Roll back in process" 329 | ); 330 | 331 | require( 332 | batches[_batch_id].txRoot != ZERO_BYTES32, 333 | "Cannot dispute blocks with no transaction" 334 | ); 335 | } 336 | 337 | bytes32 updatedBalanceRoot; 338 | bool isDisputeValid; 339 | bytes32 txRoot; 340 | (updatedBalanceRoot, txRoot, isDisputeValid) = processBatch( 341 | batches[_batch_id - 1].stateRoot, 342 | batches[_batch_id - 1].accountRoot, 343 | _txs, 344 | batchProofs, 345 | batches[_batch_id].txRoot 346 | ); 347 | 348 | // dispute is valid, we need to slash and rollback :( 349 | if (isDisputeValid) { 350 | // before rolling back mark the batch invalid 351 | // so we can pause and unpause 352 | invalidBatchMarker = _batch_id; 353 | SlashAndRollback(); 354 | return; 355 | } 356 | 357 | // if new root doesnt match what was submitted by coordinator 358 | // slash and rollback 359 | if (updatedBalanceRoot != batches[_batch_id].stateRoot) { 360 | invalidBatchMarker = _batch_id; 361 | SlashAndRollback(); 362 | return; 363 | } 364 | } 365 | 366 | function ApplyTx( 367 | Types.AccountMerkleProof memory _merkle_proof, 368 | bytes memory txBytes 369 | ) public view returns (bytes memory, bytes32 newRoot) { 370 | Types.Transaction memory transaction = RollupUtils.TxFromBytes(txBytes); 371 | return fraudProof.ApplyTx(_merkle_proof, transaction); 372 | } 373 | 374 | /** 375 | * @notice processTx processes a transactions and returns the updated balance tree 376 | * and the updated leaves 377 | * conditions in require mean that the dispute be declared invalid 378 | * if conditons evaluate if the coordinator was at fault 379 | * @return Total number of batches submitted onchain 380 | */ 381 | function processTx( 382 | bytes32 _balanceRoot, 383 | bytes32 _accountsRoot, 384 | bytes memory sig, 385 | bytes memory txBytes, 386 | Types.PDAMerkleProof memory _from_pda_proof, 387 | Types.AccountProofs memory accountProofs 388 | ) 389 | public 390 | view 391 | returns ( 392 | bytes32, 393 | bytes memory, 394 | bytes memory, 395 | Types.ErrorCode, 396 | bool 397 | ) 398 | { 399 | Types.Transaction memory _tx = RollupUtils.TxFromBytes(txBytes); 400 | _tx.signature = sig; 401 | return 402 | fraudProof.processTx( 403 | _balanceRoot, 404 | _accountsRoot, 405 | _tx, 406 | _from_pda_proof, 407 | accountProofs 408 | ); 409 | } 410 | 411 | /** 412 | * @notice processBatch processes a batch and returns the updated balance tree 413 | * and the updated leaves 414 | * conditions in require mean that the dispute be declared invalid 415 | * if conditons evaluate if the coordinator was at fault 416 | * @return Total number of batches submitted onchain 417 | */ 418 | function processBatch( 419 | bytes32 initialStateRoot, 420 | bytes32 accountsRoot, 421 | Types.Transaction[] memory _txs, 422 | Types.BatchValidationProofs memory batchProofs, 423 | bytes32 expectedTxRoot 424 | ) 425 | public 426 | view 427 | returns ( 428 | bytes32, 429 | bytes32, 430 | bool 431 | ) 432 | { 433 | return 434 | fraudProof.processBatch( 435 | initialStateRoot, 436 | accountsRoot, 437 | _txs, 438 | batchProofs, 439 | expectedTxRoot 440 | ); 441 | } 442 | 443 | /** 444 | * @notice Withdraw delay allows coordinators to withdraw their stake after the batch has been finalised 445 | * @param batch_id Batch ID that the coordinator submitted 446 | */ 447 | function WithdrawStake(uint256 batch_id) public { 448 | Types.Batch memory committedBatch = batches[batch_id]; 449 | require( 450 | committedBatch.stakeCommitted != 0, 451 | "Stake has been already withdrawn!!" 452 | ); 453 | require( 454 | msg.sender == committedBatch.committer, 455 | "You are not the correct committer for this batch" 456 | ); 457 | require( 458 | block.number > committedBatch.finalisesOn, 459 | "This batch is not yet finalised, check back soon!" 460 | ); 461 | 462 | msg.sender.transfer(committedBatch.stakeCommitted); 463 | logger.logStakeWithdraw( 464 | msg.sender, 465 | committedBatch.stakeCommitted, 466 | batch_id 467 | ); 468 | committedBatch.stakeCommitted = 0; 469 | } 470 | } 471 | -------------------------------------------------------------------------------- /contracts/test/TestTx.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import { Tx } from "../libs/Tx.sol"; 5 | 6 | contract TestTx { 7 | using Tx for bytes; 8 | 9 | function serialize( 10 | uint256[] memory senders, 11 | uint256[] memory receivers, 12 | uint256[] memory amounts, 13 | bytes[] memory signatures 14 | ) public pure returns (bytes memory) { 15 | return Tx.serialize(senders, receivers, amounts, signatures); 16 | } 17 | 18 | function hasExcessData(bytes calldata txs) external pure returns (bool) { 19 | return txs.hasExcessData(); 20 | } 21 | 22 | function size(bytes calldata txs) external pure returns (uint256) { 23 | return txs.size(); 24 | } 25 | 26 | function amountOf(bytes calldata txs, uint256 index) 27 | external 28 | pure 29 | returns (uint256) 30 | { 31 | return txs.amountOf(index); 32 | } 33 | 34 | function senderOf(bytes calldata txs, uint256 index) 35 | external 36 | pure 37 | returns (uint256) 38 | { 39 | return txs.senderOf(index); 40 | } 41 | 42 | function receiverOf(bytes calldata txs, uint256 index) 43 | external 44 | pure 45 | returns (uint256) 46 | { 47 | return txs.receiverOf(index); 48 | } 49 | 50 | function signatureOf(bytes calldata txs, uint256 index) 51 | external 52 | pure 53 | returns (bytes memory) 54 | { 55 | return txs.signatureOf(index); 56 | } 57 | 58 | function hashOf(bytes calldata txs, uint256 index) 59 | external 60 | pure 61 | returns (bytes32) 62 | { 63 | return txs.hashOf(index); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contracts/withdrawManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.15; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import { ECVerify } from "./libs/ECVerify.sol"; 5 | import { Types } from "./libs/Types.sol"; 6 | import { RollupUtils } from "./libs/RollupUtils.sol"; 7 | import { ParamManager } from "./libs/ParamManager.sol"; 8 | 9 | import { ITokenRegistry } from "./interfaces/ITokenRegistry.sol"; 10 | import { IERC20 } from "./interfaces/IERC20.sol"; 11 | 12 | import { MerkleTreeUtils as MTUtils } from "./MerkleTreeUtils.sol"; 13 | import { NameRegistry as Registry } from "./NameRegistry.sol"; 14 | import { POB } from "./POB.sol"; 15 | import { Governance } from "./Governance.sol"; 16 | import { Rollup } from "./rollup.sol"; 17 | 18 | contract WithdrawManager { 19 | using ECVerify for bytes32; 20 | 21 | MTUtils public merkleUtils; 22 | ITokenRegistry public tokenRegistry; 23 | Governance public governance; 24 | Registry public nameRegistry; 25 | Rollup public rollup; 26 | 27 | // Stores transaction paths claimed per batch 28 | bool[][] withdrawTxClaimed; 29 | 30 | /********************* 31 | * Constructor * 32 | ********************/ 33 | constructor(address _registryAddr) public { 34 | nameRegistry = Registry(_registryAddr); 35 | 36 | governance = Governance( 37 | nameRegistry.getContractDetails(ParamManager.Governance()) 38 | ); 39 | merkleUtils = MTUtils( 40 | nameRegistry.getContractDetails(ParamManager.MERKLE_UTILS()) 41 | ); 42 | 43 | rollup = Rollup( 44 | nameRegistry.getContractDetails(ParamManager.ROLLUP_CORE()) 45 | ); 46 | 47 | tokenRegistry = ITokenRegistry( 48 | nameRegistry.getContractDetails(ParamManager.TOKEN_REGISTRY()) 49 | ); 50 | withdrawTxClaimed = new bool[][](governance.MAX_TXS_PER_BATCH()); 51 | } 52 | 53 | /** 54 | * @notice Allows user to withdraw the balance in the leaf of the balances tree. 55 | * User has to do the following: Prove that a transfer of X tokens was made to the burn address or leaf 0 56 | * The batch we are allowing withdraws from should have been already finalised, so we can assume all data in the batch to be correct 57 | * @param _batch_id Deposit tree depth or depth of subtree that is being deposited 58 | * @param withdraw_tx_proof contains the siblints, txPath and the txData for the withdraw transaction 59 | */ 60 | function Withdraw( 61 | uint256 _batch_id, 62 | Types.PDAMerkleProof memory _pda_proof, 63 | Types.TransactionMerkleProof memory withdraw_tx_proof 64 | ) public { 65 | Types.Batch memory batch = rollup.getBatch(_batch_id); 66 | 67 | // check if the batch is finalised 68 | require(block.number > batch.finalisesOn, "Batch not finalised yt"); 69 | // verify transaction exists in the batch 70 | merkleUtils.verify( 71 | batch.txRoot, 72 | RollupUtils.BytesFromTx(withdraw_tx_proof._tx.data), 73 | withdraw_tx_proof._tx.pathToTx, 74 | withdraw_tx_proof.siblings 75 | ); 76 | 77 | // check if the transaction is withdraw transaction 78 | // ensure the `to` leaf was the 0th leaf 79 | require( 80 | withdraw_tx_proof._tx.data.toIndex == 0, 81 | "Not a withdraw transaction" 82 | ); 83 | 84 | bool isClaimed = withdrawTxClaimed[_batch_id][withdraw_tx_proof 85 | ._tx 86 | .pathToTx]; 87 | require(!isClaimed, "Withdraw transaction already claimed"); 88 | withdrawTxClaimed[_batch_id][withdraw_tx_proof._tx.pathToTx] = true; 89 | 90 | // withdraw checks out, transfer to the account in account tree 91 | address tokenContractAddress = tokenRegistry.registeredTokens( 92 | withdraw_tx_proof._tx.data.tokenType 93 | ); 94 | 95 | // convert pubkey path to ID 96 | uint256 computedID = merkleUtils.pathToIndex( 97 | _pda_proof._pda.pathToPubkey, 98 | governance.MAX_DEPTH() 99 | ); 100 | 101 | require( 102 | computedID == withdraw_tx_proof._tx.data.fromIndex, 103 | "Pubkey not related to the from account in the transaction" 104 | ); 105 | 106 | address receiver = RollupUtils.calculateAddress( 107 | _pda_proof._pda.pubkey_leaf.pubkey 108 | ); 109 | 110 | require( 111 | receiver == 112 | RollupUtils.HashFromTx(withdraw_tx_proof._tx.data).ecrecovery( 113 | withdraw_tx_proof._tx.data.signature 114 | ), 115 | "Signature is incorrect" 116 | ); 117 | 118 | uint256 amount = withdraw_tx_proof._tx.data.amount; 119 | 120 | IERC20 tokenContract = IERC20(tokenContractAddress); 121 | require(tokenContract.transfer(receiver, amount), "Unable to trasnfer"); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | // Libs 3 | const ECVerifyLib = artifacts.require("ECVerify"); 4 | const paramManagerLib = artifacts.require("ParamManager"); 5 | const rollupUtilsLib = artifacts.require("RollupUtils"); 6 | const Types = artifacts.require("Types"); 7 | 8 | // Contracts Deployer 9 | const governanceContract = artifacts.require("Governance"); 10 | const MTUtilsContract = artifacts.require("MerkleTreeUtils"); 11 | const loggerContract = artifacts.require("Logger"); 12 | const tokenRegistryContract = artifacts.require("TokenRegistry"); 13 | const fraudProofContract = artifacts.require("FraudProof"); 14 | 15 | const nameRegistryContract = artifacts.require("NameRegistry"); 16 | const incrementalTreeContract = artifacts.require("IncrementalTree"); 17 | const depositManagerContract = artifacts.require("DepositManager"); 18 | const rollupContract = artifacts.require("Rollup"); 19 | const testTokenContract = artifacts.require("TestToken"); 20 | const merkleTreeUtilsContract = artifacts.require("MerkleTreeUtils"); 21 | const POBContract = artifacts.require("POB"); 22 | const utils = "../test/helpers/utils.ts"; 23 | 24 | function writeContractAddresses(contractAddresses) { 25 | fs.writeFileSync( 26 | `${process.cwd()}/contractAddresses.json`, 27 | JSON.stringify(contractAddresses, null, 2) // Indent 2 spaces 28 | ); 29 | } 30 | 31 | module.exports = async function(deployer) { 32 | // picked address from mnemoic 33 | var coordinator = "0x9fB29AAc15b9A4B7F17c3385939b007540f4d791"; 34 | var max_depth = 4; 35 | var maxDepositSubtreeDepth = 1; 36 | 37 | // deploy libs 38 | await deployer.deploy(ECVerifyLib); 39 | await deployer.deploy(Types); 40 | const paramManagerInstance = await deployer.deploy(paramManagerLib); 41 | await deployer.deploy(rollupUtilsLib); 42 | 43 | // deploy name registry 44 | const nameRegistryInstance = await deployer.deploy(nameRegistryContract); 45 | 46 | // deploy governance 47 | const governanceInstance = await deployAndRegister( 48 | deployer, 49 | governanceContract, 50 | [], 51 | [max_depth, maxDepositSubtreeDepth], 52 | "Governance" 53 | ); 54 | 55 | // deploy MTUtils 56 | const mtUtilsInstance = await deployAndRegister( 57 | deployer, 58 | MTUtilsContract, 59 | [ECVerifyLib, Types, paramManagerLib, rollupUtilsLib], 60 | [nameRegistryInstance.address], 61 | "MERKLE_UTILS" 62 | ); 63 | 64 | // deploy logger 65 | const loggerInstance = await deployAndRegister( 66 | deployer, 67 | loggerContract, 68 | [], 69 | [], 70 | "LOGGER" 71 | ); 72 | 73 | // deploy Token registry contract 74 | const tokenRegistryInstance = await deployAndRegister( 75 | deployer, 76 | tokenRegistryContract, 77 | [ECVerifyLib, Types, paramManagerLib, rollupUtilsLib], 78 | [nameRegistryInstance.address], 79 | "TOKEN_REGISTRY" 80 | ); 81 | 82 | // deploy Token registry contract 83 | const fraudProofInstance = await deployAndRegister( 84 | deployer, 85 | fraudProofContract, 86 | [ECVerifyLib, Types, paramManagerLib, rollupUtilsLib], 87 | [nameRegistryInstance.address], 88 | "FRAUD_PROOF" 89 | ); 90 | 91 | // deploy POB contract 92 | const pobInstance = await deployAndRegister( 93 | deployer, 94 | POBContract, 95 | [], 96 | [], 97 | "POB" 98 | ); 99 | 100 | // deploy account tree contract 101 | const accountsTreeInstance = await deployAndRegister( 102 | deployer, 103 | incrementalTreeContract, 104 | [paramManagerLib], 105 | [nameRegistryInstance.address], 106 | "ACCOUNTS_TREE" 107 | ); 108 | 109 | // deploy test token 110 | const testTokenInstance = await deployAndRegister( 111 | deployer, 112 | testTokenContract, 113 | [], 114 | [], 115 | "TEST_TOKEN" 116 | ); 117 | 118 | const root = await getMerkleRootWithCoordinatorAccount(max_depth); 119 | 120 | // deploy deposit manager 121 | const depositManagerInstance = await deployAndRegister( 122 | deployer, 123 | depositManagerContract, 124 | [Types, paramManagerLib, rollupUtilsLib], 125 | [nameRegistryInstance.address], 126 | "DEPOSIT_MANAGER" 127 | ); 128 | 129 | // deploy Rollup core 130 | const rollupInstance = await deployAndRegister( 131 | deployer, 132 | rollupContract, 133 | [ECVerifyLib, Types, paramManagerLib, rollupUtilsLib], 134 | [nameRegistryInstance.address, root], 135 | "ROLLUP_CORE" 136 | ); 137 | 138 | const contractAddresses = { 139 | AccountTree: accountsTreeInstance.address, 140 | ParamManager: paramManagerInstance.address, 141 | DepositManager: depositManagerInstance.address, 142 | RollupContract: rollupInstance.address, 143 | ProofOfBurnContract: pobInstance.address, 144 | RollupUtilities: rollupUtilsLib.address, 145 | NameRegistry: nameRegistryInstance.address, 146 | Logger: loggerInstance.address, 147 | MerkleTreeUtils: mtUtilsInstance.address, 148 | FraudProof: fraudProofInstance.address 149 | }; 150 | 151 | writeContractAddresses(contractAddresses); 152 | }; 153 | 154 | async function getMerkleRootWithCoordinatorAccount(maxSize) { 155 | var dataLeaves = []; 156 | var rollupUtils = await rollupUtilsLib.deployed(); 157 | dataLeaves = await rollupUtils.GetGenesisLeaves(); 158 | var ZERO_BYTES32 = 159 | "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"; 160 | var numOfAccsForCoord = dataLeaves.length; 161 | console.log( 162 | "Data leaves fetched from contract", 163 | dataLeaves, 164 | "count", 165 | dataLeaves.length 166 | ); 167 | var numberOfDataLeaves = 2 ** maxSize; 168 | // create empty leaves 169 | for (var i = numOfAccsForCoord; i < numberOfDataLeaves; i++) { 170 | dataLeaves[i] = ZERO_BYTES32; 171 | } 172 | MTUtilsInstance = await merkleTreeUtilsContract.deployed(); 173 | var result = await MTUtilsInstance.getMerkleRootFromLeaves(dataLeaves); 174 | console.log("result", result); 175 | return result; 176 | } 177 | 178 | async function deployAndRegister(deployer, contract, libs, args, name) { 179 | var nameRegistryInstance = await nameRegistryContract.deployed(); 180 | var paramManagerInstance = await paramManagerLib.deployed(); 181 | 182 | for (let i = 0; i < libs.length; i++) { 183 | await deployer.link(libs[i], contract); 184 | } 185 | var contractInstance = await deployer.deploy(contract, ...args); 186 | await nameRegistryInstance.registerName( 187 | await paramManagerInstance[name](), 188 | contractInstance.address 189 | ); 190 | return contractInstance; 191 | } 192 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bopr-contracts", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "rebuild": "npm run clean && npm run generate", 8 | "solc": "solc", 9 | "create-docs": "solidity-docgen", 10 | "clean": "rm -rf build types", 11 | "truffle:coverage": "truffle run coverage", 12 | "test": "truffle test", 13 | "lint": "prettier --check \"**/*.{sol,ts,js}\"", 14 | "prettier": "prettier --write \"**/*.{sol,ts,js}\"", 15 | "truffle": "truffle", 16 | "compile": "truffle compile", 17 | "migrate": "truffle migrate", 18 | "test-deposit": "truffle test test/deposit.ts", 19 | "generate": "truffle compile && typechain --target truffle './build/**/*.json'", 20 | "tsc": "tsc --noEmit", 21 | "ganache": "ganache-cli -q --mnemonic 'clock radar mass judge dismiss just intact mind resemble fringe diary casino' --gasLimit 8000000 --gasPrice 0 -b 1 -e=10000", 22 | "actions:network": "npm run ganache", 23 | "actions:tests": "npm test", 24 | "actions": "concurrently -r -s first -k npm:actions:*" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/vaibhavchellani/BOPR-contracts.git" 29 | }, 30 | "author": "", 31 | "license": "ISC", 32 | "bugs": { 33 | "url": "https://github.com/vaibhavchellani/BOPR-contracts/issues" 34 | }, 35 | "config": { 36 | "mnemonics": "clock radar mass judge dismiss just intact mind resemble fringe diary casino" 37 | }, 38 | "homepage": "https://github.com/vaibhavchellani/BOPR-contracts#readme", 39 | "dependencies": { 40 | "@types/bignumber.js": "^5.0.0", 41 | "@types/chai-as-promised": "^7.1.2", 42 | "abi-decoder": "^2.3.0", 43 | "big-number": "^2.0.0", 44 | "bip39": "^2.5.0", 45 | "bn.js": "^5.1.1", 46 | "chai": "^4.2.0", 47 | "chai-as-promised": "^7.1.1", 48 | "concurrently": "^5.2.0", 49 | "solidity-coverage": "^0.7.4", 50 | "eth-gas-reporter": "^0.2.16", 51 | "ethereumjs-util": "^6.2.0", 52 | "ethereumjs-wallet": "0.6.2", 53 | "ethers": "^4.0.46", 54 | "ganache-cli": "^6.9.1", 55 | "merkletreejs": "^0.1.7", 56 | "node-gyp": "^3.7.0", 57 | "solc": "^0.5.15", 58 | "solidity-bytes-utils": "0.0.8", 59 | "solidity-docgen": "^0.5.3", 60 | "truffle": "^5.0.44", 61 | "truffle-assertions": "^0.9.2", 62 | "truffle-hdwallet-provider": "^1.0.17", 63 | "truffle-typings": "^1.0.8", 64 | "ts-node": "^8.8.1", 65 | "typechain-target-truffle": "^1.0.2", 66 | "typescript": "^3.8.3", 67 | "zeppelin-solidity": "^1.12.0" 68 | }, 69 | "devDependencies": { 70 | "@openzeppelin/contracts": "^2.4.0", 71 | "prettier": "^1.19.1", 72 | "prettier-plugin-solidity": "^1.0.0-alpha.51", 73 | "ts-node": "^8.8.1", 74 | "typechain": "^1.0.5", 75 | "typings": "^2.1.1" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /scripts/FillDepositSubtree.js: -------------------------------------------------------------------------------- 1 | const contracts = require("../contractAddresses.json"); 2 | const walletHelper = require("./helpers/wallet"); 3 | const MerkleTreeUtils = artifacts.require("MerkleTreeUtils"); 4 | const ParamManager = artifacts.require("ParamManager"); 5 | const nameRegistry = artifacts.require("NameRegistry"); 6 | const TokenRegistry = artifacts.require("TokenRegistry"); 7 | const TestToken = artifacts.require("TestToken"); 8 | const DepositManager = artifacts.require("DepositManager"); 9 | const RollupCore = artifacts.require("Rollup"); 10 | const { ethers } = require("ethers"); 11 | 12 | async function stake() { 13 | // get deployed name registry instance 14 | var nameRegistryInstance = await nameRegistry.deployed(); 15 | 16 | // get deployed parama manager instance 17 | var paramManager = await ParamManager.deployed(); 18 | 19 | // get accounts tree key 20 | var tokenRegistryKey = await paramManager.TOKEN_REGISTRY(); 21 | 22 | var tokenRegistryAddress = await nameRegistryInstance.getContractDetails( 23 | tokenRegistryKey 24 | ); 25 | 26 | var wallets = walletHelper.generateFirstWallets(walletHelper.mnemonics, 10); 27 | 28 | let testToken = await TestToken.deployed(); 29 | let tokenRegistryInstance = await utils.getTokenRegistry(); 30 | let depositManagerInstance = await DepositManager.deployed(); 31 | 32 | await tokenRegistryInstance.requestTokenRegistration(testToken.address, { 33 | from: wallets[0].getAddressString() 34 | }); 35 | 36 | await tokenRegistryInstance.finaliseTokenRegistration(testToken.address, { 37 | from: wallets[0].getAddressString() 38 | }); 39 | 40 | await testToken.approve( 41 | depositManagerInstance.address, 42 | ethers.utils.parseEther("1"), 43 | { 44 | from: wallets[0].getAddressString() 45 | } 46 | ); 47 | 48 | var Alice = { 49 | Address: wallets[0].getAddressString(), 50 | Pubkey: wallets[0].getPublicKeyString(), 51 | Amount: 10, 52 | TokenType: 1, 53 | AccID: 1, 54 | Path: "2" 55 | }; 56 | 57 | var Bob = { 58 | Address: wallets[1].getAddressString(), 59 | Pubkey: wallets[1].getPublicKeyString(), 60 | Amount: 10, 61 | TokenType: 1, 62 | AccID: 2, 63 | Path: "3" 64 | }; 65 | 66 | await depositManagerInstance.deposit( 67 | Alice.Amount, 68 | Alice.TokenType, 69 | Alice.Pubkey 70 | ); 71 | await depositManagerInstance.depositFor( 72 | Bob.Address, 73 | Bob.Amount, 74 | Bob.TokenType, 75 | Bob.Pubkey 76 | ); 77 | 78 | // await depositManagerInstance.depositFor( 79 | // Bob.Address, 80 | // Bob.Amount, 81 | // Bob.TokenType, 82 | // Bob.Pubkey 83 | // ); 84 | // await depositManagerInstance.depositFor( 85 | // Bob.Address, 86 | // Bob.Amount, 87 | // Bob.TokenType, 88 | // Bob.Pubkey 89 | // ); 90 | } 91 | 92 | stake(); 93 | -------------------------------------------------------------------------------- /scripts/helpers/interfaces.ts: -------------------------------------------------------------------------------- 1 | export enum Usage { 2 | Genesis, 3 | Transfer, 4 | Deposit 5 | } 6 | export interface Account { 7 | ID: number; 8 | tokenType: number; 9 | balance: number; 10 | nonce: number; 11 | } 12 | 13 | export interface Transaction { 14 | fromIndex: number; 15 | toIndex: number; 16 | tokenType: number; 17 | amount: number; 18 | txType: number; 19 | nonce: number; 20 | signature?: string; 21 | } 22 | 23 | export enum ErrorCode { 24 | NoError, 25 | InvalidTokenAddress, 26 | InvalidTokenAmount, 27 | NotEnoughTokenBalance, 28 | BadFromTokenType, 29 | BadToTokenType 30 | } 31 | -------------------------------------------------------------------------------- /scripts/helpers/utils.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import * as ethUtils from "ethereumjs-util"; 3 | import { Usage, Account, Transaction } from "./interfaces"; 4 | const MerkleTreeUtils = artifacts.require("MerkleTreeUtils"); 5 | const ParamManager = artifacts.require("ParamManager"); 6 | const nameRegistry = artifacts.require("NameRegistry"); 7 | const TokenRegistry = artifacts.require("TokenRegistry"); 8 | const RollupUtils = artifacts.require("RollupUtils"); 9 | const FraudProof = artifacts.require("FraudProof"); 10 | const RollupCore = artifacts.require("Rollup"); 11 | 12 | // returns parent node hash given child node hashes 13 | export function getParentLeaf(left: string, right: string) { 14 | var abiCoder = ethers.utils.defaultAbiCoder; 15 | var hash = ethers.utils.keccak256( 16 | abiCoder.encode(["bytes32", "bytes32"], [left, right]) 17 | ); 18 | return hash; 19 | } 20 | 21 | export function Hash(data: string) { 22 | return ethers.utils.keccak256(data); 23 | } 24 | 25 | export function PubKeyHash(pubkey: string) { 26 | var abiCoder = ethers.utils.defaultAbiCoder; 27 | var result = ethers.utils.keccak256(abiCoder.encode(["bytes"], [pubkey])); 28 | return result; 29 | } 30 | 31 | export function StringToBytes32(data: string) { 32 | return ethers.utils.formatBytes32String(data); 33 | } 34 | 35 | export async function BytesFromAccountData( 36 | ID: number, 37 | balance: number, 38 | nonce: number, 39 | token: number 40 | ) { 41 | var rollupUtils = await RollupUtils.deployed(); 42 | var account = { 43 | ID: ID, 44 | tokenType: token, 45 | balance: balance, 46 | nonce: nonce 47 | }; 48 | return rollupUtils.BytesFromAccount(account); 49 | } 50 | 51 | export async function CreateAccountLeaf(account: Account) { 52 | const rollupUtils = await RollupUtils.deployed(); 53 | const result = await rollupUtils.getAccountHash( 54 | account.ID, 55 | account.balance, 56 | account.nonce, 57 | account.tokenType 58 | ); 59 | return result; 60 | } 61 | 62 | export async function createLeaf(accountAlias: any) { 63 | const account: Account = { 64 | ID: accountAlias.AccID, 65 | balance: accountAlias.Amount, 66 | tokenType: accountAlias.TokenType, 67 | nonce: accountAlias.nonce 68 | }; 69 | return await CreateAccountLeaf(account); 70 | } 71 | 72 | export async function BytesFromTx( 73 | from: number, 74 | to: number, 75 | token: number, 76 | amount: number, 77 | type: number, 78 | nonce: number 79 | ) { 80 | var rollupUtils = await RollupUtils.deployed(); 81 | var tx = { 82 | fromIndex: from, 83 | toIndex: to, 84 | tokenType: token, 85 | nonce: nonce, 86 | txType: type, 87 | amount: amount, 88 | signature: "" 89 | }; 90 | var result = await rollupUtils.BytesFromTx(tx); 91 | return result; 92 | } 93 | 94 | export async function HashFromTx( 95 | from: number, 96 | to: number, 97 | token: number, 98 | amount: number, 99 | type: number, 100 | nonce: number 101 | ) { 102 | var data = await BytesFromTx(from, to, token, amount, type, nonce); 103 | return Hash(data); 104 | } 105 | 106 | // returns parent node hash given child node hashes 107 | // are structured in a way that the leaf are at index 0 and index increases layer by layer to root 108 | // for depth =2 109 | // defaultHashes[0] = leaves 110 | // defaultHashes[depth-1] = root 111 | export async function defaultHashes(depth: number) { 112 | var zeroValue = 0; 113 | var defaultHashes = []; 114 | var abiCoder = ethers.utils.defaultAbiCoder; 115 | var zeroHash = await getZeroHash(zeroValue); 116 | defaultHashes[0] = zeroHash; 117 | 118 | for (let i = 1; i < depth; i++) { 119 | defaultHashes[i] = getParentLeaf( 120 | defaultHashes[i - 1], 121 | defaultHashes[i - 1] 122 | ); 123 | } 124 | 125 | return defaultHashes; 126 | } 127 | 128 | export async function getZeroHash(zeroValue: any) { 129 | var abiCoder = ethers.utils.defaultAbiCoder; 130 | return ethers.utils.keccak256(abiCoder.encode(["uint256"], [zeroValue])); 131 | } 132 | 133 | export async function getMerkleTreeUtils() { 134 | // get deployed name registry instance 135 | var nameRegistryInstance = await nameRegistry.deployed(); 136 | 137 | // get deployed parama manager instance 138 | var paramManager = await ParamManager.deployed(); 139 | 140 | // get accounts tree key 141 | var merkleTreeUtilKey = await paramManager.MERKLE_UTILS(); 142 | 143 | var merkleTreeUtilsAddr = await nameRegistryInstance.getContractDetails( 144 | merkleTreeUtilKey 145 | ); 146 | return MerkleTreeUtils.at(merkleTreeUtilsAddr); 147 | } 148 | 149 | export async function getRollupUtils() { 150 | var rollupUtils: any = await rollupUtils.deployed(); 151 | return rollupUtils; 152 | } 153 | 154 | export async function getMerkleRoot(dataLeaves: any, maxDepth: any) { 155 | var nextLevelLength = dataLeaves.length; 156 | var currentLevel = 0; 157 | var nodes: any = dataLeaves.slice(); 158 | var defaultHashesForLeaves: any = defaultHashes(maxDepth); 159 | // create a merkle root to see if this is valid 160 | while (nextLevelLength > 1) { 161 | currentLevel += 1; 162 | 163 | // Calculate the nodes for the currentLevel 164 | for (var i = 0; i < nextLevelLength / 2; i++) { 165 | nodes[i] = getParentLeaf(nodes[i * 2], nodes[i * 2 + 1]); 166 | } 167 | nextLevelLength = nextLevelLength / 2; 168 | // Check if we will need to add an extra node 169 | if (nextLevelLength % 2 == 1 && nextLevelLength != 1) { 170 | nodes[nextLevelLength] = defaultHashesForLeaves[currentLevel]; 171 | nextLevelLength += 1; 172 | } 173 | } 174 | return nodes[0]; 175 | } 176 | 177 | export async function genMerkleRootFromSiblings( 178 | siblings: any, 179 | path: string, 180 | leaf: string 181 | ) { 182 | var computedNode: any = leaf; 183 | var splitPath = path.split(""); 184 | for (var i = 0; i < siblings.length; i++) { 185 | var sibling = siblings[i]; 186 | if (splitPath[splitPath.length - i - 1] == "0") { 187 | computedNode = getParentLeaf(computedNode, sibling); 188 | } else { 189 | computedNode = getParentLeaf(sibling, computedNode); 190 | } 191 | } 192 | return computedNode; 193 | } 194 | 195 | export async function getTokenRegistry() { 196 | return TokenRegistry.deployed(); 197 | } 198 | 199 | export async function compressTx( 200 | from: number, 201 | to: number, 202 | nonce: number, 203 | amount: number, 204 | token: number, 205 | sig: any 206 | ) { 207 | var rollupUtils = await RollupUtils.deployed(); 208 | var tx = { 209 | fromIndex: from, 210 | toIndex: to, 211 | tokenType: token, 212 | nonce: nonce, 213 | txType: 1, 214 | amount: amount, 215 | signature: sig 216 | }; 217 | 218 | // TODO find out why this fails 219 | // await rollupUtils.CompressTx(tx); 220 | 221 | var message = await TxToBytes(tx); 222 | var result = await rollupUtils.CompressTxWithMessage(message, tx.signature); 223 | return result; 224 | } 225 | 226 | export async function signTx(tx: Transaction, wallet: any) { 227 | const RollupUtilsInstance = await RollupUtils.deployed(); 228 | const dataToSign = await RollupUtilsInstance.getTxSignBytes( 229 | tx.fromIndex, 230 | tx.toIndex, 231 | tx.tokenType, 232 | tx.txType, 233 | tx.nonce, 234 | tx.amount 235 | ); 236 | 237 | const h = ethUtils.toBuffer(dataToSign); 238 | const signature = ethUtils.ecsign(h, wallet.getPrivateKey()); 239 | return ethUtils.toRpcSig(signature.v, signature.r, signature.s); 240 | } 241 | 242 | export async function TxToBytes(tx: Transaction) { 243 | const RollupUtilsInstance = await RollupUtils.deployed(); 244 | var txBytes = await RollupUtilsInstance.BytesFromTxDeconstructed( 245 | tx.fromIndex, 246 | tx.toIndex, 247 | tx.tokenType, 248 | tx.nonce, 249 | tx.txType, 250 | tx.amount 251 | ); 252 | return txBytes; 253 | } 254 | 255 | export async function falseProcessTx(_tx: any, accountProofs: any) { 256 | const fraudProofInstance = await FraudProof.deployed(); 257 | const _to_merkle_proof = accountProofs.to; 258 | const new_to_txApply = await fraudProofInstance.ApplyTx( 259 | _to_merkle_proof, 260 | _tx 261 | ); 262 | return new_to_txApply.newRoot; 263 | } 264 | 265 | export async function compressAndSubmitBatch(tx: Transaction, newRoot: string) { 266 | const rollupCoreInstance = await RollupCore.deployed(); 267 | const compressedTx = await compressTx( 268 | tx.fromIndex, 269 | tx.toIndex, 270 | tx.nonce, 271 | tx.amount, 272 | tx.tokenType, 273 | tx.signature 274 | ); 275 | 276 | const compressedTxs = [compressedTx]; 277 | 278 | // submit batch for that transactions 279 | await rollupCoreInstance.submitBatch( 280 | compressedTxs, 281 | newRoot, 282 | Usage.Transfer, 283 | { 284 | value: ethers.utils.parseEther("32").toString() 285 | } 286 | ); 287 | } 288 | -------------------------------------------------------------------------------- /scripts/helpers/wallet.ts: -------------------------------------------------------------------------------- 1 | const bip39 = require("bip39"); 2 | const hdkey = require("ethereumjs-wallet/hdkey"); 3 | var packageJSON = require("../../package.json"); 4 | export const mnemonics = packageJSON.config.mnemonics; 5 | 6 | export function generateFirstWallets( 7 | mnemonics: any, 8 | n: number, 9 | hdPathIndex = 0 10 | ) { 11 | const hdwallet = hdkey.fromMasterSeed(bip39.mnemonicToSeed(mnemonics)); 12 | const result = []; 13 | for (let i = 0; i < n; i++) { 14 | const node = hdwallet.derivePath(`m/44'/60'/0'/0/${i + hdPathIndex}`); 15 | result.push(node.getWallet()); 16 | } 17 | 18 | return result; 19 | } 20 | -------------------------------------------------------------------------------- /test-blockchain/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | CWD=$PWD 4 | 5 | pkill -f parity 6 | 7 | rm -rf $CWD/data/* 8 | mkdir -p $CWD/data/keys/PrivatePoA 9 | cp -rf $CWD/keys/* $CWD/data/keys/PrivatePoA/ 10 | -------------------------------------------------------------------------------- /test-blockchain/config.toml: -------------------------------------------------------------------------------- 1 | [parity] 2 | chain = "poa-spec.json" 3 | base_path = "data" 4 | keys_path = "keys" 5 | 6 | [network] 7 | port = 30300 8 | 9 | [rpc] 10 | port = 8545 11 | interface = "all" 12 | cors = ["all"] 13 | hosts = ["all"] 14 | apis = ["web3", "eth", "net", "personal", "parity", "parity_set", "traces", "rpc", "parity_accounts"] 15 | 16 | [websockets] 17 | port = 8546 18 | interface = "all" 19 | hosts = ["all"] 20 | apis = ["web3", "eth", "pubsub", "net", "parity", "parity_pubsub", "traces", "rpc", "shh", "shh_pubsub"] 21 | 22 | [ui] 23 | port = 8180 24 | 25 | #[websockets] 26 | #port = 8541 27 | 28 | [mining] 29 | engine_signer = "0xf0ebfbd01347aaca487358132394a4b87945b553" # validator 30 | usd_per_tx = "0" 31 | reseal_on_txs = "none" 32 | force_sealing = true 33 | reseal_min_period = 1000 34 | reseal_max_period = 60000 35 | 36 | [account] 37 | unlock = ["0xf0ebfbd01347aaca487358132394a4b87945b553", "0x9fb29aac15b9a4b7f17c3385939b007540f4d791", "0x96c42c56fdb78294f96b0cfa33c92bed7d75f96a"] 38 | password = ["node.password"] 39 | -------------------------------------------------------------------------------- /test-blockchain/keys/PrivatePoA/UTC--2018-02-19T10-26-21.638675000Z--9fb29aac15b9a4b7f17c3385939b007540f4d791: -------------------------------------------------------------------------------- 1 | {"address":"9fb29aac15b9a4b7f17c3385939b007540f4d791","crypto":{"cipher":"aes-128-ctr","ciphertext":"316770a9dfa36b4954f7db53e07c5501c52db7312962cf96a763292f536c2acd","cipherparams":{"iv":"3c01ac619fbb4b5b9ad5b4aa7d3af618"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"f922663678b0645dd8792b1b9fed73647947152ef265cfd82f6497b60f9aeec3"},"mac":"17afbf05c4c0cc4de9f853eaed95e300e7fd72d44199ba8025e2f987bec7ba33"},"id":"8c0a13a8-dec8-4f97-8697-fbc6cb04dc31","version":3} 2 | -------------------------------------------------------------------------------- /test-blockchain/keys/PrivatePoA/UTC--2018-02-19T10-26-22.638675000Z--96c42c56fdb78294f96b0cfa33c92bed7d75f96a: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"4ad241bd-8040-4fd8-a255-dd1f0025092c","address":"96c42c56fdb78294f96b0cfa33c92bed7d75f96a","crypto":{"ciphertext":"898a367f56394f68e3acd650b6ffa11c930e6ac195475e9aefa031caaa6d1cd3","cipherparams":{"iv":"6632d97aeb74afc1ba95f462c3eddeb6"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"487b02baa43453204ec3803941d329363840a2482aa893386668c2137631f1e8","n":262144,"r":8,"p":1},"mac":"fe2bc21e35046261a039a851865d8ba433d0e52ba1d9844e586898af1164828f"}} 2 | -------------------------------------------------------------------------------- /test-blockchain/keys/PrivatePoA/UTC--2019-06-14T06-56-42Z--fcb0cece-1d59-82de-cea0-4e7ba2da6a86: -------------------------------------------------------------------------------- 1 | {"id":"fcb0cece-1d59-82de-cea0-4e7ba2da6a86","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"66d8b4d2cf33c328ca5b229154222cd9"},"ciphertext":"a338862c19bd51e93e072830d1a861473526b119592c72308acf4374fb7ce930","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"a66528528edc01217c566621af5a1cef9fcc86019e4cb45c4d65c576cf4c7d11"},"mac":"014a8afe3b8af5d5d4b6eec007e2bbd95bd8f6d3add992041a432cc8d97c248b"},"address":"f0ebfbd01347aaca487358132394a4b87945b553","name":"","meta":"{}"} -------------------------------------------------------------------------------- /test-blockchain/keys/PrivatePoA/address_book.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test-blockchain/node.password: -------------------------------------------------------------------------------- 1 | hello 2 | -------------------------------------------------------------------------------- /test-blockchain/poa-spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PrivatePoA", 3 | "engine": { 4 | "authorityRound": { 5 | "params": { 6 | "stepDuration": "1", 7 | "validators": { 8 | "list": ["0xf0ebfbd01347aaca487358132394a4b87945b553"] 9 | } 10 | } 11 | } 12 | }, 13 | "params": { 14 | "gasLimitBoundDivisor": "0x400", 15 | "maximumExtraDataSize": "0x20", 16 | "minGasLimit": "0x1388", 17 | "networkID": "0x3A99", 18 | "eip155Transition": 0, 19 | "validateChainIdTransition": 0, 20 | "eip140Transition": 0, 21 | "eip211Transition": 0, 22 | "eip214Transition": 0, 23 | "eip658Transition": 0, 24 | "eip145Transition": 0, 25 | "eip1014Transition": 0, 26 | "eip1052Transition": 0, 27 | "eip150Transition": "0", 28 | "eip160Transition": "0", 29 | "eip161abcTransition": "0", 30 | "eip161dTransition": "0", 31 | "eip1283Transition": "0x0", 32 | "eip1344Transition": "0", 33 | "eip1706Transition": "0", 34 | "eip1884Transition": "0", 35 | "eip2028Transition": "0" 36 | }, 37 | "genesis": { 38 | "seal": { 39 | "authorityRound": { 40 | "step": "0x0", 41 | "signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 42 | } 43 | }, 44 | "difficulty": "0x00001", 45 | "gasLimit": "0xFFF5B8D80" 46 | }, 47 | "accounts": { 48 | "0x0000000000000000000000000000000000000001": { 49 | "balance": "1", 50 | "builtin": { 51 | "name": "ecrecover", 52 | "pricing": { "linear": { "base": 3000, "word": 0 } } 53 | } 54 | }, 55 | "0x0000000000000000000000000000000000000002": { 56 | "balance": "1", 57 | "builtin": { 58 | "name": "sha256", 59 | "pricing": { "linear": { "base": 60, "word": 12 } } 60 | } 61 | }, 62 | "0x0000000000000000000000000000000000000003": { 63 | "balance": "1", 64 | "builtin": { 65 | "name": "ripemd160", 66 | "pricing": { "linear": { "base": 600, "word": 120 } } 67 | } 68 | }, 69 | "0x0000000000000000000000000000000000000004": { 70 | "balance": "1", 71 | "builtin": { 72 | "name": "identity", 73 | "pricing": { "linear": { "base": 15, "word": 3 } } 74 | } 75 | }, 76 | "0xf0ebfbd01347aaca487358132394a4b87945b553": { 77 | "balance": "10000000000000000000000000000" 78 | }, 79 | "0x9fb29aac15b9a4b7f17c3385939b007540f4d791": { 80 | "balance": "10000000000000000000000000000" 81 | }, 82 | "0x96c42c56fdb78294f96b0cfa33c92bed7d75f96a": { 83 | "balance": "10000000000000000000000000000" 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test-blockchain/start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | CWD=$PWD 4 | 5 | nohup parity --config $CWD/config.toml > $CWD/data/node.log 2>&1 & 6 | echo $! > $CWD/data/node.pid 7 | echo "Node started. Check test-blockchain/data/node.log for more logs" 8 | -------------------------------------------------------------------------------- /test-blockchain/stop.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | CWD=$PWD 4 | 5 | pkill -f parity 6 | -------------------------------------------------------------------------------- /test/Deposit.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from "chai"; 2 | import * as walletHelper from "../scripts/helpers/wallet"; 3 | import { Account } from "../scripts/helpers/interfaces"; 4 | const TestToken = artifacts.require("TestToken"); 5 | const chaiAsPromised = require("chai-as-promised"); 6 | const DepositManager = artifacts.require("DepositManager"); 7 | import * as utils from "../scripts/helpers/utils"; 8 | import { RollupContract } from "../types/truffle-contracts/index"; 9 | const abiDecoder = require("abi-decoder"); // NodeJS 10 | 11 | import { ethers } from "ethers"; 12 | const RollupCore = artifacts.require("Rollup"); 13 | const IMT = artifacts.require("IncrementalTree"); 14 | const RollupUtils = artifacts.require("RollupUtils"); 15 | const EcVerify = artifacts.require("ECVerify"); 16 | chai.use(chaiAsPromised); 17 | const truffleAssert = require("truffle-assertions"); 18 | 19 | contract("DepositManager", async function(accounts) { 20 | var wallets: any; 21 | before(async function() { 22 | wallets = walletHelper.generateFirstWallets(walletHelper.mnemonics, 10); 23 | }); 24 | 25 | it("should register a token", async function() { 26 | let testToken = await TestToken.deployed(); 27 | let tokenRegistryInstance = await utils.getTokenRegistry(); 28 | await tokenRegistryInstance.requestTokenRegistration( 29 | testToken.address, 30 | { 31 | from: wallets[0].getAddressString() 32 | } 33 | ); 34 | }); 35 | 36 | // ---------------------------------------------------------------------------------- 37 | 38 | it("should finalise token registration", async () => { 39 | let testToken = await TestToken.deployed(); 40 | let tokenRegistryInstance = await utils.getTokenRegistry(); 41 | 42 | let approveToken = await tokenRegistryInstance.finaliseTokenRegistration( 43 | testToken.address, 44 | { from: wallets[0].getAddressString() } 45 | ); 46 | assert(approveToken, "token registration failed"); 47 | }); 48 | 49 | // ---------------------------------------------------------------------------------- 50 | it("should approve Rollup on TestToken", async () => { 51 | let testToken = await TestToken.deployed(); 52 | let depositManagerInstance = await DepositManager.deployed(); 53 | let approveToken = await testToken.approve( 54 | depositManagerInstance.address, 55 | ethers.utils.parseEther("1"), 56 | { 57 | from: wallets[0].getAddressString() 58 | } 59 | ); 60 | assert(approveToken, "approveToken failed"); 61 | }); 62 | 63 | it("should allow depositing 2 leaves in a subtree and merging it", async () => { 64 | let depositManagerInstance = await DepositManager.deployed(); 65 | var rollupContractInstance = await RollupCore.deployed(); 66 | var testTokenInstance = await TestToken.deployed(); 67 | let rollupCoreInstance = await RollupCore.deployed(); 68 | var rollupUtilsInstance = await RollupUtils.deployed(); 69 | var MTutilsInstance = await utils.getMerkleTreeUtils(); 70 | 71 | var Alice = { 72 | Address: wallets[0].getAddressString(), 73 | Pubkey: wallets[0].getPublicKeyString(), 74 | Amount: 10, 75 | TokenType: 1, 76 | AccID: 2, 77 | Path: "2" 78 | }; 79 | var Bob = { 80 | Address: wallets[1].getAddressString(), 81 | Pubkey: wallets[1].getPublicKeyString(), 82 | Amount: 10, 83 | TokenType: 1, 84 | AccID: 3, 85 | Path: "3" 86 | }; 87 | var coordinator_leaves = await rollupUtilsInstance.GetGenesisLeaves(); 88 | 89 | var maxSize = 4; 90 | console.log("User information", "Alice", Alice, "bob", Bob); 91 | 92 | var BalanceOfAlice = await testTokenInstance.balanceOf(Alice.Address); 93 | 94 | // Deposit Alice 95 | await depositManagerInstance.deposit( 96 | Alice.Amount, 97 | Alice.TokenType, 98 | Alice.Pubkey 99 | ); 100 | const AliceAccount: Account = { 101 | ID: Alice.AccID, 102 | tokenType: Alice.TokenType, 103 | balance: Alice.Amount, 104 | nonce: 0 105 | }; 106 | var AliceAccountLeaf = await utils.CreateAccountLeaf(AliceAccount); 107 | 108 | var BalanceOfAliceAfterDeposit = await testTokenInstance.balanceOf( 109 | Alice.Address 110 | ); 111 | 112 | assert.equal( 113 | BalanceOfAliceAfterDeposit, 114 | BalanceOfAlice - Alice.Amount, 115 | "User balance did not reduce after deposit" 116 | ); 117 | 118 | // 119 | // do second deposit 120 | // 121 | 122 | // do a deposit for bob 123 | await depositManagerInstance.depositFor( 124 | Bob.Address, 125 | Bob.Amount, 126 | Bob.TokenType, 127 | Bob.Pubkey 128 | ); 129 | 130 | const BobAccount: Account = { 131 | ID: Bob.AccID, 132 | tokenType: Bob.TokenType, 133 | balance: Bob.Amount, 134 | nonce: 0 135 | }; 136 | var BobAccountLeaf = await utils.CreateAccountLeaf(BobAccount); 137 | 138 | var pendingDeposits0 = await depositManagerInstance.dequeue.call(); 139 | 140 | assert.equal( 141 | pendingDeposits0, 142 | utils.getParentLeaf(AliceAccountLeaf, BobAccountLeaf), 143 | "Account hash mismatch 2" 144 | ); 145 | 146 | var pendingDepositAfter = await depositManagerInstance.queueNumber(); 147 | assert.equal(pendingDepositAfter, 0, "pending deposits mismatch"); 148 | 149 | // do a deposit for bob 150 | await depositManagerInstance.depositFor( 151 | Bob.Address, 152 | Bob.Amount, 153 | Bob.TokenType, 154 | Bob.Pubkey 155 | ); 156 | 157 | // do a deposit for bob 158 | await depositManagerInstance.depositFor( 159 | Bob.Address, 160 | Bob.Amount, 161 | Bob.TokenType, 162 | Bob.Pubkey 163 | ); 164 | 165 | // finalise the deposit back to the state tree 166 | var path = "001"; 167 | 168 | var subtreeDepth = 1; 169 | var defaultHashes = await utils.defaultHashes(4); 170 | 171 | var siblingsInProof = [ 172 | utils.getParentLeaf(coordinator_leaves[0], coordinator_leaves[1]), 173 | defaultHashes[2], 174 | defaultHashes[3] 175 | ]; 176 | 177 | var _zero_account_mp = { 178 | accountIP: { 179 | pathToAccount: path, 180 | account: { 181 | ID: 0, 182 | tokenType: 0, 183 | balance: 0, 184 | nonce: 0 185 | } 186 | }, 187 | siblings: siblingsInProof 188 | }; 189 | 190 | await rollupContractInstance.finaliseDepositsAndSubmitBatch( 191 | subtreeDepth, 192 | _zero_account_mp, 193 | { value: ethers.utils.parseEther("32").toString() } 194 | ); 195 | 196 | // 197 | // verify accounts exist in the new balance root 198 | // 199 | var newBalanceRoot = await rollupCoreInstance.getLatestBalanceTreeRoot(); 200 | 201 | // verify sub tree has been inserted first at path 0 202 | var isSubTreeInserted = await MTutilsInstance.verifyLeaf( 203 | newBalanceRoot, 204 | utils.getParentLeaf(AliceAccountLeaf, BobAccountLeaf), 205 | "001", 206 | siblingsInProof 207 | ); 208 | expect(isSubTreeInserted).to.be.deep.eq(true); 209 | 210 | // verify first account at path 0001 211 | var account1siblings: Array = [ 212 | BobAccountLeaf, 213 | siblingsInProof[0], 214 | siblingsInProof[1], 215 | siblingsInProof[2] 216 | ]; 217 | var leaf = AliceAccountLeaf; 218 | var firstAccountPath: string = "2"; 219 | var isValid = await MTutilsInstance.verifyLeaf( 220 | newBalanceRoot, 221 | leaf, 222 | firstAccountPath, 223 | account1siblings 224 | ); 225 | expect(isValid).to.be.deep.eq(true); 226 | 227 | // verify second account at path 11 228 | var account2siblings: Array = [ 229 | AliceAccountLeaf, 230 | siblingsInProof[0], 231 | siblingsInProof[1], 232 | siblingsInProof[2] 233 | ]; 234 | var leaf = BobAccountLeaf; 235 | var secondAccountPath: string = "3"; 236 | var isValid = await MTutilsInstance.verifyLeaf( 237 | newBalanceRoot, 238 | leaf, 239 | secondAccountPath, 240 | account2siblings 241 | ); 242 | expect(isValid).to.be.deep.eq(true); 243 | }); 244 | }); 245 | -------------------------------------------------------------------------------- /test/Rollup.spec.ts: -------------------------------------------------------------------------------- 1 | import * as utils from "../scripts/helpers/utils"; 2 | import { ethers } from "ethers"; 3 | import * as walletHelper from "../scripts/helpers/wallet"; 4 | import { Usage, Transaction, ErrorCode } from "../scripts/helpers/interfaces"; 5 | const RollupCore = artifacts.require("Rollup"); 6 | const TestToken = artifacts.require("TestToken"); 7 | const DepositManager = artifacts.require("DepositManager"); 8 | const IMT = artifacts.require("IncrementalTree"); 9 | const RollupUtils = artifacts.require("RollupUtils"); 10 | const EcVerify = artifacts.require("ECVerify"); 11 | 12 | contract("Rollup", async function(accounts) { 13 | var wallets: any; 14 | 15 | let depositManagerInstance: any; 16 | let testTokenInstance: any; 17 | let rollupCoreInstance: any; 18 | let MTutilsInstance: any; 19 | let testToken: any; 20 | let RollupUtilsInstance: any; 21 | let tokenRegistryInstance: any; 22 | let IMTInstance: any; 23 | 24 | let Alice: any; 25 | let Bob: any; 26 | 27 | let coordinator_leaves: any; 28 | let coordinatorPubkeyHash: any; 29 | let maxSize: any; 30 | var zeroHashes: any; 31 | 32 | let falseBatchZero: any; 33 | let falseBatchOne: any; 34 | let falseBatchTwo: any; 35 | let falseBatchThree: any; 36 | let falseBatchFour: any; 37 | let falseBatchFive: any; 38 | let falseBatchComb: any; 39 | 40 | let AlicePDAsiblings: any; 41 | 42 | let BobPDAsiblings: any; 43 | 44 | let alicePDAProof: any; 45 | 46 | before(async function() { 47 | wallets = walletHelper.generateFirstWallets(walletHelper.mnemonics, 10); 48 | depositManagerInstance = await DepositManager.deployed(); 49 | testTokenInstance = await TestToken.deployed(); 50 | rollupCoreInstance = await RollupCore.deployed(); 51 | MTutilsInstance = await utils.getMerkleTreeUtils(); 52 | testToken = await TestToken.deployed(); 53 | RollupUtilsInstance = await RollupUtils.deployed(); 54 | tokenRegistryInstance = await utils.getTokenRegistry(); 55 | IMTInstance = await IMT.deployed(); 56 | 57 | Alice = { 58 | Address: wallets[0].getAddressString(), 59 | Pubkey: wallets[0].getPublicKeyString(), 60 | Amount: 10, 61 | TokenType: 1, 62 | AccID: 2, 63 | Path: "2", 64 | nonce: 0 65 | }; 66 | Bob = { 67 | Address: wallets[1].getAddressString(), 68 | Pubkey: wallets[1].getPublicKeyString(), 69 | Amount: 10, 70 | TokenType: 1, 71 | AccID: 3, 72 | Path: "3", 73 | nonce: 0 74 | }; 75 | 76 | coordinator_leaves = await RollupUtilsInstance.GetGenesisLeaves(); 77 | coordinatorPubkeyHash = 78 | "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"; 79 | maxSize = 4; 80 | zeroHashes = await utils.defaultHashes(maxSize); 81 | 82 | AlicePDAsiblings = [ 83 | utils.PubKeyHash(Bob.Pubkey), 84 | utils.getParentLeaf(coordinatorPubkeyHash, coordinatorPubkeyHash), 85 | zeroHashes[2], 86 | zeroHashes[3] 87 | ]; 88 | 89 | BobPDAsiblings = [ 90 | utils.PubKeyHash(Alice.Pubkey), 91 | utils.getParentLeaf( 92 | coordinatorPubkeyHash, 93 | utils.PubKeyHash(Alice.Pubkey) 94 | ), 95 | zeroHashes[2], 96 | zeroHashes[3] 97 | ]; 98 | 99 | alicePDAProof = { 100 | _pda: { 101 | pathToPubkey: "2", 102 | pubkey_leaf: { pubkey: Alice.Pubkey } 103 | }, 104 | siblings: AlicePDAsiblings 105 | }; 106 | }); 107 | 108 | // test if we are able to create append a leaf 109 | it("make a deposit of 2 accounts", async function() { 110 | await tokenRegistryInstance.requestTokenRegistration( 111 | testToken.address, 112 | { 113 | from: wallets[0].getAddressString() 114 | } 115 | ); 116 | await tokenRegistryInstance.finaliseTokenRegistration( 117 | testToken.address, 118 | { 119 | from: wallets[0].getAddressString() 120 | } 121 | ); 122 | await testToken.approve( 123 | depositManagerInstance.address, 124 | ethers.utils.parseEther("1"), 125 | { 126 | from: wallets[0].getAddressString() 127 | } 128 | ); 129 | 130 | await testTokenInstance.transfer(Alice.Address, 100); 131 | await depositManagerInstance.deposit( 132 | Alice.Amount, 133 | Alice.TokenType, 134 | Alice.Pubkey 135 | ); 136 | await depositManagerInstance.depositFor( 137 | Bob.Address, 138 | Bob.Amount, 139 | Bob.TokenType, 140 | Bob.Pubkey 141 | ); 142 | 143 | var subtreeDepth = 1; 144 | 145 | // finalise the deposit back to the state tree 146 | var path = "001"; 147 | var defaultHashes = await utils.defaultHashes(maxSize); 148 | var siblingsInProof = [ 149 | utils.getParentLeaf(coordinator_leaves[0], coordinator_leaves[1]), 150 | defaultHashes[2], 151 | defaultHashes[3] 152 | ]; 153 | 154 | var _zero_account_mp = { 155 | accountIP: { 156 | pathToAccount: path, 157 | account: { 158 | ID: 0, 159 | tokenType: 0, 160 | balance: 0, 161 | nonce: 0 162 | } 163 | }, 164 | siblings: siblingsInProof 165 | }; 166 | 167 | await rollupCoreInstance.finaliseDepositsAndSubmitBatch( 168 | subtreeDepth, 169 | _zero_account_mp, 170 | { value: ethers.utils.parseEther("32").toString() } 171 | ); 172 | }); 173 | 174 | it("submit new batch 1st", async function() { 175 | var AliceAccountLeaf = await utils.createLeaf(Alice); 176 | var BobAccountLeaf = await utils.createLeaf(Bob); 177 | 178 | // make a transfer between alice and bob's account 179 | var tranferAmount = 1; 180 | // prepare data for process Tx 181 | var currentRoot = await rollupCoreInstance.getLatestBalanceTreeRoot(); 182 | var accountRoot = await IMTInstance.getTreeRoot(); 183 | 184 | var isValid = await MTutilsInstance.verifyLeaf( 185 | accountRoot, 186 | utils.PubKeyHash(Alice.Pubkey), 187 | "2", 188 | AlicePDAsiblings 189 | ); 190 | assert.equal(isValid, true, "pda proof wrong"); 191 | 192 | var tx: Transaction = { 193 | fromIndex: Alice.AccID, 194 | toIndex: Bob.AccID, 195 | tokenType: Alice.TokenType, 196 | amount: tranferAmount, 197 | txType: 1, 198 | nonce: 1 199 | }; 200 | 201 | tx.signature = await utils.signTx(tx, wallets[0]); 202 | 203 | // alice balance tree merkle proof 204 | var AliceAccountSiblings: Array = [ 205 | BobAccountLeaf, 206 | utils.getParentLeaf(coordinator_leaves[0], coordinator_leaves[1]), 207 | zeroHashes[2], 208 | zeroHashes[3] 209 | ]; 210 | var leaf = AliceAccountLeaf; 211 | var AliceAccountPath: string = "2"; 212 | var isValid = await MTutilsInstance.verifyLeaf( 213 | currentRoot, 214 | leaf, 215 | AliceAccountPath, 216 | AliceAccountSiblings 217 | ); 218 | expect(isValid).to.be.deep.eq(true); 219 | 220 | var AliceAccountMP = { 221 | accountIP: { 222 | pathToAccount: AliceAccountPath, 223 | account: { 224 | ID: Alice.AccID, 225 | tokenType: Alice.TokenType, 226 | balance: Alice.Amount, 227 | nonce: Alice.nonce 228 | } 229 | }, 230 | siblings: AliceAccountSiblings 231 | }; 232 | 233 | Alice.Amount -= Number(tx.amount); 234 | Alice.nonce++; 235 | 236 | var UpdatedAliceAccountLeaf = await utils.createLeaf(Alice); 237 | // bob balance tree merkle proof 238 | var BobAccountSiblings: Array = [ 239 | UpdatedAliceAccountLeaf, 240 | utils.getParentLeaf(coordinator_leaves[0], coordinator_leaves[1]), 241 | zeroHashes[2], 242 | zeroHashes[3] 243 | ]; 244 | var leaf = BobAccountLeaf; 245 | var BobAccountPath: string = "3"; 246 | 247 | var BobAccountMP = { 248 | accountIP: { 249 | pathToAccount: BobAccountPath, 250 | account: { 251 | ID: Bob.AccID, 252 | tokenType: Bob.TokenType, 253 | balance: Bob.Amount, 254 | nonce: Bob.nonce 255 | } 256 | }, 257 | siblings: BobAccountSiblings 258 | }; 259 | 260 | Bob.Amount += Number(tx.amount); 261 | 262 | var accountProofs = { 263 | from: AliceAccountMP, 264 | to: BobAccountMP 265 | }; 266 | // process transaction validity with process tx 267 | var result = await rollupCoreInstance.processTx( 268 | currentRoot, 269 | accountRoot, 270 | tx.signature, 271 | await utils.TxToBytes(tx), 272 | alicePDAProof, 273 | accountProofs 274 | ); 275 | 276 | console.log("result from processTx: " + JSON.stringify(result)); 277 | await utils.compressAndSubmitBatch(tx, result[0]); 278 | 279 | falseBatchZero = { 280 | batchId: 0, 281 | txs: [tx], 282 | batchProofs: { 283 | accountProofs: [accountProofs], 284 | pdaProof: [alicePDAProof] 285 | } 286 | }; 287 | 288 | let batchId = await rollupCoreInstance.numOfBatchesSubmitted(); 289 | falseBatchZero.batchId = Number(batchId) - 1; 290 | }); 291 | 292 | it("dispute batch correct 1th batch(no error)", async function() { 293 | await rollupCoreInstance.disputeBatch( 294 | falseBatchZero.batchId, 295 | falseBatchZero.txs, 296 | falseBatchZero.batchProofs 297 | ); 298 | 299 | let batchId = await rollupCoreInstance.numOfBatchesSubmitted(); 300 | let batchMarker = await rollupCoreInstance.invalidBatchMarker(); 301 | assert.equal(batchMarker, "0", "batchMarker is not zero"); 302 | assert.equal( 303 | batchId - 1, 304 | falseBatchZero.batchId, 305 | "dispute shouldnt happen" 306 | ); 307 | }); 308 | 309 | it("submit new batch 2nd(False Batch)", async function() { 310 | var AliceAccountLeaf = await utils.createLeaf(Alice); 311 | var BobAccountLeaf = await utils.createLeaf(Bob); 312 | 313 | // make a transfer between alice and bob's account 314 | var tranferAmount = 1; 315 | // prepare data for process Tx 316 | var currentRoot = await rollupCoreInstance.getLatestBalanceTreeRoot(); 317 | var accountRoot = await IMTInstance.getTreeRoot(); 318 | 319 | var isValid = await MTutilsInstance.verifyLeaf( 320 | accountRoot, 321 | utils.PubKeyHash(Alice.Pubkey), 322 | "2", 323 | AlicePDAsiblings 324 | ); 325 | assert.equal(isValid, true, "pda proof wrong"); 326 | 327 | var tx: Transaction = { 328 | fromIndex: Alice.AccID, 329 | toIndex: Bob.AccID, 330 | // tokenType: Alice.TokenType, 331 | tokenType: 2, // false token type (Token not valid) 332 | amount: tranferAmount, 333 | txType: 1, 334 | nonce: 2 335 | }; 336 | tx.signature = await utils.signTx(tx, wallets[0]); 337 | 338 | // alice balance tree merkle proof 339 | var AliceAccountSiblings: Array = [ 340 | BobAccountLeaf, 341 | utils.getParentLeaf(coordinator_leaves[0], coordinator_leaves[1]), 342 | zeroHashes[2], 343 | zeroHashes[3] 344 | ]; 345 | var leaf = AliceAccountLeaf; 346 | var AliceAccountPath: string = "2"; 347 | var isValid = await MTutilsInstance.verifyLeaf( 348 | currentRoot, 349 | leaf, 350 | AliceAccountPath, 351 | AliceAccountSiblings 352 | ); 353 | expect(isValid).to.be.deep.eq(true); 354 | var AliceAccountMP = { 355 | accountIP: { 356 | pathToAccount: AliceAccountPath, 357 | account: { 358 | ID: Alice.AccID, 359 | tokenType: Alice.TokenType, 360 | balance: Alice.Amount, 361 | nonce: Alice.nonce 362 | } 363 | }, 364 | siblings: AliceAccountSiblings 365 | }; 366 | 367 | Alice.Amount -= Number(tx.amount); 368 | Alice.nonce++; 369 | 370 | var UpdatedAliceAccountLeaf = await utils.createLeaf(Alice); 371 | 372 | // bob balance tree merkle proof 373 | var BobAccountSiblings: Array = [ 374 | UpdatedAliceAccountLeaf, 375 | utils.getParentLeaf(coordinator_leaves[0], coordinator_leaves[1]), 376 | zeroHashes[2], 377 | zeroHashes[3] 378 | ]; 379 | var leaf = BobAccountLeaf; 380 | var BobAccountPath: string = "3"; 381 | 382 | var BobAccountMP = { 383 | accountIP: { 384 | pathToAccount: BobAccountPath, 385 | account: { 386 | ID: Bob.AccID, 387 | tokenType: Bob.TokenType, 388 | balance: Bob.Amount, 389 | nonce: Bob.nonce 390 | } 391 | }, 392 | siblings: BobAccountSiblings 393 | }; 394 | 395 | Bob.Amount += Number(tx.amount); 396 | var accountProofs = { 397 | from: AliceAccountMP, 398 | to: BobAccountMP 399 | }; 400 | 401 | // process transaction validity with process tx 402 | var result = await rollupCoreInstance.processTx( 403 | currentRoot, 404 | accountRoot, 405 | tx.signature, 406 | await utils.TxToBytes(tx), 407 | alicePDAProof, 408 | accountProofs 409 | ); 410 | 411 | var falseResult = await utils.falseProcessTx(tx, accountProofs); 412 | assert.equal( 413 | result[3], 414 | ErrorCode.InvalidTokenAddress, 415 | "False error ID. It should be `1`" 416 | ); 417 | await utils.compressAndSubmitBatch(tx, falseResult); 418 | 419 | falseBatchOne = { 420 | batchId: 0, 421 | txs: [tx], 422 | batchProofs: { 423 | accountProofs: [accountProofs], 424 | pdaProof: [alicePDAProof] 425 | } 426 | }; 427 | 428 | let batchId = await rollupCoreInstance.numOfBatchesSubmitted(); 429 | falseBatchOne.batchId = Number(batchId) - 1; 430 | // console.log(falseBatchOne) 431 | }); 432 | it("dispute batch false 2nd batch(Tx Token Type not valid)", async function() { 433 | await rollupCoreInstance.disputeBatch( 434 | falseBatchOne.batchId, 435 | falseBatchOne.txs, 436 | falseBatchOne.batchProofs 437 | ); 438 | 439 | let batchId = await rollupCoreInstance.numOfBatchesSubmitted(); 440 | let batchMarker = await rollupCoreInstance.invalidBatchMarker(); 441 | assert.equal(batchMarker, "0", "invalidBatchMarker is not zero"); 442 | assert.equal( 443 | batchId - 1, 444 | falseBatchOne.batchId - 1, 445 | "batchId doesnt match" 446 | ); 447 | Alice.Amount += falseBatchOne.txs[0].amount; 448 | Bob.Amount -= falseBatchOne.txs[0].amount; 449 | Alice.nonce--; 450 | }); 451 | 452 | it("submit new batch 3rd", async function() { 453 | var AliceAccountLeaf = await utils.createLeaf(Alice); 454 | var BobAccountLeaf = await utils.createLeaf(Bob); 455 | 456 | // make a transfer between alice and bob's account 457 | var tranferAmount = 1; 458 | // prepare data for process Tx 459 | var currentRoot = await rollupCoreInstance.getLatestBalanceTreeRoot(); 460 | var accountRoot = await IMTInstance.getTreeRoot(); 461 | 462 | var isValid = await MTutilsInstance.verifyLeaf( 463 | accountRoot, 464 | utils.PubKeyHash(Alice.Pubkey), 465 | "2", 466 | AlicePDAsiblings 467 | ); 468 | assert.equal(isValid, true, "pda proof wrong"); 469 | 470 | var tx: Transaction = { 471 | fromIndex: Alice.AccID, 472 | toIndex: Bob.AccID, 473 | tokenType: Alice.TokenType, 474 | amount: 0, // Error 475 | txType: 1, 476 | nonce: 2 477 | }; 478 | tx.signature = await utils.signTx(tx, wallets[0]); 479 | 480 | // alice balance tree merkle proof 481 | var AliceAccountSiblings: Array = [ 482 | BobAccountLeaf, 483 | utils.getParentLeaf(coordinator_leaves[0], coordinator_leaves[1]), 484 | zeroHashes[2], 485 | zeroHashes[3] 486 | ]; 487 | var leaf = AliceAccountLeaf; 488 | var AliceAccountPath: string = "2"; 489 | var isValid = await MTutilsInstance.verifyLeaf( 490 | currentRoot, 491 | leaf, 492 | AliceAccountPath, 493 | AliceAccountSiblings 494 | ); 495 | expect(isValid).to.be.deep.eq(true); 496 | var AliceAccountMP = { 497 | accountIP: { 498 | pathToAccount: AliceAccountPath, 499 | account: { 500 | ID: Alice.AccID, 501 | tokenType: Alice.TokenType, 502 | balance: Alice.Amount, 503 | nonce: Alice.nonce 504 | } 505 | }, 506 | siblings: AliceAccountSiblings 507 | }; 508 | 509 | Alice.Amount -= Number(tx.amount); 510 | Alice.nonce++; 511 | 512 | var UpdatedAliceAccountLeaf = await utils.createLeaf(Alice); 513 | 514 | // bob balance tree merkle proof 515 | var BobAccountSiblings: Array = [ 516 | UpdatedAliceAccountLeaf, 517 | utils.getParentLeaf(coordinator_leaves[0], coordinator_leaves[1]), 518 | zeroHashes[2], 519 | zeroHashes[3] 520 | ]; 521 | var leaf = BobAccountLeaf; 522 | var BobAccountPath: string = "3"; 523 | var isBobValid = await MTutilsInstance.verifyLeaf( 524 | currentRoot, 525 | leaf, 526 | BobAccountPath, 527 | BobAccountSiblings 528 | ); 529 | 530 | var BobAccountMP = { 531 | accountIP: { 532 | pathToAccount: BobAccountPath, 533 | account: { 534 | ID: Bob.AccID, 535 | tokenType: Bob.TokenType, 536 | balance: Bob.Amount, 537 | nonce: Bob.nonce 538 | } 539 | }, 540 | siblings: BobAccountSiblings 541 | }; 542 | 543 | Bob.Amount += Number(tx.amount); 544 | var accountProofs = { 545 | from: AliceAccountMP, 546 | to: BobAccountMP 547 | }; 548 | 549 | // process transaction validity with process tx 550 | var result = await rollupCoreInstance.processTx( 551 | currentRoot, 552 | accountRoot, 553 | tx.signature, 554 | await utils.TxToBytes(tx), 555 | alicePDAProof, 556 | accountProofs 557 | ); 558 | 559 | var falseResult = await utils.falseProcessTx(tx, accountProofs); 560 | assert.equal( 561 | result[3], 562 | ErrorCode.InvalidTokenAmount, 563 | "false Error Id. It should be `2`." 564 | ); 565 | 566 | await utils.compressAndSubmitBatch(tx, falseResult); 567 | 568 | falseBatchTwo = { 569 | batchId: 0, 570 | txs: [tx], 571 | batchProofs: { 572 | accountProofs: [accountProofs], 573 | pdaProof: [alicePDAProof] 574 | } 575 | }; 576 | 577 | let batchId = await rollupCoreInstance.numOfBatchesSubmitted(); 578 | falseBatchTwo.batchId = Number(batchId) - 1; 579 | }); 580 | 581 | it("dispute batch false 3rd batch(Tx amount 0)", async function() { 582 | await rollupCoreInstance.disputeBatch( 583 | falseBatchTwo.batchId, 584 | falseBatchTwo.txs, 585 | falseBatchTwo.batchProofs 586 | ); 587 | 588 | let batchId = await rollupCoreInstance.numOfBatchesSubmitted(); 589 | let batchMarker = await rollupCoreInstance.invalidBatchMarker(); 590 | assert.equal(batchMarker, "0", "batchMarker is not zero"); 591 | assert.equal( 592 | batchId - 1, 593 | falseBatchTwo.batchId - 1, 594 | "batchId doesnt match" 595 | ); 596 | Alice.Amount += falseBatchTwo.txs[0].amount; 597 | Bob.Amount -= falseBatchTwo.txs[0].amount; 598 | Alice.nonce--; 599 | }); 600 | 601 | it("Registring new token", async function() { 602 | await TestToken.new().then(async (instance: any) => { 603 | let testToken2Instance = instance; 604 | console.log( 605 | "testToken2Instance.address: ", 606 | testToken2Instance.address 607 | ); 608 | await tokenRegistryInstance.requestTokenRegistration( 609 | testToken2Instance.address, 610 | { 611 | from: wallets[0].getAddressString() 612 | } 613 | ); 614 | await tokenRegistryInstance.finaliseTokenRegistration( 615 | testToken2Instance.address, 616 | { 617 | from: wallets[0].getAddressString() 618 | } 619 | ); 620 | }); 621 | var tokenAddress = await tokenRegistryInstance.registeredTokens(2); 622 | // TODO 623 | }); 624 | 625 | it("submit new batch 5nd", async function() { 626 | var AliceAccountLeaf = await utils.createLeaf(Alice); 627 | var BobAccountLeaf = await utils.createLeaf(Bob); 628 | 629 | // make a transfer between alice and bob's account 630 | var tranferAmount = 1; 631 | // prepare data for process Tx 632 | var currentRoot = await rollupCoreInstance.getLatestBalanceTreeRoot(); 633 | var accountRoot = await IMTInstance.getTreeRoot(); 634 | 635 | var isValid = await MTutilsInstance.verifyLeaf( 636 | accountRoot, 637 | utils.PubKeyHash(Alice.Pubkey), 638 | "2", 639 | AlicePDAsiblings 640 | ); 641 | assert.equal(isValid, true, "pda proof wrong"); 642 | 643 | var tx: Transaction = { 644 | fromIndex: Alice.AccID, 645 | toIndex: Bob.AccID, 646 | tokenType: 2, // error 647 | amount: tranferAmount, 648 | txType: 1, 649 | nonce: 2 650 | }; 651 | 652 | tx.signature = await utils.signTx(tx, wallets[0]); 653 | 654 | // alice balance tree merkle proof 655 | var AliceAccountSiblings: Array = [ 656 | BobAccountLeaf, 657 | utils.getParentLeaf(coordinator_leaves[0], coordinator_leaves[1]), 658 | zeroHashes[2], 659 | zeroHashes[3] 660 | ]; 661 | var leaf = AliceAccountLeaf; 662 | var AliceAccountPath: string = "2"; 663 | var isValid = await MTutilsInstance.verifyLeaf( 664 | currentRoot, 665 | leaf, 666 | AliceAccountPath, 667 | AliceAccountSiblings 668 | ); 669 | expect(isValid).to.be.deep.eq(true); 670 | var AliceAccountMP = { 671 | accountIP: { 672 | pathToAccount: AliceAccountPath, 673 | account: { 674 | ID: Alice.AccID, 675 | tokenType: Alice.TokenType, 676 | balance: Alice.Amount, 677 | nonce: Alice.nonce 678 | } 679 | }, 680 | siblings: AliceAccountSiblings 681 | }; 682 | 683 | Alice.Amount -= Number(tx.amount); 684 | Alice.nonce++; 685 | 686 | var UpdatedAliceAccountLeaf = await utils.createLeaf(Alice); 687 | 688 | // bob balance tree merkle proof 689 | var BobAccountSiblings: Array = [ 690 | UpdatedAliceAccountLeaf, 691 | utils.getParentLeaf(coordinator_leaves[0], coordinator_leaves[1]), 692 | zeroHashes[2], 693 | zeroHashes[3] 694 | ]; 695 | var leaf = BobAccountLeaf; 696 | var BobAccountPath: string = "3"; 697 | var isBobValid = await MTutilsInstance.verifyLeaf( 698 | currentRoot, 699 | leaf, 700 | BobAccountPath, 701 | BobAccountSiblings 702 | ); 703 | 704 | var BobAccountMP = { 705 | accountIP: { 706 | pathToAccount: BobAccountPath, 707 | account: { 708 | ID: Bob.AccID, 709 | tokenType: Bob.TokenType, 710 | balance: Bob.Amount, 711 | nonce: Bob.nonce 712 | } 713 | }, 714 | siblings: BobAccountSiblings 715 | }; 716 | 717 | Bob.Amount += Number(tx.amount); 718 | var accountProofs = { 719 | from: AliceAccountMP, 720 | to: BobAccountMP 721 | }; 722 | 723 | // process transaction validity with process tx 724 | var result = await rollupCoreInstance.processTx( 725 | currentRoot, 726 | accountRoot, 727 | tx.signature, 728 | await utils.TxToBytes(tx), 729 | alicePDAProof, 730 | accountProofs 731 | ); 732 | 733 | var falseResult = await utils.falseProcessTx(tx, accountProofs); 734 | assert.equal( 735 | result[3], 736 | ErrorCode.BadFromTokenType, 737 | "False ErrorId. It should be `4`" 738 | ); 739 | await utils.compressAndSubmitBatch(tx, falseResult); 740 | 741 | falseBatchFive = { 742 | batchId: 0, 743 | txs: [tx], 744 | batchProofs: { 745 | accountProofs: [accountProofs], 746 | pdaProof: [alicePDAProof] 747 | } 748 | }; 749 | 750 | let batchId = await rollupCoreInstance.numOfBatchesSubmitted(); 751 | falseBatchFive.batchId = Number(batchId) - 1; 752 | }); 753 | it("dispute batch false 5th batch(From Token Type)", async function() { 754 | await rollupCoreInstance.disputeBatch( 755 | falseBatchFive.batchId, 756 | falseBatchFive.txs, 757 | falseBatchFive.batchProofs 758 | ); 759 | 760 | let batchId = await rollupCoreInstance.numOfBatchesSubmitted(); 761 | let batchMarker = await rollupCoreInstance.invalidBatchMarker(); 762 | assert.equal(batchMarker, "0", "batchMarker is not zero"); 763 | assert.equal( 764 | batchId - 1, 765 | falseBatchFive.batchId - 1, 766 | "batchId doesnt match" 767 | ); 768 | Alice.Amount += falseBatchFive.txs[0].amount; 769 | Bob.Amount -= falseBatchFive.txs[0].amount; 770 | Alice.nonce--; 771 | }); 772 | 773 | it("submit new batch 6nd(False Batch)", async function() { 774 | var AliceAccountLeaf = await utils.createLeaf(Alice); 775 | var BobAccountLeaf = await utils.createLeaf(Bob); 776 | 777 | // make a transfer between alice and bob's account 778 | var tranferAmount = 1; 779 | // prepare data for process Tx 780 | var currentRoot = await rollupCoreInstance.getLatestBalanceTreeRoot(); 781 | var accountRoot = await IMTInstance.getTreeRoot(); 782 | 783 | var isValid = await MTutilsInstance.verifyLeaf( 784 | accountRoot, 785 | utils.PubKeyHash(Alice.Pubkey), 786 | "2", 787 | AlicePDAsiblings 788 | ); 789 | assert.equal(isValid, true, "pda proof wrong"); 790 | 791 | var bobPDAProof = { 792 | _pda: { 793 | pathToPubkey: "2", 794 | pubkey_leaf: { pubkey: Bob.Pubkey } 795 | }, 796 | siblings: BobPDAsiblings 797 | }; 798 | 799 | var tx: Transaction = { 800 | fromIndex: Alice.AccID, 801 | toIndex: Bob.AccID, 802 | tokenType: 3, // false type 803 | amount: tranferAmount, 804 | txType: 1, 805 | nonce: 2 806 | }; 807 | tx.signature = await utils.signTx(tx, wallets[0]); 808 | // alice balance tree merkle proof 809 | var AliceAccountSiblings: Array = [ 810 | BobAccountLeaf, 811 | utils.getParentLeaf(coordinator_leaves[0], coordinator_leaves[1]), 812 | zeroHashes[2], 813 | zeroHashes[3] 814 | ]; 815 | var leaf = AliceAccountLeaf; 816 | var AliceAccountPath: string = "2"; 817 | var isValid = await MTutilsInstance.verifyLeaf( 818 | currentRoot, 819 | leaf, 820 | AliceAccountPath, 821 | AliceAccountSiblings 822 | ); 823 | expect(isValid).to.be.deep.eq(true); 824 | var AliceAccountMP = { 825 | accountIP: { 826 | pathToAccount: AliceAccountPath, 827 | account: { 828 | ID: Alice.AccID, 829 | tokenType: Alice.TokenType, 830 | balance: Alice.Amount, 831 | nonce: Alice.nonce 832 | } 833 | }, 834 | siblings: AliceAccountSiblings 835 | }; 836 | 837 | Alice.Amount -= Number(tx.amount); 838 | Alice.nonce++; 839 | 840 | var UpdatedAliceAccountLeaf = await utils.createLeaf(Alice); 841 | 842 | // bob balance tree merkle proof 843 | var BobAccountSiblings: Array = [ 844 | UpdatedAliceAccountLeaf, 845 | utils.getParentLeaf(coordinator_leaves[0], coordinator_leaves[1]), 846 | zeroHashes[2], 847 | zeroHashes[3] 848 | ]; 849 | var leaf = BobAccountLeaf; 850 | var BobAccountPath: string = "3"; 851 | var isBobValid = await MTutilsInstance.verifyLeaf( 852 | currentRoot, 853 | leaf, 854 | BobAccountPath, 855 | BobAccountSiblings 856 | ); 857 | 858 | var BobAccountMP = { 859 | accountIP: { 860 | pathToAccount: BobAccountPath, 861 | account: { 862 | ID: Bob.AccID, 863 | tokenType: Bob.TokenType, 864 | balance: Bob.Amount, 865 | nonce: Bob.nonce 866 | } 867 | }, 868 | siblings: BobAccountSiblings 869 | }; 870 | 871 | Bob.Amount += Number(tx.amount); 872 | var accountProofs = { 873 | from: AliceAccountMP, 874 | to: BobAccountMP 875 | }; 876 | 877 | // process transaction validity with process tx 878 | var result = await rollupCoreInstance.processTx( 879 | currentRoot, 880 | accountRoot, 881 | tx.signature, 882 | await utils.TxToBytes(tx), 883 | alicePDAProof, 884 | accountProofs 885 | ); 886 | 887 | var falseResult = await utils.falseProcessTx(tx, accountProofs); 888 | assert.equal(result[3], 1, "Wrong ErrorId"); 889 | var compressedTx = await utils.compressTx( 890 | tx.fromIndex, 891 | tx.toIndex, 892 | tx.nonce, 893 | tx.amount, 894 | tx.tokenType, 895 | tx.signature 896 | ); 897 | 898 | let compressedTxs: string[] = []; 899 | compressedTxs.push(compressedTx); 900 | console.log("compressedTx: " + JSON.stringify(compressedTxs)); 901 | 902 | // submit batch for that transactions 903 | await rollupCoreInstance.submitBatch( 904 | compressedTxs, 905 | falseResult, 906 | Usage.Transfer, 907 | { 908 | value: ethers.utils.parseEther("32").toString() 909 | } 910 | ); 911 | 912 | falseBatchComb = { 913 | batchId: 0, 914 | txs: [tx], 915 | batchProofs: { 916 | accountProofs: [accountProofs], 917 | pdaProof: [alicePDAProof] 918 | } 919 | }; 920 | 921 | let batchId = await rollupCoreInstance.numOfBatchesSubmitted(); 922 | falseBatchComb.batchId = Number(batchId) - 1; 923 | }); 924 | 925 | it("submit new batch 7th(false batch)", async function() { 926 | var AliceAccountLeaf = await utils.createLeaf(Alice); 927 | var BobAccountLeaf = await utils.createLeaf(Bob); 928 | 929 | // make a transfer between alice and bob's account 930 | var tranferAmount = 1; 931 | // prepare data for process Tx 932 | var currentRoot = await rollupCoreInstance.getLatestBalanceTreeRoot(); 933 | var accountRoot = await IMTInstance.getTreeRoot(); 934 | 935 | var isValid = await MTutilsInstance.verifyLeaf( 936 | accountRoot, 937 | utils.PubKeyHash(Alice.Pubkey), 938 | "2", 939 | AlicePDAsiblings 940 | ); 941 | assert.equal(isValid, true, "pda proof wrong"); 942 | 943 | var tx: Transaction = { 944 | fromIndex: Alice.AccID, 945 | toIndex: Bob.AccID, 946 | tokenType: Alice.TokenType, 947 | amount: 0, // An invalid amount 948 | txType: 1, 949 | nonce: 2 950 | }; 951 | tx.signature = await utils.signTx(tx, wallets[0]); 952 | 953 | // alice balance tree merkle proof 954 | var AliceAccountSiblings: Array = [ 955 | BobAccountLeaf, 956 | utils.getParentLeaf(coordinator_leaves[0], coordinator_leaves[1]), 957 | zeroHashes[2], 958 | zeroHashes[3] 959 | ]; 960 | var leaf = AliceAccountLeaf; 961 | var AliceAccountPath: string = "2"; 962 | var isValid = await MTutilsInstance.verifyLeaf( 963 | currentRoot, 964 | leaf, 965 | AliceAccountPath, 966 | AliceAccountSiblings 967 | ); 968 | expect(isValid).to.be.deep.eq(true); 969 | var AliceAccountMP = { 970 | accountIP: { 971 | pathToAccount: AliceAccountPath, 972 | account: { 973 | ID: Alice.AccID, 974 | tokenType: Alice.TokenType, 975 | balance: Alice.Amount, 976 | nonce: Alice.nonce 977 | } 978 | }, 979 | siblings: AliceAccountSiblings 980 | }; 981 | 982 | Alice.Amount -= Number(tx.amount); 983 | Alice.nonce++; 984 | 985 | var UpdatedAliceAccountLeaf = await utils.createLeaf(Alice); 986 | 987 | // bob balance tree merkle proof 988 | var BobAccountSiblings: Array = [ 989 | UpdatedAliceAccountLeaf, 990 | utils.getParentLeaf(coordinator_leaves[0], coordinator_leaves[1]), 991 | zeroHashes[2], 992 | zeroHashes[3] 993 | ]; 994 | var leaf = BobAccountLeaf; 995 | var BobAccountPath: string = "3"; 996 | var isBobValid = await MTutilsInstance.verifyLeaf( 997 | currentRoot, 998 | leaf, 999 | BobAccountPath, 1000 | BobAccountSiblings 1001 | ); 1002 | 1003 | var BobAccountMP = { 1004 | accountIP: { 1005 | pathToAccount: BobAccountPath, 1006 | account: { 1007 | ID: Bob.AccID, 1008 | tokenType: Bob.TokenType, 1009 | balance: Bob.Amount, 1010 | nonce: Bob.nonce 1011 | } 1012 | }, 1013 | siblings: BobAccountSiblings 1014 | }; 1015 | 1016 | Bob.Amount += Number(tx.amount); 1017 | var accountProofs = { 1018 | from: AliceAccountMP, 1019 | to: BobAccountMP 1020 | }; 1021 | 1022 | // process transaction validity with process tx 1023 | var result = await rollupCoreInstance.processTx( 1024 | currentRoot, 1025 | accountRoot, 1026 | tx.signature, 1027 | await utils.TxToBytes(tx), 1028 | alicePDAProof, 1029 | accountProofs 1030 | ); 1031 | 1032 | var falseResult = await utils.falseProcessTx(tx, accountProofs); 1033 | assert.equal( 1034 | result[3], 1035 | ErrorCode.InvalidTokenAmount, 1036 | "false ErrorId. it should be `2`" 1037 | ); 1038 | await utils.compressAndSubmitBatch(tx, falseResult); 1039 | 1040 | falseBatchComb.txs.push(tx); 1041 | falseBatchComb.batchProofs.accountProofs.push(accountProofs); 1042 | falseBatchComb.batchProofs.pdaProof.push(alicePDAProof); 1043 | }); 1044 | 1045 | it("dispute batch false Combo batch", async function() { 1046 | await rollupCoreInstance.disputeBatch( 1047 | falseBatchComb.batchId, 1048 | falseBatchComb.txs, 1049 | falseBatchComb.batchProofs 1050 | ); 1051 | 1052 | let batchId = await rollupCoreInstance.numOfBatchesSubmitted(); 1053 | let batchMarker = await rollupCoreInstance.invalidBatchMarker(); 1054 | assert.equal(batchMarker, "0", "batchMarker is not zero"); 1055 | assert.equal( 1056 | batchId - 1, 1057 | falseBatchComb.batchId - 1, 1058 | "batchId doesnt match" 1059 | ); 1060 | Alice.Amount += falseBatchComb.txs[0].amount; 1061 | Alice.Amount += falseBatchComb.txs[1].amount; 1062 | Bob.Amount -= falseBatchComb.txs[0].amount; 1063 | Bob.Amount -= falseBatchComb.txs[1].amount; 1064 | Alice.nonce--; 1065 | Alice.nonce--; 1066 | }); 1067 | }); 1068 | -------------------------------------------------------------------------------- /test/fixtures/Fill-Deposit-Subtree.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from "chai"; 2 | import * as walletHelper from "../../scripts/helpers/wallet"; 3 | const TestToken = artifacts.require("TestToken"); 4 | const chaiAsPromised = require("chai-as-promised"); 5 | const DepositManager = artifacts.require("DepositManager"); 6 | import * as utils from "../../scripts/helpers/utils"; 7 | import { ethers } from "ethers"; 8 | 9 | chai.use(chaiAsPromised); 10 | 11 | contract("DepositManager", async function(accounts) { 12 | var wallets: any; 13 | before(async function() { 14 | wallets = walletHelper.generateFirstWallets(walletHelper.mnemonics, 10); 15 | }); 16 | 17 | it("should register a token", async function() { 18 | let testToken = await TestToken.deployed(); 19 | let tokenRegistryInstance = await utils.getTokenRegistry(); 20 | let registerTokenReceipt = await tokenRegistryInstance.requestTokenRegistration( 21 | testToken.address, 22 | { from: wallets[0].getAddressString() } 23 | ); 24 | }); 25 | 26 | it("should finalise token registration", async () => { 27 | let testToken = await TestToken.deployed(); 28 | 29 | let tokenRegistryInstance = await utils.getTokenRegistry(); 30 | 31 | let approveToken = await tokenRegistryInstance.finaliseTokenRegistration( 32 | testToken.address, 33 | { from: wallets[0].getAddressString() } 34 | ); 35 | 36 | assert(approveToken, "token registration failed"); 37 | }); 38 | 39 | // ---------------------------------------------------------------------------------- 40 | it("should approve Rollup on TestToken", async () => { 41 | let testToken = await TestToken.deployed(); 42 | let depositManagerInstance = await DepositManager.deployed(); 43 | let approveToken = await testToken.approve( 44 | depositManagerInstance.address, 45 | ethers.utils.parseEther("1"), 46 | { 47 | from: wallets[0].getAddressString() 48 | } 49 | ); 50 | assert(approveToken, "approveToken failed"); 51 | }); 52 | 53 | it("should allow doing one deposit", async () => { 54 | let depositManagerInstance = await DepositManager.deployed(); 55 | var Alice = { 56 | Address: wallets[0].getAddressString(), 57 | Pubkey: wallets[0].getPublicKeyString(), 58 | Amount: 10, 59 | TokenType: 1, 60 | AccID: 1, 61 | Path: "2" 62 | }; 63 | var Bob = { 64 | Address: wallets[1].getAddressString(), 65 | Pubkey: wallets[1].getPublicKeyString(), 66 | Amount: 10, 67 | TokenType: 1, 68 | AccID: 2, 69 | Path: "3" 70 | }; 71 | await depositManagerInstance.deposit( 72 | Alice.Amount, 73 | Alice.TokenType, 74 | Alice.Pubkey 75 | ); 76 | await depositManagerInstance.depositFor( 77 | Bob.Address, 78 | Bob.Amount, 79 | Bob.TokenType, 80 | Bob.Pubkey 81 | ); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/fixtures/Single-Deposit.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from "chai"; 2 | import * as walletHelper from "../../scripts/helpers/wallet"; 3 | const TestToken = artifacts.require("TestToken"); 4 | const chaiAsPromised = require("chai-as-promised"); 5 | const DepositManager = artifacts.require("DepositManager"); 6 | const RollupCore = artifacts.require("Rollup"); 7 | import * as utils from "../../scripts/helpers/utils"; 8 | import { ethers } from "ethers"; 9 | 10 | chai.use(chaiAsPromised); 11 | 12 | contract("DepositManager", async function(accounts) { 13 | var wallets: any; 14 | before(async function() { 15 | wallets = walletHelper.generateFirstWallets(walletHelper.mnemonics, 10); 16 | }); 17 | 18 | it("should register a token", async function() { 19 | let testToken = await TestToken.deployed(); 20 | let tokenRegistryInstance = await utils.getTokenRegistry(); 21 | let registerTokenReceipt = await tokenRegistryInstance.requestTokenRegistration( 22 | testToken.address, 23 | { from: wallets[0].getAddressString() } 24 | ); 25 | }); 26 | 27 | it("should finalise token registration", async () => { 28 | let testToken = await TestToken.deployed(); 29 | 30 | let tokenRegistryInstance = await utils.getTokenRegistry(); 31 | 32 | let approveToken = await tokenRegistryInstance.finaliseTokenRegistration( 33 | testToken.address, 34 | { from: wallets[0].getAddressString() } 35 | ); 36 | 37 | assert(approveToken, "token registration failed"); 38 | }); 39 | 40 | // ---------------------------------------------------------------------------------- 41 | it("should approve Rollup on TestToken", async () => { 42 | let testToken = await TestToken.deployed(); 43 | let depositManagerInstance = await DepositManager.deployed(); 44 | let approveToken = await testToken.approve( 45 | depositManagerInstance.address, 46 | ethers.utils.parseEther("1"), 47 | { 48 | from: wallets[0].getAddressString() 49 | } 50 | ); 51 | assert(approveToken, "approveToken failed"); 52 | }); 53 | 54 | it("should allow doing one deposit", async () => { 55 | let depositManagerInstance = await DepositManager.deployed(); 56 | var Alice = { 57 | Address: wallets[0].getAddressString(), 58 | Pubkey: wallets[0].getPublicKeyString(), 59 | Amount: 10, 60 | TokenType: 1, 61 | AccID: 1, 62 | Path: "2" 63 | }; 64 | let result = await depositManagerInstance.deposit( 65 | Alice.Amount, 66 | Alice.TokenType, 67 | Alice.Pubkey 68 | ); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/merkalization/IncrementalTree.spec.ts: -------------------------------------------------------------------------------- 1 | const MTLib = artifacts.require("MerkleTreeUtils"); 2 | const IMT = artifacts.require("IncrementalTree"); 3 | const nameRegistry = artifacts.require("NameRegistry"); 4 | const ParamManager = artifacts.require("ParamManager"); 5 | import * as walletHelper from "../../scripts/helpers/wallet"; 6 | import * as utils from "../../scripts/helpers/utils"; 7 | const BN = require("bn.js"); 8 | 9 | contract("IncrementalTree", async function(accounts) { 10 | var wallets: any; 11 | var depth: number = 2; 12 | var firstDataBlock = utils.StringToBytes32("0x123"); 13 | var secondDataBlock = utils.StringToBytes32("0x334"); 14 | var thirdDataBlock = utils.StringToBytes32("0x4343"); 15 | var fourthDataBlock = utils.StringToBytes32("0x334"); 16 | var dataBlocks = [ 17 | firstDataBlock, 18 | secondDataBlock, 19 | thirdDataBlock, 20 | fourthDataBlock 21 | ]; 22 | var dataLeaves = [ 23 | utils.Hash(firstDataBlock), 24 | utils.Hash(secondDataBlock), 25 | utils.Hash(thirdDataBlock), 26 | utils.Hash(fourthDataBlock) 27 | ]; 28 | 29 | before(async function() { 30 | wallets = walletHelper.generateFirstWallets(walletHelper.mnemonics, 10); 31 | }); 32 | 33 | // test if we are able to create append a leaf 34 | it("create incremental MT and add 2 leaves", async function() { 35 | // get mtlibInstance 36 | var mtlibInstance = await utils.getMerkleTreeUtils(); 37 | 38 | // get IMT tree instance 39 | let IMTInstace = await IMT.deployed(); 40 | 41 | // get leaf to be inserted 42 | var leaf = dataLeaves[0]; 43 | var coordinator = 44 | "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"; 45 | var zeroLeaf = await mtlibInstance.getRoot(0); 46 | var zeroLeaf1 = await mtlibInstance.getRoot(1); 47 | var zeroLeaf2 = await mtlibInstance.getRoot(2); 48 | var zeroLeaf3 = await mtlibInstance.getRoot(3); 49 | 50 | // append leaf to the tree 51 | await IMTInstace.appendLeaf(leaf); 52 | 53 | // validate if the leaf was inserted correctly 54 | var root = await IMTInstace.getTreeRoot(); 55 | var path = "2"; 56 | var siblings = [coordinator, zeroLeaf1, zeroLeaf2, zeroLeaf3]; 57 | 58 | // call stateless merkle tree utils 59 | var isValid = await mtlibInstance.verifyLeaf( 60 | root, 61 | leaf, 62 | path, 63 | siblings 64 | ); 65 | expect(isValid).to.be.deep.eq(true); 66 | 67 | // add another leaf to the tree 68 | leaf = dataLeaves[1]; 69 | await IMTInstace.appendLeaf(leaf); 70 | var nextLeafIndex = await IMTInstace.nextLeafIndex(); 71 | // verify that the new leaf was inserted correctly 72 | var root1 = await IMTInstace.getTreeRoot(); 73 | 74 | var pathToSecondAccount = "3"; 75 | var siblings2 = [ 76 | dataLeaves[0], 77 | utils.getParentLeaf(coordinator, coordinator), 78 | zeroLeaf2, 79 | zeroLeaf3 80 | ]; 81 | isValid = await mtlibInstance.verifyLeaf( 82 | root1, 83 | leaf, 84 | pathToSecondAccount, 85 | siblings2 86 | ); 87 | expect(isValid).to.be.deep.eq(true); 88 | }); 89 | }); 90 | 91 | /** 92 | * Converts a big number to a hex string. 93 | * @param bn the big number to be converted. 94 | * @returns the big number as a string. 95 | */ 96 | export const bnToHexString = (bn: BN): string => { 97 | return "0x" + bn.toString("hex"); 98 | }; 99 | 100 | /** 101 | * Converts a buffer to a hex string. 102 | * @param buf the buffer to be converted. 103 | * @returns the buffer as a string. 104 | */ 105 | export const bufToHexString = (buf: Buffer): string => { 106 | return "0x" + buf.toString("hex"); 107 | }; 108 | -------------------------------------------------------------------------------- /test/merkalization/MerkleTree.spec.ts: -------------------------------------------------------------------------------- 1 | const MTLib = artifacts.require("MerkleTreeUtils"); 2 | const IMT = artifacts.require("IncrementalTree.sol"); 3 | const nameRegistry = artifacts.require("NameRegistry"); 4 | const ParamManager = artifacts.require("ParamManager"); 5 | const ECVerify = artifacts.require("ECVerify"); 6 | const RollupUtils = artifacts.require("RollupUtils"); 7 | const Types = artifacts.require("Types"); 8 | import * as walletHelper from "../../scripts/helpers/wallet"; 9 | import * as utils from "../../scripts/helpers/utils"; 10 | 11 | // Test all stateless operations 12 | contract("MerkleTreeUtils", async function(accounts) { 13 | var wallets: any; 14 | var firstDataBlock = utils.StringToBytes32("0x123"); 15 | var secondDataBlock = utils.StringToBytes32("0x334"); 16 | var thirdDataBlock = utils.StringToBytes32("0x4343"); 17 | var fourthDataBlock = utils.StringToBytes32("0x334"); 18 | var dataBlocks = [ 19 | firstDataBlock, 20 | secondDataBlock, 21 | thirdDataBlock, 22 | fourthDataBlock 23 | ]; 24 | var dataLeaves = [ 25 | utils.Hash(firstDataBlock), 26 | utils.Hash(secondDataBlock), 27 | utils.Hash(thirdDataBlock), 28 | utils.Hash(fourthDataBlock) 29 | ]; 30 | before(async function() { 31 | wallets = walletHelper.generateFirstWallets(walletHelper.mnemonics, 10); 32 | }); 33 | 34 | it("ensure root created on-chain and via utils is the same", async function() { 35 | var coordinator = 36 | "0x012893657d8eb2efad4de0a91bcd0e39ad9837745dec3ea923737ea803fc8e3d"; 37 | var maxSize = 4; 38 | var tempDataLeaves = []; 39 | tempDataLeaves[0] = coordinator; 40 | var numberOfDataLeaves = 2 ** maxSize; 41 | // create empty leaves 42 | for (var i = 1; i < numberOfDataLeaves; i++) { 43 | tempDataLeaves[i] = 44 | "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"; 45 | } 46 | var mtlibInstance = await utils.getMerkleTreeUtils(); 47 | var rootFromContract = await mtlibInstance.getMerkleRootFromLeaves( 48 | tempDataLeaves 49 | ); 50 | // root that we are creating for on-chain data 51 | var rootWhileDeployingContracts = 52 | "0xfe55b4be5e50828667fb2dc97c9757fd0c295b19c6de05f4d8800ed128f66ad4"; 53 | var generatedRoot = await utils.getMerkleRoot(tempDataLeaves, maxSize); 54 | var rootFromContract = await mtlibInstance.getMerkleRootFromLeaves( 55 | tempDataLeaves 56 | ); 57 | assert.equal( 58 | rootFromContract, 59 | generatedRoot, 60 | "root generated off chain and on-chains should match" 61 | ); 62 | }); 63 | 64 | it("utils hash should be the same as keccak hash", async function() { 65 | var data = utils.StringToBytes32("0x123"); 66 | 67 | var mtlibInstance = await utils.getMerkleTreeUtils(); 68 | var hash = utils.Hash(data); 69 | 70 | var keccakHash = await mtlibInstance.keecakHash(data); 71 | expect(keccakHash).to.be.deep.eq(hash); 72 | }); 73 | 74 | it("test get parent", async function() { 75 | var mtlibInstance = await utils.getMerkleTreeUtils(); 76 | 77 | let localHash = utils.getParentLeaf(firstDataBlock, secondDataBlock); 78 | let contractHash = await mtlibInstance.getParent( 79 | firstDataBlock, 80 | secondDataBlock 81 | ); 82 | 83 | expect(localHash).to.be.deep.eq(contractHash); 84 | }); 85 | 86 | it("test index to path", async function() { 87 | var mtlibInstance = await utils.getMerkleTreeUtils(); 88 | 89 | var result = await mtlibInstance.pathToIndex("10", 2); 90 | expect(result.toNumber()).to.be.deep.eq(2); 91 | 92 | var result2 = await mtlibInstance.pathToIndex("11", 2); 93 | expect(result2.toNumber()).to.be.deep.eq(3); 94 | 95 | var result3 = await mtlibInstance.pathToIndex("111", 3); 96 | expect(result3.toNumber()).to.be.deep.eq(7); 97 | 98 | // var result4 = await mtlibInstance.pathToIndex( 99 | // "11111111111111111111111", 100 | // "11111111111111111111111".length 101 | // ); 102 | // expect(result4.toNumber()).to.be.deep.eq(8388607); 103 | }); 104 | 105 | it("[LEAF] [STATELESS] verifying correct proof", async function() { 106 | var mtlibInstance = await utils.getMerkleTreeUtils(); 107 | 108 | var root = await mtlibInstance.getMerkleRoot(dataBlocks); 109 | 110 | var siblings: Array = [ 111 | dataLeaves[1], 112 | utils.getParentLeaf(dataLeaves[2], dataLeaves[3]) 113 | ]; 114 | 115 | var leaf = dataLeaves[0]; 116 | 117 | var path: string = "00"; 118 | 119 | var isValid = await mtlibInstance.verifyLeaf( 120 | root, 121 | leaf, 122 | path, 123 | siblings 124 | ); 125 | 126 | expect(isValid).to.be.deep.eq(true); 127 | }); 128 | 129 | it("[DATABLOCK] [STATELESS] verifying correct proof", async function() { 130 | var mtlibInstance = await utils.getMerkleTreeUtils(); 131 | 132 | var root = await mtlibInstance.getMerkleRoot(dataBlocks); 133 | 134 | var siblings: Array = [ 135 | dataLeaves[1], 136 | utils.getParentLeaf(dataLeaves[2], dataLeaves[3]) 137 | ]; 138 | 139 | var leaf = dataBlocks[0]; 140 | 141 | var path: string = "00"; 142 | 143 | var isValid = await mtlibInstance.verify(root, leaf, path, siblings); 144 | 145 | expect(isValid).to.be.deep.eq(true); 146 | }); 147 | 148 | it("[LEAF] [STATELESS] verifying proof with wrong path", async function() { 149 | var mtlibInstance = await utils.getMerkleTreeUtils(); 150 | // create merkle tree and get root 151 | var root = await mtlibInstance.getMerkleRoot(dataBlocks); 152 | var siblings: Array = [ 153 | dataLeaves[1], 154 | utils.getParentLeaf(dataLeaves[2], dataLeaves[3]) 155 | ]; 156 | var leaf = dataLeaves[0]; 157 | var path: string = "01"; 158 | var isValid = await mtlibInstance.verifyLeaf( 159 | root, 160 | leaf, 161 | path, 162 | siblings 163 | ); 164 | expect(isValid).to.be.deep.eq(false); 165 | }); 166 | 167 | it("[DATABLOCK] [STATELESS] verifying proof with wrong path", async function() { 168 | var mtlibInstance = await utils.getMerkleTreeUtils(); 169 | 170 | // create merkle tree and get root 171 | var root = await mtlibInstance.getMerkleRoot(dataBlocks); 172 | 173 | var siblings: Array = [ 174 | dataLeaves[1], 175 | utils.getParentLeaf(dataLeaves[2], dataLeaves[3]) 176 | ]; 177 | var leaf = dataLeaves[0]; 178 | var path: string = "01"; 179 | var isValid = await mtlibInstance.verifyLeaf( 180 | root, 181 | leaf, 182 | path, 183 | siblings 184 | ); 185 | expect(isValid).to.be.deep.eq(false); 186 | }); 187 | 188 | it("[LEAF] [STATELESS] verifying other leaves", async function() { 189 | var mtlibInstance = await utils.getMerkleTreeUtils(); 190 | 191 | var root = await mtlibInstance.getMerkleRoot(dataBlocks); 192 | 193 | var siblings: Array = [ 194 | dataLeaves[0], 195 | utils.getParentLeaf(dataLeaves[2], dataLeaves[3]) 196 | ]; 197 | var leaf = dataLeaves[1]; 198 | var path: string = "01"; 199 | var isValid = await mtlibInstance.verifyLeaf( 200 | root, 201 | leaf, 202 | path, 203 | siblings 204 | ); 205 | expect(isValid).to.be.deep.eq(true); 206 | }); 207 | 208 | it("[DATABLOCK] [STATELESS] verifying other leaves", async function() { 209 | var mtlibInstance = await utils.getMerkleTreeUtils(); 210 | 211 | var root = await mtlibInstance.getMerkleRoot(dataBlocks); 212 | 213 | var siblings: Array = [ 214 | dataLeaves[0], 215 | utils.getParentLeaf(dataLeaves[2], dataLeaves[3]) 216 | ]; 217 | var leaf = dataLeaves[1]; 218 | var path: string = "01"; 219 | var isValid = await mtlibInstance.verifyLeaf( 220 | root, 221 | leaf, 222 | path, 223 | siblings 224 | ); 225 | expect(isValid).to.be.deep.eq(true); 226 | }); 227 | 228 | it("[DATABLOCK] [STATELESS] path greater than depth", async function() { 229 | var mtlibInstance = await utils.getMerkleTreeUtils(); 230 | 231 | var root = await mtlibInstance.getMerkleRoot(dataBlocks); 232 | 233 | var siblings: Array = [ 234 | dataLeaves[0], 235 | utils.getParentLeaf(dataLeaves[2], dataLeaves[3]) 236 | ]; 237 | var leaf = dataLeaves[1]; 238 | var path: string = "010"; 239 | var isValid = await mtlibInstance.verifyLeaf( 240 | root, 241 | leaf, 242 | path, 243 | siblings 244 | ); 245 | 246 | // TODO fix 247 | expect(isValid).to.be.deep.eq(false); 248 | }); 249 | }); 250 | -------------------------------------------------------------------------------- /test/tx.ts: -------------------------------------------------------------------------------- 1 | // import * as mcl from "./mcl"; 2 | 3 | const amountLen = 4; 4 | const senderLen = 4; 5 | const receiverLen = 4; 6 | const sigLen = 64; 7 | 8 | export function serialize(txs: Tx[]) { 9 | return "0x" + txs.map(tx => tx.encode()).join(""); 10 | } 11 | 12 | export class Tx { 13 | public static rand(): Tx { 14 | const sender = web3.utils.hexToNumber(web3.utils.randomHex(senderLen)); 15 | const receiver = web3.utils.hexToNumber( 16 | web3.utils.randomHex(receiverLen) 17 | ); 18 | const amount = web3.utils.hexToNumber(web3.utils.randomHex(amountLen)); 19 | const signature = web3.utils.randomHex(sigLen); 20 | return new Tx(sender, receiver, amount, signature); 21 | } 22 | constructor( 23 | readonly sender: number, 24 | readonly receiver: number, 25 | readonly amount: number, 26 | readonly signature: string 27 | ) {} 28 | 29 | public hash(): string { 30 | return web3.utils.soliditySha3( 31 | { v: this.sender, t: "uint32" }, 32 | { v: this.receiver, t: "uint32" }, 33 | { v: this.amount, t: "uint32" } 34 | ); 35 | } 36 | 37 | // public mapToPoint() { 38 | // const e = this.hash(); 39 | // return mcl.g1ToHex(mcl.mapToPoint(e)); 40 | // } 41 | 42 | public encode(): string { 43 | let sender = web3.utils.padLeft( 44 | web3.utils.toHex(this.sender), 45 | senderLen * 2 46 | ); 47 | let receiver = web3.utils.padLeft( 48 | web3.utils.toHex(this.receiver), 49 | receiverLen * 2 50 | ); 51 | let amount = web3.utils.padLeft( 52 | web3.utils.toHex(this.amount), 53 | amountLen * 2 54 | ); 55 | return ( 56 | sender.slice(2) + 57 | receiver.slice(2) + 58 | amount.slice(2) + 59 | this.signature.slice(2) 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/utils/rollup-utils.spec.ts: -------------------------------------------------------------------------------- 1 | import * as utils from "../../scripts/helpers/utils"; 2 | import * as walletHelper from "../..//scripts/helpers/wallet"; 3 | import { assert } from "chai"; 4 | 5 | const RollupUtils = artifacts.require("RollupUtils"); 6 | 7 | contract("RollupUtils", async function(accounts) { 8 | var wallets: any; 9 | before(async function() { 10 | wallets = walletHelper.generateFirstWallets(walletHelper.mnemonics, 10); 11 | }); 12 | 13 | // test if we are able to create append a leaf 14 | /* it("test if account hash is correctly generated", async function () { */ 15 | /* var Alice = { */ 16 | /* Address: wallets[0].getAddressString(), */ 17 | /* Pubkey: wallets[0].getPublicKeyString(), */ 18 | /* Amount: 10, */ 19 | /* TokenType: 1, */ 20 | /* AccID: 1, */ 21 | /* Path: "0000", */ 22 | /* Nonce: 0, */ 23 | /* }; */ 24 | 25 | /* var AliceAccountLeaf = utils.CreateAccountLeaf( */ 26 | /* Alice.AccID, */ 27 | /* Alice.Amount, */ 28 | /* Alice.Nonce, */ 29 | /* Alice.TokenType */ 30 | /* ); */ 31 | /* var rollupUtils = await RollupUtils.deployed(); */ 32 | /* var data = { */ 33 | /* ID: Alice.AccID, */ 34 | /* tokenType: Alice.TokenType, */ 35 | /* balance: Alice.Amount, */ 36 | /* nonce: Alice.Nonce, */ 37 | /* }; */ 38 | /* var accountHash = await rollupUtils.getAccountHash( */ 39 | /* data.ID, */ 40 | /* data.balance, */ 41 | /* data.nonce, */ 42 | /* data.tokenType */ 43 | /* ); */ 44 | /* assert.equal(AliceAccountLeaf, accountHash, "Account hash mismatch"); */ 45 | /* }); */ 46 | // it("test if tx is correctly encoded to bytes and hash", async function () { 47 | // var rollupUtils = await RollupUtils.deployed(); 48 | // var tx = { 49 | // fromIndex: 1, 50 | // toIndex: 2, 51 | // tokenType: 1, 52 | // amount: 1, 53 | // signature: 54 | // "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563", 55 | // }; 56 | 57 | // var expectedResult = utils.HashFromTx( 58 | // tx.fromIndex, 59 | // tx.toIndex, 60 | // tx.tokenType, 61 | // tx.amount 62 | // ); 63 | 64 | // var result = await rollupUtils.getTxHash( 65 | // tx.fromIndex, 66 | // tx.toIndex, 67 | // tx.tokenType, 68 | // tx.amount 69 | // ); 70 | // assert.equal(expectedResult, result, "Account hash mismatch"); 71 | // }); 72 | 73 | it("test account encoding and decoding", async function() { 74 | var rollupUtils = await RollupUtils.deployed(); 75 | var account = { 76 | ID: 1, 77 | tokenType: 2, 78 | balance: 3, 79 | nonce: 4 80 | }; 81 | 82 | var accountBytes = await rollupUtils.BytesFromAccountDeconstructed( 83 | account.ID, 84 | account.balance, 85 | account.nonce, 86 | account.tokenType 87 | ); 88 | var regeneratedAccount = await rollupUtils.AccountFromBytes( 89 | accountBytes 90 | ); 91 | assert.equal(regeneratedAccount["0"].toNumber(), account.ID); 92 | assert.equal(regeneratedAccount["1"].toNumber(), account.balance); 93 | assert.equal(regeneratedAccount["2"].toNumber(), account.nonce); 94 | assert.equal(regeneratedAccount["3"].toNumber(), account.tokenType); 95 | 96 | var tx = { 97 | fromIndex: 1, 98 | toIndex: 2, 99 | tokenType: 1, 100 | amount: 10, 101 | signature: 102 | "0x1ad4773ace8ee65b8f1d94a3ca7adba51ee2ca0bdb550907715b3b65f1e3ad9f69e610383dc9ceb8a50c882da4b1b98b96500bdf308c1bdce2187cb23b7d736f1b", 103 | txType: 1, 104 | nonce: 0 105 | }; 106 | 107 | var txBytes = await rollupUtils.BytesFromTxDeconstructed( 108 | tx.fromIndex, 109 | tx.toIndex, 110 | tx.tokenType, 111 | tx.nonce, 112 | tx.txType, 113 | tx.amount 114 | ); 115 | 116 | var txData = await rollupUtils.TxFromBytes(txBytes); 117 | assert.equal(txData.fromIndex.toString(), tx.fromIndex.toString()); 118 | assert.equal(txData.toIndex.toString(), tx.toIndex.toString()); 119 | assert.equal(txData.tokenType.toString(), tx.tokenType.toString()); 120 | assert.equal(txData.nonce.toString(), tx.nonce.toString()); 121 | assert.equal(txData.txType.toString(), tx.txType.toString()); 122 | assert.equal(txData.amount.toString(), tx.amount.toString()); 123 | 124 | var compressedTx = await rollupUtils.CompressTxWithMessage( 125 | txBytes, 126 | tx.signature 127 | ); 128 | 129 | var decompressedTx = await rollupUtils.DecompressTx(compressedTx); 130 | assert.equal(decompressedTx[0].toNumber(), tx.fromIndex); 131 | assert.equal(decompressedTx[1].toNumber(), tx.toIndex); 132 | assert.equal(decompressedTx[2].toNumber(), tx.amount); 133 | assert.equal(decompressedTx[3].toString(), tx.signature); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | require("ts-node/register"); 2 | 3 | const HDWalletProvider = require("truffle-hdwallet-provider"); 4 | 5 | const MNEMONIC = 6 | process.env.MNEMONIC || 7 | "clock radar mass judge dismiss just intact mind resemble fringe diary casino"; 8 | const API_KEY = process.env.API_KEY; 9 | 10 | module.exports = { 11 | test_file_extension_regexp: /.*\.ts$/, 12 | networks: { 13 | development: { 14 | host: "127.0.0.1", // Localhost (default: none) 15 | port: 8545, // Standard Ethereum port (default: none) 16 | network_id: "*", // Any network (default: none), 17 | gas: 8000000, 18 | }, 19 | ropsten: { 20 | provider: function () { 21 | return new HDWalletProvider( 22 | MNEMONIC, 23 | `https://ropsten.infura.io/v3/${API_KEY}` 24 | ); 25 | }, 26 | network_id: "*", 27 | gas: 8000000, 28 | }, 29 | }, 30 | 31 | // Set default mocha options here, use special reporters etc. 32 | mocha: { 33 | reporter: "eth-gas-reporter", 34 | useColors: true, 35 | }, 36 | 37 | // Configure your compilers 38 | compilers: { 39 | solc: { 40 | version: "0.5.15", // Fetch exact version from solc-bin (default: truffle's version) 41 | docker: false, // Use "0.5.1" you've installed locally with docker (default: false) 42 | settings: { 43 | // See the solidity docs for advice about optimization and evmVersion 44 | optimizer: { 45 | enabled: false, 46 | runs: 200, 47 | }, 48 | evmVersion: "byzantium", 49 | }, 50 | }, 51 | }, 52 | plugins: ['solidity-coverage'], 53 | }; 54 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "strict": true, 6 | "moduleResolution": "node", 7 | "noImplicitThis": true, 8 | "alwaysStrict": true, 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "lib": ["es2015", "esnext.asynciterable"], 13 | "sourceMap": true, 14 | "typeRoots": ["./node_modules/@types", "./types"], 15 | "types": ["node", "truffle-contracts"] 16 | }, 17 | "include": ["**/*.ts"], 18 | "exclude": ["node_modules", "example", "build"] 19 | } 20 | --------------------------------------------------------------------------------