@ethersproject/bytes#arrayify TypeScript Examples

The following examples show how to use @ethersproject/bytes#arrayify. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts    From fuels-ts with Apache License 2.0 7 votes vote down vote up
export function mnemonicWordsToEntropy(words: Array<string>, wordlist: Array<string>): BytesLike {
  const size = Math.ceil((11 * words.length) / 8);
  const entropy = arrayify(new Uint8Array(size));

  let offset = 0;
  for (let i = 0; i < words.length; i += 1) {
    const index = wordlist.indexOf(words[i].normalize('NFKD'));
    if (index === -1) {
      throw new Error('invalid mnemonic');
    }

    for (let bit = 0; bit < 11; bit += 1) {
      if (index & (1 << (10 - bit))) {
        entropy[offset >> 3] |= 1 << (7 - (offset % 8));
      }
      offset += 1;
    }
  }
  const entropyBits = (32 * words.length) / 3;
  const checksumBits = words.length / 3;
  const checksumMask = getUpperMask(checksumBits);
  const checksum = arrayify(sha256(entropy.slice(0, entropyBits / 8)))[0] & checksumMask;

  if (checksum !== (entropy[entropy.length - 1] & checksumMask)) {
    throw new Error('invalid checksum');
  }

  return entropy.slice(0, entropyBits / 8);
}
Example #2
Source File: parseTransaction.ts    From bodhi.js with Apache License 2.0 6 votes vote down vote up
export function checkSignatureType(rawTransaction: BytesLike): SignatureType {
  const payload = arrayify(rawTransaction);

  if (payload[0] > 0x7f || payload[0] === 1) return 'Ethereum'; // Legacy and EIP-155
  if (payload[0] === 2) return 'Eip1559'; // EIP-1559
  if (payload[0] === 96) return 'AcalaEip712'; // Acala EIP-712

  return logger.throwError(`unsupported transaction type: ${payload[0]}`, Logger.errors.UNSUPPORTED_OPERATION, {
    operation: 'checkSignatureType',
    transactionType: payload[0]
  });
}
Example #3
Source File: byte-array.ts    From fuels-ts with Apache License 2.0 6 votes vote down vote up
encode(value: BytesLike): Uint8Array {
    const parts: Uint8Array[] = [];

    const data = arrayify(value);
    parts.push(data);
    // Write padding
    const pad = padToBytes - (this.length % padToBytes);
    if (pad % padToBytes) {
      parts.push(new Uint8Array(pad).fill(0));
    }

    return concat(parts);
  }
