import '../setup' ; import { createMockProvider , deployContract , getWallets } from 'ethereum-waffle' ; import { DefaultSignatureProvider , SignatureProvider , SignatureVerifier , DefaultSignatureVerifier , } from '@pigi/core-utils' ; import { SwapTransition , TransferTransition , CreateAndTransferTransition , abiEncodeTransition , Transfer , Swap , abiEncodeTransaction , Address , } from '../../src' ; import { getAddress , getSignature , getStateRoot , UNISWAP_ADDRESS , UNISWAP_STORAGE_SLOT , } from '../helpers' ; import debug from 'debug' ; const log = debug ( 'test:info:unipig-transition-evaluator' ) ; import * as UnipigTransitionEvaluator from '../../build/contracts/UnipigTransitionEvaluator.json' ; describe ( 'UnipigTransitionEvaluator' , ( ) => { const provider = createMockProvider ( ) ; const [ wallet1 ] = getWallets ( provider ) ; let unipigEvaluator ; let signatureProvider ; let signatureVerifier ; before ( async ( ) => { signatureProvider = new DefaultSignatureProvider ( ) ; signatureVerifier = DefaultSignatureVerifier . instance ( ) ; } ) ; beforeEach ( async ( ) => { unipigEvaluator = await deployContract ( wallet1 , UnipigTransitionEvaluator , [ ] , { gasLimit : 6700000 , } ) ; } ) ; describe ( 'inferTransitionType() ' , async ( ) => { const txTypes = { TRANSFER_NEW_ACCOUNT_TYPE : 0 , TRANSFER_STORED_ACCOUNT_TYPE : 1 , SWAP_TYPE : 2 , } ; it ( 'should infer a transfer new account transaction' , async ( ) => { const createAndTransfer = { stateRoot : getStateRoot ( 'ab' ) , senderSlotIndex : 2 , recipientSlotIndex : 2 , createdAccountPubkey : getAddress ( '01' ) , tokenType : 0 , amount : 1 , signature : getSignature ( '01' ) , } ; const encoded = abiEncodeTransition ( createAndTransfer ) ; const res = await unipigEvaluator . inferTransitionType ( encoded ) ; res . should . equal ( txTypes . TRANSFER_NEW_ACCOUNT_TYPE ) ; } ) ; it ( 'should infer a transfer stored account transaction' , async ( ) => { const transfer = { stateRoot : getStateRoot ( 'ab' ) , senderSlotIndex : 2 , recipientSlotIndex : 2 , tokenType : 0 , amount : 1 , signature : getSignature ( '01' ) , } ; const encoded = abiEncodeTransition ( transfer ) ; const res = await unipigEvaluator . inferTransitionType ( encoded ) ; res . should . equal ( txTypes . TRANSFER_STORED_ACCOUNT_TYPE ) ; } ) ; it ( 'should infer a swap transaction' , async ( ) => { const swap = { stateRoot : getStateRoot ( 'cd' ) , senderSlotIndex : 2 , uniswapSlotIndex : 2 , tokenType : 1 , inputAmount : 1 , minOutputAmount : 3 , timeout : 6 , signature : getSignature ( '0' ) , } ; const encoded = abiEncodeTransition ( swap ) ; const res = await unipigEvaluator . inferTransitionType ( encoded ) ; res . should . equal ( txTypes . SWAP_TYPE ) ; } ) ; it ( 'should revert if a tx has the wrong number of bytes' , async ( ) => { try { const res = await unipigEvaluator . inferTransitionType ( '0x1234' ) ; } catch ( err ) { return ; } throw new Error ( 'Revert expected on invalid tx type length' ) ; } ) ; } ) ; describe ( 'getTransitionStateRootAndAccessList() ' , async ( ) => { const stateRoot = getStateRoot ( 'ab' ) ; const accessList = [ 1 , 2 ] ; it ( 'should return the expected storage slots for createAndTransferTransition' , async ( ) => { const createAndTransfer = { stateRoot , senderSlotIndex : accessList [ 0 ] , recipientSlotIndex : accessList [ 1 ] , createdAccountPubkey : getAddress ( '01' ) , tokenType : 0 , amount : 1 , signature : getSignature ( '01' ) , } ; const encoded = abiEncodeTransition ( createAndTransfer ) ; const res = await unipigEvaluator . getTransitionStateRootAndAccessList ( encoded ) ; res . should . deep . equal ( [ stateRoot , accessList ] ) ; } ) ; it ( 'should return the expected storage slots for transferTransition' , async ( ) => { const transfer = { stateRoot , senderSlotIndex : accessList [ 0 ] , recipientSlotIndex : accessList [ 1 ] , tokenType : 0 , amount : 1 , signature : getSignature ( '01' ) , } ; const encoded = abiEncodeTransition ( transfer ) ; const res = await unipigEvaluator . getTransitionStateRootAndAccessList ( encoded ) ; res . should . deep . equal ( [ stateRoot , accessList ] ) ; } ) ; it ( 'should return the expected storage slots for swapTransition' , async ( ) => { const swap = { stateRoot , senderSlotIndex : accessList [ 0 ] , uniswapSlotIndex : accessList [ 1 ] , tokenType : 1 , inputAmount : 1 , minOutputAmount : 3 , timeout : 6 , signature : getSignature ( '01' ) , } ; const encoded = abiEncodeTransition ( swap ) ; const res = await unipigEvaluator . getTransitionStateRootAndAccessList ( encoded ) ; res . should . deep . equal ( [ stateRoot , accessList ] ) ; } ) ; it ( 'should throw if we put in some random bytes as the transition' , async ( ) => { const badEncoding = '0xdeadbeefdeadbeefdeadbeef' ; try { const res = await unipigEvaluator . getTransitionStateRootAndAccessList ( badEncoding ) ; } catch ( err ) { return ; } throw new Error ( 'Expected bad encoding to fail!' ) ; } ) ; } ) ; describe ( 'applyTransferTransition() ' , async ( ) => { it ( 'should return the correct storage slots after a successful send' , async ( ) => { const sentAmount = 5 ; const initialBalances = [ 1000 , 1000 ] ; const senderSlotIndex = 50 ; const senderAddress = ( await signatureProvider . getAddress ( ) ) as Address ; const recipientAddress = getAddress ( '38' ) ; const recipientSlotIndex = 100 ; const tokenType = 0 ; const senderStorageSlot = { slotIndex : senderSlotIndex , value : { pubkey : senderAddress , balances : initialBalances , } , } ; const recipientStorageSlot = { slotIndex : recipientSlotIndex , value : { pubkey : recipientAddress , balances : initialBalances , } , } ; const transaction = { sender : senderAddress , recipient : recipientAddress , tokenType , amount : sentAmount , } ; const signature = await signatureProvider . sign ( abiEncodeTransaction ( transaction ) ) ; const transition = { stateRoot : getStateRoot ( 'ab' ) , senderSlotIndex , recipientSlotIndex , tokenType , amount : sentAmount , signature , } ; const res = await unipigEvaluator . applyTransferTransition ( transition , [ senderStorageSlot , recipientStorageSlot , ] ) ; res [ 0 ] . balances . should . deep . equal ( [ 995 , 1000 ] ) ; res [ 1 ] . balances . should . deep . equal ( [ 1005 , 1000 ] ) ; } ) ; it ( 'should throw if the sender does not have enough money' , async ( ) => { const sentAmount = 1100 ; const initialBalances = [ 1000 , 1000 ] ; const senderSlotIndex = 50 ; const senderAddress = getAddress ( '48' ) ; const recipientAddress = getAddress ( '38' ) ; const recipientSlotIndex = 100 ; const senderStorageSlot = { slotIndex : senderSlotIndex , value : { pubkey : senderAddress , balances : initialBalances , } , } ; const recipientStorageSlot = { slotIndex : recipientSlotIndex , value : { pubkey : recipientAddress , balances : initialBalances , } , } ; const transition = { stateRoot : getStateRoot ( 'ab' ) , senderSlotIndex , recipientSlotIndex , tokenType : 0 , amount : sentAmount , signature : getSignature ( '9' ) , } ; try { const res = await unipigEvaluator . applyTransferTransition ( transition , [ senderStorageSlot , recipientStorageSlot , ] ) ; } catch ( err ) { return ; } throw new Error ( 'Expected to fail due to insufficient balance!' ) ; } ) ; it ( 'should throw if the signature for the transfer is invalid' , async ( ) => { const sentAmount = 900 ; const initialBalances = [ 1000 , 1000 ] ; const senderSlotIndex = 50 ; const senderAddress = getAddress ( '48' ) ; const recipientAddress = getAddress ( '38' ) ; const recipientSlotIndex = 100 ; const senderStorageSlot = { slotIndex : senderSlotIndex , value : { pubkey : senderAddress , balances : initialBalances , } , } ; const recipientStorageSlot = { slotIndex : recipientSlotIndex , value : { pubkey : recipientAddress , balances : initialBalances , } , } ; const transition = { stateRoot : getStateRoot ( 'ab' ) , senderSlotIndex , recipientSlotIndex , tokenType : 0 , amount : sentAmount , signature : getSignature ( '9' ) , } ; try { const res = await unipigEvaluator . applyTransferTransition ( transition , [ senderStorageSlot , recipientStorageSlot , ] ) ; } catch ( err ) { return ; } throw new Error ( 'Expected to fail due to infalid signature!' ) ; } ) ; } ) ; describe ( 'applyCreateAndTransferTransition() ' , async ( ) => { it ( 'should succeed if the recipient storage is empty and it is a successful send' , async ( ) => { const sentAmount = 5 ; const initialBalances = [ 1000 , 1000 ] ; const senderSlotIndex = 50 ; const senderAddress = await signatureProvider . getAddress ( ) ; const recipientAddress = getAddress ( '38' ) ; const recipientSlotIndex = 100 ; const tokenType = 0 ; const senderStorageSlot = { slotIndex : senderSlotIndex , value : { pubkey : senderAddress , balances : initialBalances , } , } ; const recipientStorageSlot = { slotIndex : recipientSlotIndex , value : { pubkey : getAddress ( '00' ) , balances : [ 0 , 0 ] , } , } ; const transaction = { sender : senderAddress , recipient : recipientAddress , tokenType , amount : sentAmount , } ; const signature = await signatureProvider . sign ( abiEncodeTransaction ( transaction ) ) ; const transition = { stateRoot : getStateRoot ( 'ab' ) , senderSlotIndex , recipientSlotIndex , createdAccountPubkey : recipientAddress , tokenType , amount : sentAmount , signature , } ; const res = await unipigEvaluator . applyCreateAndTransferTransition ( transition , [ senderStorageSlot , recipientStorageSlot ] ) ; res [ 0 ] . balances . should . deep . equal ( [ 995 , 1000 ] ) ; res [ 1 ] . balances . should . deep . equal ( [ 5 , 0 ] ) ; res [ 1 ] . pubkey . should . equal ( recipientAddress ) ; } ) ; it ( 'should throw if the recipient storage is NOT empty and it is a successful send' , async ( ) => { const sentAmount = 5 ; const initialBalances = [ 1000 , 1000 ] ; const senderSlotIndex = 50 ; const senderAddress = getAddress ( '48' ) ; const recipientAddress = getAddress ( '38' ) ; const recipientSlotIndex = 100 ; const senderStorageSlot = { slotIndex : senderSlotIndex , value : { pubkey : senderAddress , balances : initialBalances , } , } ; const recipientStorageSlot = { slotIndex : recipientSlotIndex , value : { pubkey : recipientAddress , balances : [ 10 , 10 ] , } , } ; const transition = { stateRoot : getStateRoot ( 'ab' ) , senderSlotIndex , recipientSlotIndex , createdAccountPubkey : recipientAddress , tokenType : 0 , amount : sentAmount , signature : getSignature ( '9' ) , } ; try { const res = await unipigEvaluator . applyCreateAndTransferTransition ( transition , [ senderStorageSlot , recipientStorageSlot ] ) ; } catch ( err ) { return ; } throw new Error ( 'Expected to fail due to a non-empty storage slot!' ) ; } ) ; } ) ; describe ( 'applySwapTransition() ' , async ( ) => { const senderSlotIndex = 3428942 ; const timeout = + new Date ( ) ; const tokenType = 1 ; it ( 'should return the correct storage slots after a successful swap' , async ( ) => { const senderAddress = await signatureProvider . getAddress ( ) ; const inputAmount = 5 ; const minOutputAmount = 4 ; const initialBalances = [ 1000 , 1000 ] ; const senderStorageSlot = { slotIndex : senderSlotIndex , value : { pubkey : senderAddress , balances : initialBalances , } , } ; const uniswapStorageSlot = { slotIndex : UNISWAP_STORAGE_SLOT , value : { pubkey : UNISWAP_ADDRESS , balances : initialBalances , } , } ; const transaction = { sender : senderAddress , tokenType , inputAmount , minOutputAmount , timeout , } ; const signature = await signatureProvider . sign ( abiEncodeTransaction ( transaction ) ) ; const swap = { stateRoot : getStateRoot ( 'cd' ) , senderSlotIndex , uniswapSlotIndex : UNISWAP_STORAGE_SLOT , tokenType , inputAmount , minOutputAmount , timeout , signature , } ; const res = await unipigEvaluator . applySwapTransition ( swap , [ senderStorageSlot , uniswapStorageSlot , ] ) ; res [ 0 ] . balances . should . deep . equal ( [ 1004 , 995 ] ) ; res [ 0 ] . pubkey . should . equal ( senderAddress ) ; res [ 1 ] . balances . should . deep . equal ( [ 996 , 1005 ] ) ; res [ 1 ] . pubkey . should . equal ( UNISWAP_ADDRESS ) ; } ) ; it ( 'should throw if the min output amount is too high' , async ( ) => { const senderAddress = await signatureProvider . getAddress ( ) ; const inputAmount = 5 ; const minOutputAmount = 5 ; const initialBalances = [ 1000 , 1000 ] ; const senderStorageSlot = { slotIndex : senderSlotIndex , value : { pubkey : senderAddress , balances : initialBalances , } , } ; const uniswapStorageSlot = { slotIndex : UNISWAP_STORAGE_SLOT , value : { pubkey : UNISWAP_ADDRESS , balances : initialBalances , } , } ; const swap = { stateRoot : getStateRoot ( 'cd' ) , senderSlotIndex , uniswapSlotIndex : UNISWAP_STORAGE_SLOT , tokenType , inputAmount , minOutputAmount , timeout , signature : getSignature ( 'aa' ) , } ; try { const res = await unipigEvaluator . applySwapTransition ( swap , [ senderStorageSlot , uniswapStorageSlot , ] ) ; } catch ( err ) { return ; } throw new Error ( 'Expected minOutputAmount to be too high & for this to throw!' ) ; } ) ; it ( 'should throw if the sender cannot aford the swap' , async ( ) => { const senderAddress = await signatureProvider . getAddress ( ) ; const inputAmount = 5000 ; const minOutputAmount = 4 ; const initialBalances = [ 1000 , 1000 ] ; const senderStorageSlot = { slotIndex : senderSlotIndex , value : { pubkey : senderAddress , balances : initialBalances , } , } ; const uniswapStorageSlot = { slotIndex : UNISWAP_STORAGE_SLOT , value : { pubkey : UNISWAP_ADDRESS , balances : initialBalances , } , } ; const swap = { stateRoot : getStateRoot ( 'cd' ) , senderSlotIndex , uniswapSlotIndex : UNISWAP_STORAGE_SLOT , tokenType , inputAmount , minOutputAmount , timeout , signature : getSignature ( 'aa' ) , } ; try { const res = await unipigEvaluator . applySwapTransition ( swap , [ senderStorageSlot , uniswapStorageSlot , ] ) ; } catch ( err ) { return ; } throw new Error ( 'Expected sender not being able to afford the tx to cause this to throw!' ) ; } ) ; it ( 'should throw if the 2nd storage slot doesnt match the Uniswap address' , async ( ) => { const senderAddress = await signatureProvider . getAddress ( ) ; const NOT_UNISWAP_STORAGE_SLOT = 10 ; const NOT_UNISWAP_ADDRESS = getAddress ( '99' ) ; const inputAmount = 5000 ; const minOutputAmount = 4 ; const initialBalances = [ 1000 , 1000 ] ; const senderStorageSlot = { slotIndex : senderSlotIndex , value : { pubkey : senderAddress , balances : initialBalances , } , } ; const uniswapStorageSlot = { slotIndex : NOT_UNISWAP_STORAGE_SLOT , value : { pubkey : NOT_UNISWAP_ADDRESS , balances : initialBalances , } , } ; const swap = { stateRoot : getStateRoot ( 'cd' ) , senderSlotIndex , uniswapSlotIndex : NOT_UNISWAP_STORAGE_SLOT , tokenType , inputAmount , minOutputAmount , timeout , signature : getSignature ( 'aa' ) , } ; try { const res = await unipigEvaluator . applySwapTransition ( swap , [ senderStorageSlot , uniswapStorageSlot , ] ) ; } catch ( err ) { return ; } throw new Error ( 'Swapping with a user other than Uniswap should cause the tx to throw!' ) ; } ) ; it ( 'should throw if the signature for the swap does not match the sender' , async ( ) => { const senderAddress = await signatureProvider . getAddress ( ) ; const inputAmount = 5 ; const minOutputAmount = 4 ; const initialBalances = [ 1000 , 1000 ] ; const senderStorageSlot = { slotIndex : senderSlotIndex , value : { pubkey : senderAddress , balances : initialBalances , } , } ; const uniswapStorageSlot = { slotIndex : UNISWAP_STORAGE_SLOT , value : { pubkey : UNISWAP_ADDRESS , balances : initialBalances , } , } ; const swap = { stateRoot : getStateRoot ( 'cd' ) , senderSlotIndex , uniswapSlotIndex : UNISWAP_STORAGE_SLOT , tokenType , inputAmount , minOutputAmount , timeout , signature : getSignature ( 'aa' ) , } ; try { const res = await unipigEvaluator . applySwapTransition ( swap , [ senderStorageSlot , uniswapStorageSlot , ] ) ; } catch ( err ) { return ; } throw new Error ( 'Expected to fail due to infalid signature!' ) ; } ) ; } ) ; describe ( 'evaluateTransition() ' , async ( ) => { it ( 'should evaluate a transfer transition without failing' , async ( ) => { const sentAmount = 5 ; const initialBalances = [ 1000 , 1000 ] ; const senderSlotIndex = 50 ; const senderAddress = await signatureProvider . getAddress ( ) ; const recipientAddress = getAddress ( '38' ) ; const recipientSlotIndex = 100 ; const tokenType = 0 ; const senderStorageSlot = { slotIndex : senderSlotIndex , value : { pubkey : senderAddress , balances : initialBalances , } , } ; const recipientStorageSlot = { slotIndex : recipientSlotIndex , value : { pubkey : recipientAddress , balances : initialBalances , } , } ; const transaction = { sender : senderAddress , recipient : recipientAddress , tokenType , amount : sentAmount , } ; const signature = await signatureProvider . sign ( abiEncodeTransaction ( transaction ) ) ; const transfer = { stateRoot : getStateRoot ( 'ab' ) , senderSlotIndex , recipientSlotIndex , tokenType , amount : sentAmount , signature , } ; const encoded = abiEncodeTransition ( transfer ) ; const res = await unipigEvaluator . evaluateTransition ( encoded , [ senderStorageSlot , recipientStorageSlot , ] ) ; log ( 'Transfer Transition successfully returned:' , res ) ; } ) ; it ( 'should evaluate a CreateAndTransferTransition without throwing' , async ( ) => { const sentAmount = 5 ; const initialBalances = [ 1000 , 1000 ] ; const senderSlotIndex = 50 ; const senderAddress = await signatureProvider . getAddress ( ) ; const recipientAddress = getAddress ( '38' ) ; const recipientSlotIndex = 100 ; const tokenType = 0 ; const senderStorageSlot = { slotIndex : senderSlotIndex , value : { pubkey : senderAddress , balances : initialBalances , } , } ; const recipientStorageSlot = { slotIndex : recipientSlotIndex , value : { pubkey : getAddress ( '00' ) , balances : [ 0 , 0 ] , } , } ; const transaction = { sender : senderAddress , recipient : recipientAddress , tokenType , amount : sentAmount , } ; const signature = await signatureProvider . sign ( abiEncodeTransaction ( transaction ) ) ; const createAndTransfer = { stateRoot : getStateRoot ( 'ab' ) , senderSlotIndex , recipientSlotIndex , createdAccountPubkey : recipientAddress , tokenType , amount : sentAmount , signature , } ; const encoded = abiEncodeTransition ( createAndTransfer ) ; const res = await unipigEvaluator . evaluateTransition ( encoded , [ senderStorageSlot , recipientStorageSlot , ] ) ; log ( 'Create and Transfer Transition successfully returned:' , res ) ; } ) ; it ( 'should evaluate a swap transition without failing' , async ( ) => { const senderAddress = await signatureProvider . getAddress ( ) ; const senderSlotIndex = 3428942 ; const timeout = + new Date ( ) ; const tokenType = 1 ; const inputAmount = 5 ; const minOutputAmount = 4 ; const initialBalances = [ 1000 , 1000 ] ; const senderStorageSlot = { slotIndex : senderSlotIndex , value : { pubkey : senderAddress , balances : initialBalances , } , } ; const uniswapStorageSlot = { slotIndex : UNISWAP_STORAGE_SLOT , value : { pubkey : UNISWAP_ADDRESS , balances : initialBalances , } , } ; const transaction = { sender : senderAddress , tokenType , inputAmount , minOutputAmount , timeout , } ; const signature = await signatureProvider . sign ( abiEncodeTransaction ( transaction ) ) ; const swap = { stateRoot : getStateRoot ( 'cd' ) , senderSlotIndex , uniswapSlotIndex : UNISWAP_STORAGE_SLOT , tokenType , inputAmount , minOutputAmount , timeout , signature , } ; const encoded = abiEncodeTransition ( swap ) ; const res = await unipigEvaluator . evaluateTransition ( encoded , [ senderStorageSlot , uniswapStorageSlot , ] ) ; log ( 'Swap Transition successfully returned:' , res ) ; } ) ; } ) ; } ) ;