import '../setup' ; import { createMockProvider , deployContract , getWallets } from 'ethereum-waffle' ; import { SparseMerkleTreeImpl , newInMemoryDB } from '@pigi/core-db' ; import { hexStrToBuf , bufToHexString , BigNumber , DefaultSignatureProvider , } from '@pigi/core-utils' ; import { SwapTransition , TransferTransition , CreateAndTransferTransition , Transfer , abiEncodeTransition , abiEncodeTransaction , State , abiEncodeState , DefaultRollupBlock , } from '../../src' ; import { generateNTransitions , ZERO_BYTES32 , getAddress , getSignature , getStateRoot , assertThrowsAsync , } from '../helpers' ; import debug from 'debug' ; const log = debug ( 'test:info:rollup-chain-manager' ) ; import * as RollupChain from '../../build/contracts/RollupChain.json' ; import * as UnipigTransitionEvaluator from '../../build/contracts/UnipigTransitionEvaluator.json' ; import * as RollupMerkleUtils from '../../build/contracts/RollupMerkleUtils.json' ; import { Contract } from 'ethers' ; describe ( 'RollupChain' , ( ) => { const provider = createMockProvider ( ) ; const [ wallet1 , wallet2 ] = getWallets ( provider ) ; let rollupChain ; let rollupMerkleUtils ; let unipigEvaluator ; let rollupCtLogFilter ; before ( async ( ) => { unipigEvaluator = await deployContract ( wallet1 , UnipigTransitionEvaluator , [ ] , { gasLimit : 6700000 , } ) ; rollupMerkleUtils = await deployContract ( wallet1 , RollupMerkleUtils , [ ] , { gasLimit : 6700000 , } ) ; } ) ; beforeEach ( async ( ) => { rollupChain = await deployContract ( wallet1 , RollupChain , [ unipigEvaluator . address , rollupMerkleUtils . address , wallet1 . address ] , { gasLimit : 6700000 , } ) ; rollupCtLogFilter = { address : rollupChain . address , fromBlock : 0 , toBlock : 'latest' , } ; } ) ; describe ( 'submitBlock() ' , async ( ) => { it ( 'should not throw as long as it gets a bytes array (even if its invalid)' , async ( ) => { await rollupChain . submitBlock ( [ '0x1234' , '0x1234' ] ) ; } ) ; it ( 'should prevent non-aggregator from submitting block' , async ( ) => { const rollupChainWallet2 = new Contract ( rollupChain . address , rollupChain . interface , wallet2 as any ) ; await assertThrowsAsync ( async ( ) => { await rollupChainWallet2 . submitBlock ( [ '0x1234' , '0x1234' ] ) ; } ) ; } ) ; it ( 'should process blocks many transitions' , async ( ) => { const numTransitions = 50 ; await rollupChain . submitBlock ( [ '0x1234' , '0x1234' ] ) ; const createAndTransfer = { stateRoot : getStateRoot ( 'ab' ) , senderSlotIndex : 2 , recipientSlotIndex : 2 , createdAccountPubkey : getAddress ( '11' ) , tokenType : 0 , amount : 1 , signature : getSignature ( '0ad1' ) , } ; const transfer = { stateRoot : getStateRoot ( 'ab' ) , senderSlotIndex : 2 , recipientSlotIndex : 2 , tokenType : 0 , amount : 1 , signature : getSignature ( 'ab10' ) , } ; const swap = { stateRoot : getStateRoot ( 'cd' ) , senderSlotIndex : 2 , uniswapSlotIndex : 0 , tokenType : 1 , inputAmount : 1 , minOutputAmount : 3 , timeout : 6 , signature : getSignature ( '1301' ) , } ; const encoded = [ abiEncodeTransition ( createAndTransfer ) , abiEncodeTransition ( transfer ) , abiEncodeTransition ( swap ) , ] ; const fullCallData = [ ] ; for ( let i = 0 ; i < numTransitions ; i ++ ) { fullCallData . push ( encoded [ Math . floor ( Math . random ( ) * encoded . length ) ] ) ; } const result = await rollupChain . submitBlock ( fullCallData ) ; } ) . timeout ( 3000 ) ; } ) ; describe ( 'verifySequentialTransitions()' , async ( ) => { let blocks ; beforeEach ( async ( ) => { blocks = [ new DefaultRollupBlock ( generateNTransitions ( 10 ) , 0 ) , new DefaultRollupBlock ( generateNTransitions ( 10 ) , 1 ) , ] ; for ( const block of blocks ) { await block . generateTree ( ) ; } await rollupChain . submitBlock ( blocks [ 0 ] . encodedTransitions ) ; await rollupChain . submitBlock ( blocks [ 1 ] . encodedTransitions ) ; } ) ; describe ( 'same block' , async ( ) => { it ( 'should NOT throw if the transitions are sequential in the same block' , async ( ) => { const includedTransitions = [ await blocks [ 0 ] . getIncludedTransition ( 0 ) , await blocks [ 0 ] . getIncludedTransition ( 1 ) , ] ; await rollupChain . verifySequentialTransitions ( includedTransitions [ 0 ] , includedTransitions [ 1 ] ) ; } ) ; it ( 'should throw if they are not sequential in the same block' , async ( ) => { const includedTransitions = [ await blocks [ 0 ] . getIncludedTransition ( 0 ) , await blocks [ 0 ] . getIncludedTransition ( 2 ) , ] ; try { await rollupChain . verifySequentialTransitions ( includedTransitions [ 0 ] , includedTransitions [ 1 ] ) ; } catch ( err ) { return ; } throw new Error ( 'Verify sequential should throw when not sequential!' ) ; } ) ; } ) ; describe ( 'different blocks' , async ( ) => { it ( 'should NOT throw if the transitions are last of prev block & first of next block' , async ( ) => { const includedTransitions = [ await blocks [ 0 ] . getIncludedTransition ( blocks [ 0 ] . transitions . length - 1 ) , await blocks [ 1 ] . getIncludedTransition ( 0 ) , ] ; await rollupChain . verifySequentialTransitions ( includedTransitions [ 0 ] , includedTransitions [ 1 ] ) ; } ) ; it ( 'should throw if the transitions are NOT last of prev block & first of next block' , async ( ) => { const includedTransitions = [ await blocks [ 0 ] . getIncludedTransition ( 0 ) , await blocks [ 1 ] . getIncludedTransition ( 0 ) , ] ; try { await rollupChain . verifySequentialTransitions ( includedTransitions [ 0 ] , includedTransitions [ 1 ] ) ; } catch ( err ) { return ; } throw new Error ( 'Verify sequential should throw when not sequential!' ) ; } ) ; } ) ; } ) ; describe ( 'checkTransitionIncluded()' , async ( ) => { it ( 'should verify n included transitions for the first block' , async ( ) => { const block = new DefaultRollupBlock ( generateNTransitions ( 10 ) , 0 ) ; await block . generateTree ( ) ; await rollupChain . submitBlock ( block . encodedTransitions ) ; for ( let i = 0 ; i < block . transitions . length ; i ++ ) { const inclusionProof = await block . getInclusionProof ( i ) ; const isIncluded = await rollupChain . checkTransitionInclusion ( { transition : block . encodedTransitions [ i ] , inclusionProof , } ) ; isIncluded . should . equal ( true ) ; } } ) ; it ( 'should verify n included transitions for the second block' , async ( ) => { const block0 = new DefaultRollupBlock ( generateNTransitions ( 5 ) , 0 ) ; const block1 = new DefaultRollupBlock ( generateNTransitions ( 5 ) , 1 ) ; await block0 . generateTree ( ) ; await block1 . generateTree ( ) ; await rollupChain . submitBlock ( block0 . encodedTransitions ) ; await rollupChain . submitBlock ( block1 . encodedTransitions ) ; for ( let i = 0 ; i < block1 . transitions . length ; i ++ ) { const inclusionProof = await block1 . getInclusionProof ( i ) ; const isIncluded = await rollupChain . checkTransitionInclusion ( { transition : block1 . encodedTransitions [ i ] , inclusionProof , } ) ; isIncluded . should . equal ( true ) ; } } ) ; it ( 'should fail to verify inclusion for a transition which is not included' , async ( ) => { const block0 = new DefaultRollupBlock ( generateNTransitions ( 5 ) , 0 ) ; await block0 . generateTree ( ) ; await rollupChain . submitBlock ( block0 . encodedTransitions ) ; const notIncluded = '0xdeadbeefdeadbeefdeadbeef' ; const res = await rollupChain . checkTransitionInclusion ( { transition : notIncluded , inclusionProof : { blockNumber : 0 , transitionIndex : 0 , path : 0 , siblings : [ ZERO_BYTES32 ] , } , } ) ; res . should . equal ( false ) ; } ) ; } ) ; describe ( 'getStateRootsAndStorageSlots()' , async ( ) => { it ( 'should not throw' , async ( ) => { const expectedSlots = [ 5 , 10 ] ; const transferTransitions = [ { stateRoot : getStateRoot ( 'ab' ) , senderSlotIndex : expectedSlots [ 0 ] , recipientSlotIndex : expectedSlots [ 1 ] , tokenType : 0 , amount : 1 , signature : getSignature ( '42' ) , } , { stateRoot : getStateRoot ( 'ab' ) , senderSlotIndex : expectedSlots [ 0 ] , recipientSlotIndex : expectedSlots [ 1 ] , tokenType : 0 , amount : 1 , signature : getSignature ( '42' ) , } , ] ; const transferTransitionsEncoded = transferTransitions . map ( ( transition ) => abiEncodeTransition ( transition ) ) ; const block = new DefaultRollupBlock ( transferTransitions , 0 ) ; await block . generateTree ( ) ; const includedTransitions = [ await block . getIncludedTransition ( 0 ) , await block . getIncludedTransition ( 1 ) , ] ; const res = await rollupChain . getStateRootsAndStorageSlots ( transferTransitionsEncoded [ 0 ] , transferTransitionsEncoded [ 1 ] ) ; } ) ; } ) ; describe ( 'proveTransitionInvalid() ' , async ( ) => { it ( 'should throw if attempting to prove invalid a valid transition' , async ( ) => { const signatureProvider = new DefaultSignatureProvider ( ) ; const sentAmount = 5 ; const tokenType = 0 ; const storageSlots = [ 5 , 10 ] ; const pubkeys = [ await signatureProvider . getAddress ( ) , getAddress ( '22' ) ] ; const balances = [ { '0' : 10 , '1' : 20 } , { '0' : 100 , '1' : 200 } ] ; const postBalances = [ { '0' : 10 - sentAmount , '1' : 20 } , { '0' : 100 + sentAmount , '1' : 200 } , ] ; const stateBalancesToContractBalances = ( bal ) => [ bal [ '0' ] , bal [ '1' ] ] ; const preStateObjects = [ { pubkey : pubkeys [ 0 ] , balances : balances [ 0 ] , } , { pubkey : pubkeys [ 1 ] , balances : balances [ 1 ] , } , ] ; const encodedPreStates = preStateObjects . map ( ( obj ) => abiEncodeState ( obj ) ) ; const treeHeight = 32 ; const stateTree = await SparseMerkleTreeImpl . create ( newInMemoryDB ( ) , undefined , treeHeight + 1 ) ; for ( let i = 0 ; i < preStateObjects . length ; i ++ ) { await stateTree . update ( new BigNumber ( storageSlots [ i ] ) , hexStrToBuf ( encodedPreStates [ i ] ) ) ; } const preStateRoot = bufToHexString ( await stateTree . getRootHash ( ) ) ; const siblings = [ ] ; for ( let i = 0 ; i < preStateObjects . length ; i ++ ) { const inclusionProof = await stateTree . getMerkleProof ( new BigNumber ( storageSlots [ i ] ) , hexStrToBuf ( encodedPreStates [ i ] ) ) ; siblings . push ( inclusionProof . siblings . map ( ( sibBuf ) => bufToHexString ( sibBuf ) ) ) ; } const postStateObjects = preStateObjects . map ( ( obj , index ) => { return { pubkey : obj . pubkey , balances : postBalances [ index ] } ; } ) ; for ( let i = 0 ; i < preStateObjects . length ; i ++ ) { await stateTree . update ( new BigNumber ( storageSlots [ i ] ) , hexStrToBuf ( abiEncodeState ( postStateObjects [ i ] ) ) ) ; } const postStateRoot = bufToHexString ( await stateTree . getRootHash ( ) ) ; const transactionForSecondTransfer = { sender : pubkeys [ 0 ] , recipient : pubkeys [ 1 ] , tokenType , amount : sentAmount , } ; const signature = await signatureProvider . sign ( abiEncodeTransaction ( transactionForSecondTransfer ) ) ; const transferTransitions = [ { stateRoot : preStateRoot , senderSlotIndex : storageSlots [ 0 ] , recipientSlotIndex : storageSlots [ 1 ] , tokenType , amount : 1 , signature : getSignature ( '42' ) , } , { stateRoot : postStateRoot , senderSlotIndex : storageSlots [ 0 ] , recipientSlotIndex : storageSlots [ 1 ] , tokenType , amount : sentAmount , signature , } , ] ; const transferTransitionsEncoded = transferTransitions . map ( ( transition ) => abiEncodeTransition ( transition ) ) ; const block = new DefaultRollupBlock ( transferTransitions , 0 ) ; await block . generateTree ( ) ; await rollupChain . submitBlock ( block . encodedTransitions ) ; const includedTransitions = [ await block . getIncludedTransition ( 0 ) , await block . getIncludedTransition ( 1 ) , ] ; const includedStorageSlots = [ { storageSlot : { value : { pubkey : pubkeys [ 0 ] , balances : [ balances [ 0 ] [ '0' ] , balances [ 0 ] [ '1' ] ] , } , slotIndex : storageSlots [ 0 ] , } , siblings : siblings [ 0 ] , } , { storageSlot : { value : { pubkey : pubkeys [ 1 ] , balances : [ balances [ 1 ] [ '0' ] , balances [ 1 ] [ '1' ] ] , } , slotIndex : storageSlots [ 1 ] , } , siblings : siblings [ 1 ] , } , ] ; try { await rollupChain . proveTransitionInvalid ( includedTransitions [ 0 ] , includedTransitions [ 1 ] , includedStorageSlots ) ; } catch ( err ) { return ; } throw new Error ( 'Expected no fraud to be detected & therefore an error to be thrown!' ) ; } ) . timeout ( 8000 ) ; } ) ; } ) ;