Example #4
Source File: Tokens.ts    From limit-orders-lib with GNU General Public License v3.0 6 votes vote down vote up
function parseStringOrBytes32(
  str: string | undefined,
  bytes32: string | undefined,
  defaultValue: string
): string {
  return str && str.length > 0
    ? str
    : // need to check for proper bytes string and valid terminator
    bytes32 && BYTES32_REGEX.test(bytes32) && arrayify(bytes32)[31] === 0
    ? parseBytes32String(bytes32)
    : defaultValue;
}
Example #5
Source File: eip1271.ts    From snapshot.js with MIT License 6 votes vote down vote up
export async function verifyDefault(
  address: string,
  sig: string,
  hash: string,
  provider: StaticJsonRpcProvider
) {
  let returnValue;
  const magicValue = '0x1626ba7e';
  const abi =
    'function isValidSignature(bytes32 _hash, bytes memory _signature) public view returns (bytes4 magicValue)';
  try {
    returnValue = await call(
      provider,
      [abi],
      [address, 'isValidSignature', [arrayify(hash), sig]]
    );
  } catch (e) {
    console.log(e);
    return false;
  }
  return returnValue.toLowerCase() === magicValue.toLowerCase();
}
Example #6
Source File: transaction.test.ts    From hubble-contracts with MIT License 6 votes vote down vote up
txFactory = (
    fromIndex: number,
    toIndex: number,
    amount: number,
    fee: number,
    nonce: number
): OffchainTx => {
    const tx = new TransferOffchainTx(
        BigNumber.from(fromIndex),
        BigNumber.from(toIndex),
        BigNumber.from(amount),
        BigNumber.from(fee),
        BigNumber.from(nonce)
    );
    const signer = BlsSigner.new(arrayify(randHex(32)));
    tx.signature = signer.sign(tx.message());
    return tx;
}
Example #7
Source File: mnemonic.ts    From fuels-ts with Apache License 2.0 6 votes vote down vote up
/**
   *  Create a new mnemonic using a randomly generated number as entropy.
   *  As defined in BIP39, the entropy must be a multiple of 32 bits, and its size must be between 128 and 256 bits.
   *  Therefore, the possible values for `strength` are 128, 160, 192, 224, and 256.
   *  If not provided, the default entropy length will be set to 256 bits.
   *  The return is a list of words that encodes the generated entropy.
   *
   *
   * @param size - Number of bytes used as an entropy
   * @param extraEntropy - Optional extra entropy to increase randomness
   * @returns A randomly generated mnemonic
   */
  static generate(size: number = 32, extraEntropy: BytesLike = '') {
    const entropy = extraEntropy
      ? sha256(concat([randomBytes(size), arrayify(extraEntropy)]))
      : randomBytes(size);
    return Mnemonic.entropyToMnemonic(entropy);
  }
Example #8
Source File: byterepr.ts    From clarity with Apache License 2.0 6 votes vote down vote up
toBytesNumber = (
  bitSize: number,
  signed: boolean,
  value: BigNumberish
) => {
  let v = BigNumber.from(value);

  // Check bounds are safe for encoding
  const maxUintValue = MaxUint256.mask(bitSize);
  if (signed) {
    const bounds = maxUintValue.mask(bitSize - 1); // 1 bit for signed
    if (v.gt(bounds) || v.lt(bounds.add(One).mul(NegativeOne))) {
      throw new Error('value out-of-bounds, value: ' + value);
    }
  } else if (v.lt(Zero) || v.gt(maxUintValue.mask(bitSize))) {
    throw new Error('value out-of-bounds, value: ' + value);
  }
  v = v.toTwos(bitSize).mask(bitSize);
  const bytes = arrayify(v);
  if (v.gte(0)) {
    // for positive number, we had to deal with paddings
    if (bitSize > 64) {
      // for u128, u256, u512, we have to and append extra byte for length
      return concat([bytes, Uint8Array.from([bytes.length])]).reverse();
    } else {
      // for other types, we have to add padding 0s
      const byteLength = bitSize / 8;
      return concat([
        bytes.reverse(),
        new Uint8Array(byteLength - bytes.length)
      ]);
    }
  } else {
    return bytes.reverse();
  }
}
Example #9
Source File: parseTransaction.ts    From bodhi.js with Apache License 2.0 6 votes vote down vote up
export function parseTransaction(rawTransaction: BytesLike): AcalaEvmTX {
  const payload = arrayify(rawTransaction);

  // Ethereum Transactions
  if (payload[0] > 0x7f || payload[0] === 1 || payload[0] === 2) {
    return parse(payload);
  }

  // EIP 712
  if (payload[0] === 96) {
    return parseEip712(payload);
  }

  return logger.throwError(`unsupported transaction type: ${payload[0]}`, Logger.errors.UNSUPPORTED_OPERATION, {
    operation: 'parseTransaction',
    transactionType: payload[0]
  });
}
Example #10
Source File: transaction-request.ts    From fuels-ts with Apache License 2.0 6 votes vote down vote up
toTransaction(): Transaction {
    const script = arrayify(this.script ?? '0x');
    const scriptData = arrayify(this.scriptData ?? '0x');
    return {
      type: TransactionType.Script,
      ...super.getBaseTransaction(),
      scriptLength: script.length,
      scriptDataLength: scriptData.length,
      receiptsRoot: ZeroBytes32,
      script: hexlify(script),
      scriptData: hexlify(scriptData),
    };
  }
