import '../setup' ; import * as assert from 'assert' ; import { DB , newInMemoryDB } from '@pigi/core-db' ; import { bufToHexString , ChecksumAgnosticIdentityVerifier , NULL_ADDRESS , } from '@pigi/core-utils' ; import { AGGREGATOR_ADDRESS , ALICE_ADDRESS , ALICE_GENESIS_STATE_INDEX , BOB_ADDRESS , UNISWAP_GENESIS_STATE_INDEX , } from '../helpers' ; import { UNI_TOKEN_TYPE , UNISWAP_ADDRESS , PIGI_TOKEN_TYPE , DefaultRollupStateValidator , RollupStateValidator , LocalFraudProof , CreateAndTransferTransition , StateSnapshot , TransferTransition , State , SwapTransition , RollupBlock , ValidationOutOfOrderError , AggregatorUnsupportedError , ContractFraudProof , DefaultRollupStateMachine , } from '../../src' ; /***********
 * HELPERS *
 ***********/ const BOB_GENESIS_STATE_INDEX = 3 ; function getMultiBalanceGenesis ( aliceAddress = ALICE_ADDRESS , bobAddress = BOB_ADDRESS ) { return [ { pubkey : UNISWAP_ADDRESS , balances : { [ UNI_TOKEN_TYPE ] : 650000 , [ PIGI_TOKEN_TYPE ] : 650000 , } , } , { pubkey : aliceAddress , balances : { [ UNI_TOKEN_TYPE ] : 5000 , [ PIGI_TOKEN_TYPE ] : 5000 , } , } , { pubkey : AGGREGATOR_ADDRESS , balances : { [ UNI_TOKEN_TYPE ] : 1000000 , [ PIGI_TOKEN_TYPE ] : 1000000 , } , } , { pubkey : bobAddress , balances : { [ UNI_TOKEN_TYPE ] : 5000 , [ PIGI_TOKEN_TYPE ] : 5000 , } , } , ] ; } /*********
 * TESTS *
 *********/ describe ( 'RollupStateValidator' , ( ) => { let rollupGuard ; let stateDb ; beforeEach ( async ( ) => { stateDb = newInMemoryDB ( ) ; const rollupStateMachine = ( await DefaultRollupStateMachine . create ( getMultiBalanceGenesis ( ) , stateDb , AGGREGATOR_ADDRESS , ChecksumAgnosticIdentityVerifier . instance ( ) ) ) as DefaultRollupStateMachine ; rollupGuard = new DefaultRollupStateValidator ( rollupStateMachine ) ; } ) ; afterEach ( async ( ) => { await stateDb . close ( ) ; } ) ; describe ( 'initialization' , ( ) => { it ( 'should create Guarder with a rollup machine' , async ( ) => { rollupGuard . rollupMachine . should . not . be . undefined ; } ) ; } ) ; describe ( 'getInputStateSnapshots' , ( ) => { it ( 'should get right inclusion proof for a swap' , async ( ) => { const genesisStateRootBuf = await rollupGuard . rollupMachine . getStateRoot ( ) ; const genesisStateRoot = bufToHexString ( genesisStateRootBuf ) ; const swapTransition = { stateRoot : 'DOESNT_MATTER' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , uniswapSlotIndex : UNISWAP_GENESIS_STATE_INDEX , tokenType : UNI_TOKEN_TYPE , inputAmount : 100 , minOutputAmount : 20 , timeout : 10 , signature : ALICE_ADDRESS , } ; const snaps = await rollupGuard . getInputStateSnapshots ( swapTransition ) ; snaps [ 0 ] . stateRoot . should . equal ( genesisStateRoot . replace ( '0x' , '' ) ) ; snaps [ 1 ] . stateRoot . should . equal ( genesisStateRoot . replace ( '0x' , '' ) ) ; snaps [ 0 ] . state . pubkey . should . equal ( ALICE_ADDRESS ) ; snaps [ 1 ] . state . pubkey . should . equal ( UNISWAP_ADDRESS ) ; } ) ; it ( 'should get right inclusion proof for a non creation transfer' , async ( ) => { const genesisStateRootBuf = await rollupGuard . rollupMachine . getStateRoot ( ) ; const genesisStateRoot = bufToHexString ( genesisStateRootBuf ) ; const transferTransition = { stateRoot : 'DOESNT_MATTER' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , recipientSlotIndex : BOB_GENESIS_STATE_INDEX , tokenType : UNI_TOKEN_TYPE , amount : 10 , signature : ALICE_ADDRESS , } ; const snaps = await rollupGuard . getInputStateSnapshots ( transferTransition ) ; snaps [ 0 ] . stateRoot . should . equal ( genesisStateRoot . replace ( '0x' , '' ) ) ; snaps [ 1 ] . stateRoot . should . equal ( genesisStateRoot . replace ( '0x' , '' ) ) ; snaps [ 0 ] . state . pubkey . should . equal ( ALICE_ADDRESS ) ; snaps [ 1 ] . state . pubkey . should . equal ( BOB_ADDRESS ) ; } ) ; it ( 'should get right inclusion proof for a createAndTransfer' , async ( ) => { const genesisStateRootBuf = await rollupGuard . rollupMachine . getStateRoot ( ) ; const genesisStateRoot = bufToHexString ( genesisStateRootBuf ) ; const creationTransition = { stateRoot : 'DOESNT_MATTER' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , recipientSlotIndex : 40 , tokenType : UNI_TOKEN_TYPE , amount : 10 , signature : ALICE_ADDRESS , createdAccountPubkey : BOB_ADDRESS , } ; const snaps = await rollupGuard . getInputStateSnapshots ( creationTransition ) ; snaps [ 0 ] . stateRoot . should . equal ( genesisStateRoot . replace ( '0x' , '' ) ) ; snaps [ 1 ] . stateRoot . should . equal ( genesisStateRoot . replace ( '0x' , '' ) ) ; snaps [ 0 ] . state . pubkey . should . equal ( ALICE_ADDRESS ) ; assert ( snaps [ 1 ] . state . should . deep . equal ( { pubkey : NULL_ADDRESS , balances : { [ UNI_TOKEN_TYPE ] : 0 , [ PIGI_TOKEN_TYPE ] : 0 } , } ) , 'Empty slot should give a default state.' ) ; } ) ; } ) ; describe ( 'checkNextTransition' , ( ) => { it ( 'should return no fraud if correct root for transfer' , async ( ) => { const transitionAliceToBob = { stateRoot : '0x68cb03c6cace1db3a6f7e58db36e8e480ade32e1cba9451a0a63a750b8c48e1a' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , recipientSlotIndex : BOB_GENESIS_STATE_INDEX , tokenType : 0 , amount : 100 , signature : ALICE_ADDRESS , } ; const res = await rollupGuard . checkNextTransition ( transitionAliceToBob ) ; assert ( res === undefined , 'Fraud should not be detected for this valid transition.' ) ; } ) ; it ( 'should return no fraud if correct root for swap' , async ( ) => { const transitionAliceSwap = { stateRoot : '0x351f9762c0826a3c53eb990d3b69f6f27d6a8793b29f2edf825658065f7a991e' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , uniswapSlotIndex : UNISWAP_GENESIS_STATE_INDEX , tokenType : UNI_TOKEN_TYPE , inputAmount : 100 , minOutputAmount : 20 , timeout : 10 , signature : ALICE_ADDRESS , } ; const res = await rollupGuard . checkNextTransition ( transitionAliceSwap ) ; assert ( res === undefined , 'Fraud should not be detected for this valid transition.' ) ; } ) ; it ( 'should return no fraud if correct root for creation transition' , async ( ) => { const transitionAliceToCreatedBob = { stateRoot : '0x24a9c3fdd45a8fadb92d89ab74bb249edbe9a415f1d82a488c2efc5372979710' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , recipientSlotIndex : 4 , tokenType : 0 , amount : 100 , signature : ALICE_ADDRESS , createdAccountPubkey : '0x0100000000000000000000000000000000000000' , } ; const res = await rollupGuard . checkNextTransition ( transitionAliceToCreatedBob ) ; assert ( res === undefined , 'Fraud should not be detected for this valid transition.' ) ; } ) ; it ( 'should return positive for fraud if transition has invalid root' , async ( ) => { const transitionAliceSwap = { stateRoot : '0xdeadbeefb833c9e1086ded944c9fbe011248203e586d81f9fe0922434632dcde' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , uniswapSlotIndex : UNISWAP_GENESIS_STATE_INDEX , tokenType : UNI_TOKEN_TYPE , inputAmount : 100 , minOutputAmount : 20 , timeout : 10 , signature : ALICE_ADDRESS , } ; const res = await rollupGuard . checkNextTransition ( transitionAliceSwap ) ; res . should . not . equal ( undefined ) ; } ) ; it ( "should let us know we can't currently validate if accounts are not created sequentially" , async ( ) => { const outOfOrderCreation = { stateRoot : '0x8bb6f1bd59e26928f8f1531af52224d59d76d6951db31c403bf1e215c99372e6' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , recipientSlotIndex : 300 , tokenType : 0 , amount : 100 , signature : ALICE_ADDRESS , createdAccountPubkey : '0x0100000000000000000000000000000000000000' , } ; try { await rollupGuard . checkNextTransition ( outOfOrderCreation ) ; } catch ( error ) { error . should . be . instanceOf ( AggregatorUnsupportedError ) ; return ; } false . should . equal ( true ) ; } ) ; } ) ; describe ( 'checkNextBlock' , ( ) => { it ( 'should throw if it recieves blocks out of order' , async ( ) => { const blockNumber = 5 ; const wrongOrderBlock = { blockNumber , transitions : undefined , } ; await rollupGuard . storeBlock ( wrongOrderBlock ) ; try { await rollupGuard . validateStoredBlock ( blockNumber ) ; } catch ( e ) { e . should . be . an . instanceOf ( ValidationOutOfOrderError ) ; } } ) ; it ( 'should successfully validate a send followed by a swap' , async ( ) => { const transitionAliceToBob = { stateRoot : '0x68cb03c6cace1db3a6f7e58db36e8e480ade32e1cba9451a0a63a750b8c48e1a' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , recipientSlotIndex : BOB_GENESIS_STATE_INDEX , tokenType : 0 , amount : 100 , signature : ALICE_ADDRESS , } ; const transitionAliceSwap = { stateRoot : '0x0ae582fd70c6fa55ced00cc5a7f5a0f0e0d68447ee7ece74d841548142ba9d32' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , uniswapSlotIndex : UNISWAP_GENESIS_STATE_INDEX , tokenType : UNI_TOKEN_TYPE , inputAmount : 100 , minOutputAmount : 20 , timeout : 10 , signature : ALICE_ADDRESS , } ; const blockNumber = 1 ; const sendThenSwapBlock = { blockNumber , transitions : [ transitionAliceToBob , transitionAliceSwap ] , } ; await rollupGuard . storeBlock ( sendThenSwapBlock ) ; const res = await rollupGuard . validateStoredBlock ( blockNumber ) ; assert ( res === undefined , 'Fraud should not be detected for this valid transition.' ) ; } ) ; it ( 'should successfully get a fraud proof for a valid transition followed by another with invalid root' , async ( ) => { const transitionAliceToBob = { stateRoot : '0x68cb03c6cace1db3a6f7e58db36e8e480ade32e1cba9451a0a63a750b8c48e1a' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , recipientSlotIndex : BOB_GENESIS_STATE_INDEX , tokenType : 0 , amount : 100 , signature : ALICE_ADDRESS , } ; const transitionAliceSwap = { stateRoot : '0xdeadbeef3b1531efd3fa80ce5698f5838e45c62efca5ecde0152f9b165ce6813' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , uniswapSlotIndex : UNISWAP_GENESIS_STATE_INDEX , tokenType : UNI_TOKEN_TYPE , inputAmount : 100 , minOutputAmount : 20 , timeout : 10 , signature : ALICE_ADDRESS , } ; const blockNumber = 1 ; const sendThenSwapBlock = { blockNumber , transitions : [ transitionAliceToBob , transitionAliceSwap ] , } ; await rollupGuard . storeBlock ( sendThenSwapBlock ) ; const res = await rollupGuard . validateStoredBlock ( blockNumber ) ; res . should . not . equal ( undefined ) ; } ) ; it ( 'should return a fraud proof for a block with an invalid initial tx' , async ( ) => { const transitionAliceToBob = { stateRoot : '0x68cb03c6cace1db3a6f7e58db36e8e480ade32e1cba9451a0a63a750b8c48e1a' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , recipientSlotIndex : BOB_GENESIS_STATE_INDEX , tokenType : 0 , amount : 100 , signature : ALICE_ADDRESS , } ; const transitionAliceSwap = { stateRoot : '0x0ae582fd70c6fa55ced00cc5a7f5a0f0e0d68447ee7ece74d841548142ba9d32' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , uniswapSlotIndex : UNISWAP_GENESIS_STATE_INDEX , tokenType : UNI_TOKEN_TYPE , inputAmount : 100 , minOutputAmount : 20 , timeout : 10 , signature : ALICE_ADDRESS , } ; const validFirstBlock = { blockNumber : 1 , transitions : [ transitionAliceToBob , transitionAliceSwap ] , } ; const invalidSendTransition = { stateRoot : '0xdeadbeef000000efd3fa80ce5698f5838e45c62efca5ecde0152f9b165ce6813' , senderSlotIndex : ALICE_GENESIS_STATE_INDEX , recipientSlotIndex : BOB_GENESIS_STATE_INDEX , tokenType : 0 , amount : 100 , signature : ALICE_ADDRESS , } ; const invalidFirstTransitionBlock = { blockNumber : 2 , transitions : [ invalidSendTransition , invalidSendTransition , invalidSendTransition , ] , } ; await rollupGuard . storeBlock ( validFirstBlock ) ; await rollupGuard . validateStoredBlock ( 1 ) ; await rollupGuard . storeBlock ( invalidFirstTransitionBlock ) ; const res = await rollupGuard . validateStoredBlock ( 2 ) ; res [ 0 ] . inclusionProof . transitionIndex . should . equal ( 1 ) ; res [ 0 ] . inclusionProof . blockNumber . should . equal ( 1 ) ; res [ 1 ] . inclusionProof . transitionIndex . should . equal ( 0 ) ; res [ 1 ] . inclusionProof . blockNumber . should . equal ( 2 ) ; } ) ; } ) ; } ) ;