Example #11
Source File: index.ts    From ccip-read with MIT License 5 votes vote down vote up
async function handleCall(
  provider: CCIPReadProvider,
  params: { transaction: TransactionRequest; blockTag?: BlockTag },
  maxCalls = 4
): Promise<{ transaction: TransactionRequest; result: BytesLike }> {
  for (let i = 0; i < maxCalls; i++) {
    let result;
    let bytes: Uint8Array;
    try {
      result = await provider.parent.perform('call', params);
      bytes = arrayify(result);
    } catch (e) {
      if (isRevertError(e)) {
        bytes = arrayify(e.error.data.originalError.data);
      } else {
        return logger.throwError('The error message does not contain originalError', Logger.errors.UNKNOWN_ERROR);
      }
    }
    if (bytes.length % 32 !== 4 || hexlify(bytes.slice(0, 4)) !== CCIP_READ_INTERFACE.getSighash('OffchainLookup')) {
      return { transaction: params.transaction, result: bytes };
    }
    const { sender, urls, callData, callbackFunction, extraData } = CCIP_READ_INTERFACE.decodeErrorResult(
      'OffchainLookup',
      bytes
    );
    if (params.transaction.to === undefined || sender.toLowerCase() !== params.transaction.to.toLowerCase()) {
      return logger.throwError('OffchainLookup thrown in nested scope', Logger.errors.UNSUPPORTED_OPERATION, {
        to: params.transaction.to,
        sender,
        urls,
        callData,
        callbackFunction,
        extraData,
      });
    }
    const response = await sendRPC(provider.fetcher, urls, params.transaction.to, callData);
    const data = hexConcat([
      callbackFunction,
      defaultAbiCoder.encode(CCIP_READ_INTERFACE.getFunction('callback').inputs, [response, extraData]),
    ]);
    params = Object.assign({}, params, {
      transaction: Object.assign({}, params.transaction, { data }),
    });
  }
  return logger.throwError('Too many redirects', Logger.errors.TIMEOUT, { to: params.transaction.to });
}
Example #12
Source File: ByteConverters.ts    From casper-js-sdk with Apache License 2.0 5 votes vote down vote up
toBytesNumber = (bitSize: number, signed: boolean) => (
  value: BigNumberish
): Uint8Array => {
  const val = BigNumber.from(value);

  // Check bounds are safe for encoding
  const maxUintValue = MaxUint256.mask(bitSize);

  if (signed) {
    const bounds = maxUintValue.mask(bitSize - 1); // 1 bit for signed
    if (val.gt(bounds) || val.lt(bounds.add(One).mul(NegativeOne))) {
      throw new Error('value out-of-bounds, value: ' + value);
    }
  } else if (val.lt(Zero) || val.gt(maxUintValue.mask(bitSize))) {
    throw new Error('value out-of-bounds, value: ' + value);
  }

  const valTwos = val.toTwos(bitSize).mask(bitSize);

  const bytes = arrayify(valTwos);

  if (valTwos.gte(0)) {
    // for positive number, we had to deal with paddings
    if (bitSize > 64) {
      // if zero just return zero
      if (valTwos.eq(0)) {
        return bytes;
      }
      // for u128, u256, u512, we have to and append extra byte for length
      return concat([bytes, Uint8Array.from([bytes.length])])
        .slice()
        .reverse();
    } else {
      // for other types, we have to add padding 0s
      const byteLength = bitSize / 8;
      return concat([
        bytes.slice().reverse(),
        new Uint8Array(byteLength - bytes.length)
      ]);
    }
  } else {
    return bytes.reverse();
  }
}
Example #13
Source File: scripts.ts    From fuels-ts with Apache License 2.0 5 votes vote down vote up
contractCallScript = new Script<
  { contractId: BytesLike; assetId?: BytesLike; amount?: BigNumberish; data: BytesLike },
  Uint8Array
>(
  // Script to call the contract
  contractCallScriptBin,
  ({ contractId, amount, assetId, data }) => {
    // Decode data in internal format
    const dataArray = arrayify(data);
    const functionSelector = dataArray.slice(0, 8);
    const isReferenceType = dataArray.slice(8, 16).some((b) => b === 0x01);
    const args = dataArray.slice(16);

    // Encode data in script format
    let scriptData = [
      // Insert asset_id to be forwarded
      new B256Coder().encode(hexlify(assetId || NativeAssetId)),
      // Insert amount to be forwarded
      new NumberCoder('u64').encode(BigInt(amount ?? 0)),
      // Contract id
      contractId,
      // Function selector
      functionSelector,
    ];

    if (isReferenceType) {
      // Insert data offset to custom argument types
      scriptData = scriptData.concat(
        new NumberCoder('u64').encode(contractCallScript.getArgOffset())
      );
    }

    // Encode script data
    return concat(
      // Insert arguments
      scriptData.concat(args)
    );
  },
  (result) => {
    if (result.code !== 0n) {
      throw new Error(`Script returned non-zero result: ${result.code}`);
    }
    const contractReturnReceipt = result.receipts.pop();
    if (!contractReturnReceipt) {
      throw new Error(`Expected contractReturnReceipt`);
    }
    switch (contractReturnReceipt.type) {
      case ReceiptType.Return: {
        // The receipt doesn't have the expected encoding, so encode it manually
        const returnValue = new NumberCoder('u64').encode(contractReturnReceipt.val);
        return returnValue;
      }
      case ReceiptType.ReturnData: {
        return arrayify(contractReturnReceipt.data);
      }
      default: {
        throw new Error(`Invalid contractReturnReceipt type: ${contractReturnReceipt.type}`);
      }
    }
  }
)
Example #14
Source File: provider.test.ts    From ccip-read with MIT License 4 votes vote down vote up
describe('ethers-ccip-read-provider', () => {
  const baseProvider = new ethers.providers.Web3Provider(ganache.provider());
  const messageSigner = new ethers.Wallet(TEST_PRIVATE_KEY);
  let ccipProvider: CCIPReadProvider;
  let utilsContract: ethers.Contract;
  let contract: ethers.Contract;
  let account: string;
  let snapshot: number;

  const server = new Server();
  server.add(
    ['function getSignedBalance(address addr) view returns(uint256 balance, bytes memory sig)'],
    [
      {
        type: 'getSignedBalance',
        func: async (args) => {
          const [addr] = args;
          const balance = ethers.BigNumber.from('1000000000000000000000');
          let messageHash = keccak256(['uint256', 'address'], [balance, addr]);
          let messageHashBinary = arrayify(messageHash);
          const signature = await messageSigner.signMessage(messageHashBinary);
          return [balance, signature];
        },
      },
    ]
  );

  function fetcher(url: string, json?: string, _processFunc?: (value: any, response: FetchJsonResponse) => any) {
    if(json === undefined) {
      const [_match, to, data] = url.match(/http:\/\/localhost:8000\/rpc\/([^/]+)\/([^/]+).json/) as RegExpMatchArray;
      return server.call({ to, data });
    } else {
      expect(url).to.equal(TEST_POST_URL);
      const {sender, data} = JSON.parse(json);
      return server.call({ to: sender, data});
    }
  }

  beforeAll(async () => {
    const signer = await baseProvider.getSigner();
    account = await signer.getAddress();

    const proxyMiddleware = new RevertNormalisingMiddleware(baseProvider);
    ccipProvider = new CCIPReadProvider(proxyMiddleware, fetcher);

    utilsContract = (await deploySolidity(testUtils, signer)).connect(ccipProvider);

    const c = await deploySolidity(token, signer, 'Test', 'TST', 0);
    await c.setSigner(await messageSigner.getAddress());
    await c.setUrls([TEST_URL]);
    contract = c.connect(ccipProvider);

    snapshot = await baseProvider.send('evm_snapshot', []);
  });

  afterEach(async () => {
    await baseProvider.send('evm_revert', [snapshot]);
  });

  describe('CCIPReadProvider', () => {
    it('passes calls through to the underlying provider', async () => {
      const network = await baseProvider.getNetwork();
      expect((await ccipProvider.getNetwork()).chainId).to.equal(network.chainId);
    });

    it('handles an OffchainLookup', async () => {
      expect((await contract.connect(ccipProvider).balanceOf(account)).toString()).to.equal('1000000000000000000000');
    });

    it('handles an OffchainLookup via POST', async () => {
      await contract.connect(await baseProvider.getSigner()).setUrls([TEST_POST_URL]);
      expect((await contract.connect(ccipProvider).balanceOf(account)).toString()).to.equal('1000000000000000000000');
    });

    it('throws an error if the OffchainLookup is thrown in a nested scope', async () => {
      await expect(utilsContract.balanceOf(contract.address, account)).to.be.rejectedWith(
        'OffchainLookup thrown in nested scope'
      );
    });
  });

  describe('CCIPReadSigner', () => {
    let signer: CCIPReadSigner;

    beforeAll(async () => {
      signer = await ccipProvider.getSigner();
    });

    it('sends regular transactions', async () => {
      await contract.connect(signer).setUrls([TEST_URL]);
      expect(await contract.urls(0)).to.equal(TEST_URL);
    });

    it('translates CCIP read transactions', async () => {
      expect((await contract.connect(signer).balanceOf(account)).toString()).to.equal('1000000000000000000000');
      const tx = await contract.connect(signer).transfer(TEST_ACCOUNT, '1000000000000000000');
      const receipt = await tx.wait();
      expect(receipt.status).to.equal(1);
      expect((await contract.balanceOf(account)).toString()).to.equal('999000000000000000000');
      expect((await contract.balanceOf(TEST_ACCOUNT)).toString()).to.equal('1001000000000000000000');
    });
  });
});
Example #15
Source File: provider.test.ts    From fuels-ts with Apache License 2.0 4 votes vote down vote up
describe('Provider', () => {
  it('can getVersion()', async () => {
    const provider = new Provider('http://127.0.0.1:4000/graphql');

    const version = await provider.getVersion();

    expect(version).toEqual('0.7.1');
  });

  it('can call()', async () => {
    const provider = new Provider('http://127.0.0.1:4000/graphql');

    const callResult = await provider.call({
      type: TransactionType.Script,
      gasPrice: 0n,
      gasLimit: 1000000n,
      bytePrice: 0n,
      script:
        /*
          Opcode::ADDI(0x10, REG_ZERO, 0xCA)
          Opcode::ADDI(0x11, REG_ZERO, 0xBA)
          Opcode::LOG(0x10, 0x11, REG_ZERO, REG_ZERO)
          Opcode::RET(REG_ONE)
        */
        arrayify('0x504000ca504400ba3341100024040000'),
      scriptData: randomBytes(32),
    });

    const expectedReceipts: Receipt[] = [
      {
        type: ReceiptType.Log,
        id: ZeroBytes32,
        val0: BigInt(202),
        val1: BigInt(186),
        val2: BigInt(0),
        val3: BigInt(0),
        pc: BigInt(0x2878),
        is: BigInt(0x2870),
      },
      {
        type: ReceiptType.Return,
        id: ZeroBytes32,
        val: BigInt(1),
        pc: BigInt(0x287c),
        is: BigInt(0x2870),
      },
      {
        type: ReceiptType.ScriptResult,
        result: BigInt(0),
        gasUsed: BigInt(0x2c),
      },
    ];

    expect(callResult.receipts).toEqual(expectedReceipts);
  });

  // TODO: Add tests to provider sendTransaction
  // sendTransaction can't be tested without a valid signature
  // importing and testing it here can generate cycle dependency
  // as we test this in other modules like call contract its ok to
  // skip for now
  it.skip('can sendTransaction()', async () => {
    const provider = new Provider('http://127.0.0.1:4000/graphql');

    const response = await provider.sendTransaction({
      type: TransactionType.Script,
      gasPrice: 0n,
      gasLimit: 1000000n,
      bytePrice: 0n,
      script:
        /*
          Opcode::ADDI(0x10, REG_ZERO, 0xCA)
          Opcode::ADDI(0x11, REG_ZERO, 0xBA)
          Opcode::LOG(0x10, 0x11, REG_ZERO, REG_ZERO)
          Opcode::RET(REG_ONE)
        */
        arrayify('0x504000ca504400ba3341100024040000'),
      scriptData: randomBytes(32),
    });

    const result = await response.wait();

    expect(result.receipts).toEqual([
      {
        type: ReceiptType.Log,
        id: ZeroBytes32,
        val0: BigInt(202),
        val1: BigInt(186),
        val2: BigInt(0),
        val3: BigInt(0),
        pc: BigInt(0x2878),
        is: BigInt(0x2870),
      },
      {
        type: ReceiptType.Return,
        id: ZeroBytes32,
        val: BigInt(1),
        pc: BigInt(0x287c),
        is: BigInt(0x2870),
      },
      {
        type: ReceiptType.ScriptResult,
        result: BigInt(0),
        gasUsed: BigInt(0x2c),
      },
    ]);
  });

  it('can manage session', async () => {
    const provider = new Provider('http://127.0.0.1:4000/graphql');

    const { startSession: id } = await provider.operations.startSession();

    const { reset: resetSuccess } = await provider.operations.reset({ sessionId: id });
    expect(resetSuccess).toEqual(true);

    const { endSession: endSessionSuccess } = await provider.operations.endSession({
      sessionId: id,
    });
    expect(endSessionSuccess).toEqual(true);
  });
});
Example #16
Source File: integration.test.ts    From hubble-contracts with MIT License 4 votes vote down vote up
/**
 * This integration test ensures that
 * 1. A node in packing/proposal mode syncs from L1 and then packs deposits and transfers.
 * 2. A node in syncing mode successfully updates to match the first node's state from L1 events.
 *
 * When a watching/contesting node is implemented,
 * this might be worth moving over to more of a
 * E2E (end-to-end) style test in which all 3 node
 * types run independantly on an initial data set
 * and reach a correct end state.
 */
describe("Client Integration", function() {
    before(async function() {
        await del("./leveldb/*");
    });

    after(async function() {
        await del("./leveldb/*");
    });

    it("run", async function() {
        await mcl.init();
        const [signer] = await ethers.getSigners();
        const provider = signer.provider as providers.JsonRpcProvider;
        const [genesisEth1Block, network] = await Promise.all([
            provider.getBlockNumber(),
            provider.getNetwork()
        ]);

        await deployKeyless(signer, false);

        const storageSyncer = await storageManagerFactory();
        const storagePacker = await storageManagerFactory();

        // Ensure initial states match
        assert.equal(storageSyncer.state.root, storagePacker.state.root);
        assert.equal(storageSyncer.pubkey.root, storagePacker.pubkey.root);

        // Deploy contracts
        const parameters = PRODUCTION_PARAMS;
        parameters.USE_BURN_AUCTION = false;
        parameters.GENESIS_STATE_ROOT = storagePacker.state.root;

        const contracts = await deployAll(signer, parameters);

        // Setup and register custom tokens
        const customTokens = await Promise.all([
            new CustomToken__factory(signer).deploy("Hubble", "HUB"),
            new CustomToken__factory(signer).deploy("Telescope", "TLSC")
        ]);
        for (const token of customTokens) {
            await contracts.tokenRegistry.registerToken(token.address);
        }
        const allTokens = [contracts.exampleToken, ...customTokens];

        // Setup users/accounts
        const numUsers = 32;
        const initialBalance = CommonToken.fromHumanValue("100.12");
        const group = Group.new({ n: numUsers });

        const domainSeparator = await contracts.rollup.domainSeparator();
        group.setupSigners(arrayify(domainSeparator));

        const pubkeyBatch: any = [];
        let batchBasePubkeyID = 2 ** (parameters.MAX_DEPTH - 1);
        for (const user of group.userIterator()) {
            // Pubkey
            if (user.pubkeyID < 16) {
                await contracts.blsAccountRegistry.register(user.pubkey);
            } else {
                user.changePubkeyID(batchBasePubkeyID++);
                pubkeyBatch.push(user.pubkey);
            }
        }
        await contracts.blsAccountRegistry.registerBatch(pubkeyBatch);

        let numDeposits = 0;
        let currentStateID = 0;
        // Split users in subtree sized chunks
        const subtreeSize = 2 ** parameters.MAX_DEPOSIT_SUBTREE_DEPTH;
        for (const subGroup of group.groupInterator(subtreeSize)) {
            // Setup L1 for syncer & packer
            for (const user of subGroup.userIterator()) {
                // Clear out default stateIDs
                user.clearStateIDs();
                // Deposit tokens
                for (let tokenID = 0; tokenID < allTokens.length; tokenID++) {
                    const token = allTokens[tokenID];
                    // Approve token transfer
                    await token.approve(
                        contracts.depositManager.address,
                        initialBalance.l1Value
                    );
                    // Queue deposit
                    await contracts.depositManager.depositFor(
                        user.pubkeyID,
                        initialBalance.l1Value,
                        tokenID
                    );
                    user.addStateID(tokenID, currentStateID++);
                    numDeposits++;
                }
            }
        }

        // Setup a pool which simulates random token transfers
        // Use first user as fee receiver
        const firstUser = group.getUser(0);
        const numTransferBatches = 10;
        const maxTransfers =
            numTransferBatches * parameters.MAX_TXS_PER_COMMIT ** 2;
        const simPool = new SimulatorPool({
            group,
            storage: storagePacker,
            feeReceivers: allTokens.map((_token, tokenID) => ({
                tokenID,
                stateID: firstUser.getStateID(tokenID)
            })),
            maxTransfers
        });

        const genesis = await Genesis.fromContracts(
            contracts,
            parameters,
            genesisEth1Block,
            network.chainId
        );
        const apiSyncer = CoreAPI.new(storageSyncer, genesis, provider, signer);
        const apiPacker = CoreAPI.new(storagePacker, genesis, provider, signer);

        // Simulate packing node running
        const packerSyncer = new SyncerService(apiPacker);
        const packer = new Packer(apiPacker, simPool);
        await packerSyncer.initialSync();
        await packer.runOnce();

        // Simulate syncing node running
        const syncer = new SyncerService(apiSyncer);
        await syncer.initialSync();

        // Confirm final states match
        assert.equal(storageSyncer.state.root, storagePacker.state.root);
        assert.equal(storageSyncer.pubkey.root, storagePacker.pubkey.root);

        // Confirm storage has correct counts
        const numGenesisBatches = 1;
        const numDepositBatches = numDeposits / subtreeSize;
        const numBatches =
            numGenesisBatches + numDepositBatches + numTransferBatches;
        assert.equal(storageSyncer.batches.count(), numBatches);
        assert.equal(storagePacker.batches.count(), numBatches);

        assert.equal(await storageSyncer.transactions.count(), maxTransfers);
        assert.equal(await storagePacker.transactions.count(), maxTransfers);
    }).timeout(900000);
});