From 2b3045bcb5a3df68465b16ccefa960b6ac1e1d91 Mon Sep 17 00:00:00 2001 From: nikitosing Date: Sun, 7 Nov 2021 13:59:01 +0300 Subject: [PATCH] Fix bugs and improve contract interactions --- CHANGELOG.md | 1 + .../js/lib/smart_contract/common_helpers.js | 38 ++++++----- .../controllers/smart_contract_controller.ex | 41 +++++++++++- .../views/smart_contract_view_test.exs | 65 +++++++++++++++++++ .../lib/ethereum_jsonrpc/contract.ex | 10 ++- .../lib/ethereum_jsonrpc/encoder.ex | 34 +++++----- .../lib/explorer/smart_contract/reader.ex | 7 +- mix.lock | 4 +- 8 files changed, 161 insertions(+), 39 deletions(-) create mode 100644 apps/block_scout_web/test/block_scout_web/views/smart_contract_view_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index e61219cae8..050942f5ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - [#4945](https://github.com/blockscout/blockscout/pull/4945) - Fix `Verify & Publish` button link - [#4938](https://github.com/blockscout/blockscout/pull/4938) - Fix displaying of nested arrays for contracts read - [#4888](https://github.com/blockscout/blockscout/pull/4888) - Fix fetch_top_tokens method: add nulls last for token holders desc order +- [#4867](https://github.com/blockscout/blockscout/pull/4867) - Fix bug in quering contracts method and improve contracts interactions ### Chore - [#4998](https://github.com/blockscout/blockscout/pull/4998) - API endpoints logger diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js b/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js index c0b1a3c485..0f9364738e 100644 --- a/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js +++ b/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js @@ -36,22 +36,26 @@ export function prepareMethodArgs ($functionInputs, inputs) { if (sanitizedInputValue === '' || sanitizedInputValue === '[]') { return [[]] } else { - if (sanitizedInputValue.startsWith('[') && sanitizedInputValue.endsWith(']')) { - sanitizedInputValue = sanitizedInputValue.substring(1, sanitizedInputValue.length - 1) - } - const inputValueElements = sanitizedInputValue.split(',') - const sanitizedInputValueElements = inputValueElements.map(elementValue => { - const elementInputType = inputType.split('[')[0] - - let sanitizedElementValue = replaceDoubleQuotes(elementValue, elementInputType) - sanitizedElementValue = replaceSpaces(sanitizedElementValue, elementInputType) - - if (isBoolInputType(elementInputType)) { - sanitizedElementValue = convertToBool(elementValue) + if (isArrayOfTuple(inputType)) { + return [JSON.parse(sanitizedInputValue)] + } else { + if (sanitizedInputValue.startsWith('[') && sanitizedInputValue.endsWith(']')) { + sanitizedInputValue = sanitizedInputValue.substring(1, sanitizedInputValue.length - 1) } - return sanitizedElementValue - }) - return [sanitizedInputValueElements] + const inputValueElements = sanitizedInputValue.split(',') + const sanitizedInputValueElements = inputValueElements.map(elementValue => { + const elementInputType = inputType.split('[')[0] + + var sanitizedElementValue = replaceDoubleQuotes(elementValue, elementInputType) + sanitizedElementValue = replaceSpaces(sanitizedElementValue, elementInputType) + + if (isBoolInputType(elementInputType)) { + sanitizedElementValue = convertToBool(elementValue) + } + return sanitizedElementValue + }) + return [sanitizedInputValueElements] + } } } else if (isBoolInputType(inputType)) { return convertToBool(sanitizedInputValue) @@ -221,6 +225,10 @@ function isTupleInputType (inputType) { return inputType && inputType.includes('tuple') && !isArrayInputType(inputType) } +function isArrayOfTuple (inputType) { + return inputType && inputType.includes('tuple') && isArrayInputType(inputType) +} + function isAddressInputType (inputType) { return inputType && inputType.includes('address') && !isArrayInputType(inputType) } diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex index 9dbb941476..6c3fb9843e 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex @@ -105,11 +105,14 @@ defmodule BlockScoutWeb.SmartContractController do {:ok, _address} <- Chain.find_contract_address(address_hash, address_options, true) do contract_type = if params["type"] == "proxy", do: :proxy, else: :regular + # we should convert: %{"0" => _, "1" => _} to [_, _] + args = params["args"] |> convert_map_to_array() + %{output: outputs, names: names} = if params["from"] do Reader.query_function_with_names( address_hash, - %{method_id: params["method_id"], args: params["args"]}, + %{method_id: params["method_id"], args: args}, contract_type, params["function_name"], params["from"] @@ -117,7 +120,7 @@ defmodule BlockScoutWeb.SmartContractController do else Reader.query_function_with_names( address_hash, - %{method_id: params["method_id"], args: params["args"]}, + %{method_id: params["method_id"], args: args}, contract_type, params["function_name"] ) @@ -145,4 +148,38 @@ defmodule BlockScoutWeb.SmartContractController do not_found(conn) end end + + defp convert_map_to_array(map) do + if is_turned_out_array?(map) do + map |> Map.values() |> try_to_map_elements() + else + try_to_map_elements(map) + end + end + + defp try_to_map_elements(values) do + if Enumerable.impl_for(values) do + Enum.map(values, &convert_map_to_array/1) + else + values + end + end + + defp is_turned_out_array?(map) when is_map(map), do: Enum.all?(Map.keys(map), &is_integer?/1) + + defp is_turned_out_array?(_), do: false + + defp is_integer?(string) when is_binary(string) do + case string |> String.trim() |> Integer.parse() do + {_, ""} -> + true + + _ -> + false + end + end + + defp is_integer?(integer) when is_integer(integer), do: true + + defp is_integer?(_), do: false end diff --git a/apps/block_scout_web/test/block_scout_web/views/smart_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/smart_contract_view_test.exs new file mode 100644 index 0000000000..b2d750aff3 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/smart_contract_view_test.exs @@ -0,0 +1,65 @@ +defmodule BlockScoutWeb.SmartContractViewTest do + use ExUnit.Case + + alias BlockScoutWeb.SmartContractView + + doctest BlockScoutWeb.SmartContractView, import: true + + describe "values_with_type/1" do + test "complex data type case" do + value = + {<<156, 209, 70, 119, 249, 170, 85, 105, 179, 187, 179, 81, 252, 214, 125, 17, 21, 170, 86, 58, 225, 98, 66, + 118, 211, 212, 230, 127, 179, 214, 249, 38>>, 23_183_417, true, + [ + {<<164, 118, 64, 69, 133, 31, 23, 170, 96, 182, 200, 232, 182, 32, 114, 190, 169, 83, 133, 33>>, + [ + <<15, 103, 152, 165, 96, 121, 58, 84, 195, 188, 254, 134, 169, 60, 222, 30, 115, 8, 125, 148, 76, 14, 162, + 5, 68, 19, 125, 65, 33, 57, 104, 133>>, + <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 61, 111, 131, 12, 226, 99, 202, 233, 135, 25, 57, 130, 25, 44, + 217, 144, 68, 43, 83>> + ], + <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 178, 96, 212, 241, 78, 0, 0>>}, + {<<164, 118, 64, 69, 133, 31, 23, 170, 96, 182, 200, 232, 182, 32, 114, 190, 169, 83, 133, 33>>, + [ + <<221, 242, 82, 173, 27, 226, 200, 155, 105, 194, 176, 104, 252, 55, 141, 170, 149, 43, 167, 241, 99, 196, + 161, 22, 40, 245, 90, 77, 245, 35, 179, 239>>, + <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>, + <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 61, 111, 131, 12, 226, 99, 202, 233, 135, 25, 57, 130, 25, 44, + 217, 144, 68, 43, 83>> + ], + <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 178, 96, 212, 241, 78, 0, 0>>}, + {<<166, 139, 214, 89, 169, 22, 127, 61, 60, 1, 186, 151, 118, 161, 32, 141, 174, 143, 0, 59>>, + [ + <<47, 154, 96, 152, 212, 80, 58, 18, 119, 121, 186, 151, 95, 95, 107, 4, 248, 66, 54, 43, 24, 9, 243, 70, + 152, 158, 154, 188, 11, 77, 237, 182>>, + <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 61, 111, 131, 12, 226, 99, 202, 233, 135, 25, 57, 130, 25, 44, + 217, 144, 68, 43, 83>>, + <<0, 5, 0, 0, 36, 155, 252, 47, 60, 200, 214, 143, 107, 107, 247, 35, 14, 160, 168, 237, 133, 61, 231, 49, + 0, 0, 0, 0, 0, 0, 2, 79>> + ], + <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 178, 96, 212, 241, 78, 0, 0>>}, + {<<254, 68, 107, 239, 29, 191, 122, 254, 36, 232, 30, 5, 188, 139, 39, 28, 27, 169, 165, 96>>, + [ + <<39, 51, 62, 219, 139, 220, 212, 10, 10, 233, 68, 251, 18, 27, 94, 45, 98, 234, 120, 38, 131, 148, 102, + 84, 160, 245, 230, 7, 169, 8, 213, 120>>, + <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 95, 197, 45, 138, 86, 59, 47, 24, 28, 106, 82, 125, 66, 46, 21, + 146, 201, 236, 250>>, + <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166, 139, 214, 89, 169, 22, 127, 61, 60, 1, 186, 151, 118, 161, 32, + 141, 174, 143, 0, 59>>, + <<0, 5, 0, 0, 36, 155, 252, 47, 60, 200, 214, 143, 107, 107, 247, 35, 14, 160, 168, 237, 133, 61, 231, 49, + 0, 0, 0, 0, 0, 0, 2, 79>> + ], <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1>>} + ]} + + type = "tuple[bytes32,uint256,bool,tuple[address,bytes32[],bytes][]]" + + names = [ + "struct AsyncCallTest.Receipt", + ["txHash", "blockNumber", "status", ["logs", ["from", "topics", "data"]]] + ] + + assert SmartContractView.values_with_type(value, type, names, 0) == + "
struct AsyncCallTest.Receipt (tuple[bytes32,uint256,bool,tuple[address,bytes32[],bytes][]]) :
txHash (bytes32) : 0x9cd14677f9aa5569b3bbb351fcd67d1115aa563ae1624276d3d4e67fb3d6f926
blockNumber (uint256) : 23183417
status (bool) : true
logs (tuple[address,bytes32[],bytes][]) : [(\n
from (address) : 0xa4764045851f17aa60b6c8e8b62072bea9538521
topics (bytes32[]) : [0x0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d4121396885, 0x000000000000000000000000bf3d6f830ce263cae987193982192cd990442b53]
data (bytes) : 0x000000000000000000000000000000000000000000000000aab260d4f14e0000
),\n(
from (address) : 0xa4764045851f17aa60b6c8e8b62072bea9538521
topics (bytes32[]) : [0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000bf3d6f830ce263cae987193982192cd990442b53]
data (bytes) : 0x000000000000000000000000000000000000000000000000aab260d4f14e0000
),\n(
from (address) : 0xa68bd659a9167f3d3c01ba9776a1208dae8f003b
topics (bytes32[]) : [0x2f9a6098d4503a127779ba975f5f6b04f842362b1809f346989e9abc0b4dedb6, 0x000000000000000000000000bf3d6f830ce263cae987193982192cd990442b53, 0x00050000249bfc2f3cc8d68f6b6bf7230ea0a8ed853de731000000000000024f]
data (bytes) : 0x000000000000000000000000000000000000000000000000aab260d4f14e0000
),\n(
from (address) : 0xfe446bef1dbf7afe24e81e05bc8b271c1ba9a560
topics (bytes32[]) : [0x27333edb8bdcd40a0ae944fb121b5e2d62ea782683946654a0f5e607a908d578, 0x0000000000000000000000002a5fc52d8a563b2f181c6a527d422e1592c9ecfa, 0x000000000000000000000000a68bd659a9167f3d3c01ba9776a1208dae8f003b, 0x00050000249bfc2f3cc8d68f6b6bf7230ea0a8ed853de731000000000000024f]
data (bytes) : 0x0000000000000000000000000000000000000000000000000000000000000001
)]
" + end + end +end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex index 1fbf5c9f59..4d803a9f6b 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex @@ -109,16 +109,21 @@ defmodule EthereumJSONRPC.Contract do defp convert_int_string_to_array(arg) when is_nil(arg), do: true + defp convert_int_string_to_array(arg) when is_list(arg), do: convert_int_string_to_array_inner(arg) + defp convert_int_string_to_array(arg) when not is_nil(arg) do cond do String.starts_with?(arg, "[") && String.ends_with?(arg, "]") -> arg |> String.trim_leading("[") |> String.trim_trailing("]") + |> String.split(",") |> convert_int_string_to_array_inner() arg !== "" -> - convert_int_string_to_array_inner(arg) + arg + |> String.split(",") + |> convert_int_string_to_array_inner() true -> [] @@ -127,7 +132,6 @@ defmodule EthereumJSONRPC.Contract do defp convert_int_string_to_array_inner(arg) do arg - |> String.split(",") |> Enum.map(fn el -> {int, _} = Integer.parse(el) int @@ -136,6 +140,8 @@ defmodule EthereumJSONRPC.Contract do defp convert_string_to_array(arg) when is_nil(arg), do: true + defp convert_string_to_array(arg) when is_list(arg), do: arg + defp convert_string_to_array(arg) when not is_nil(arg) do cond do String.starts_with?(arg, "[") && String.ends_with?(arg, "]") -> diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex index 0f6ab1e4db..eae2cceb1b 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex @@ -23,23 +23,25 @@ defmodule EthereumJSONRPC.Encoder do "0x" <> encoded_args end - defp parse_args(args) do + defp parse_args(args) when is_list(args) do args - |> Enum.map(fn - <<"0x", hexadecimal_digits::binary>> -> - Base.decode16!(hexadecimal_digits, case: :mixed) - - item -> - if is_list(item) do - item - |> Enum.map(fn el -> - <<"0x", hexadecimal_digits::binary>> = el - Base.decode16!(hexadecimal_digits, case: :mixed) - end) - else - item - end - end) + |> Enum.map(&parse_args/1) + end + + defp parse_args(<<"0x", hexadecimal_digits::binary>>), do: Base.decode16!(hexadecimal_digits, case: :mixed) + + defp parse_args(<>), do: try_to_decode(hexadecimal_digits) + + defp parse_args(arg), do: arg + + defp try_to_decode(hexadecimal_digits) do + case Base.decode16(hexadecimal_digits, case: :mixed) do + {:ok, decoded_value} -> + decoded_value + + _ -> + hexadecimal_digits + end end @doc """ diff --git a/apps/explorer/lib/explorer/smart_contract/reader.ex b/apps/explorer/lib/explorer/smart_contract/reader.ex index 28fd2bc5ce..eb54a8dec2 100644 --- a/apps/explorer/lib/explorer/smart_contract/reader.ex +++ b/apps/explorer/lib/explorer/smart_contract/reader.ex @@ -566,8 +566,7 @@ defmodule Explorer.SmartContract.Reader do |> Tuple.to_list() |> Enum.map(fn value -> if is_list(value) do - value - |> Enum.join("") + parse_item(value) else hex = value @@ -578,6 +577,10 @@ defmodule Explorer.SmartContract.Reader do end) end + defp parse_item(items) when is_list(items) do + Enum.map(items, &parse_item/1) + end + defp parse_item(item) do response = Integer.parse(item) diff --git a/mix.lock b/mix.lock index f339afc6ad..7495a2511b 100644 --- a/mix.lock +++ b/mix.lock @@ -35,7 +35,7 @@ "ecto_sql": {:hex, :ecto_sql, "3.7.0", "2fcaad4ab0c8d76a5afbef078162806adbe709c04160aca58400d5cbbe8eeac6", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a26135dfa1d99bf87a928c464cfa25bba6535a4fe761eefa56077a4febc60f70"}, "elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_abi": {:hex, :ex_abi, "0.5.7", "bb9695c1776a84ee4760794207f3c67c0412c28ba7c556790a6e887569d0667c", [:mix], [{:ex_keccak, "~> 0.2.2", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "64be72bcbab2e8d2c2d40df19c38d2bf4c29a3b2813e61dbf3d73e3f39004beb"}, + "ex_abi": {:hex, :ex_abi, "0.5.8", "3bf7320188e57983c206f6eacaf4dae604aea881d70040b666afec626b6a4f3b", [:mix], [{:ex_keccak, "~> 0.2.2", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "3ff6b7b70984118fca86ce8328fe672186a9a904f04696c579d4a121c9d759fc"}, "ex_cldr": {:hex, :ex_cldr, "2.19.1", "6bd81c826202d08420ebf7174306d277a8c4093c3c32c188ac9a636927f27c7e", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:cldr_utils, "~> 2.12", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "5541261dd2915b7c9fb1408b1cfe9075657515e4b348ccb921e45e149dea6b11"}, "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.8.0", "b2ecc94e9fa4b8ec07614830f4d6e811e5df5e7679c6d2be92f4fe4f31184913", [:mix], [{:ex_cldr, "~> 2.18", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "a39780667b73bfd3d2bd08e61981bca23a97912b86f3236042850ecb062f48eb"}, "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.7.0", "86264f509ada404afc9d469faf58ad78a9cbba4b728776eb218ee1bf9a9396a2", [:mix], [{:ex_cldr_numbers, "~> 2.16", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ec9f55af45aa628930900e84d09bab50ee61841ad974aeb8fd51f627a9685353"}, @@ -107,7 +107,7 @@ "quantile_estimator": {:hex, :quantile_estimator, "0.2.1", "ef50a361f11b5f26b5f16d0696e46a9e4661756492c981f7b2229ef42ff1cd15", [:rebar3], [], "hexpm", "282a8a323ca2a845c9e6f787d166348f776c1d4a41ede63046d72d422e3da946"}, "que": {:hex, :que, "0.10.1", "788ed0ec92ed69bdf9cfb29bf41a94ca6355b8d44959bd0669cf706e557ac891", [:mix], [{:ex_utils, "~> 0.1.6", [hex: :ex_utils, repo: "hexpm", optional: false]}, {:memento, "~> 0.3.0", [hex: :memento, repo: "hexpm", optional: false]}], "hexpm", "a737b365253e75dbd24b2d51acc1d851049e87baae08cd0c94e2bc5cd65088d5"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, - "rustler": {:hex, :rustler, "0.22.0", "e2930f9d6933e910f87526bb0a7f904e32b62a7e838a3ca4a884ee7fdfb957ed", [:mix], [{:toml, "~> 0.5.2", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "01f5989dd511ebec09be481e07d3c59773d5373c5061e09d3ebc3ef61811b49d"}, + "rustler": {:hex, :rustler, "0.22.2", "f92d6dba71bef6fe5f0d955649cb071127adc92f32a78890e8fa9939e59a1b41", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.5.2", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "56b129141e86d60a2d670af9a2b55a9071e10933ef593034565af77e84655118"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "spandex": {:hex, :spandex, "3.0.3", "91aa318f3de696bb4d931adf65f7ebdbe5df25cccce1fe8fd376a44c46bcf69b", [:mix], [{:decorator, "~> 1.2", [hex: :decorator, repo: "hexpm", optional: true]}, {:optimal, "~> 0.3.3", [hex: :optimal, repo: "hexpm", optional: false]}, {:plug, ">= 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e3e6c319d0ab478ddc9a39102a727a410c962b4d51c0932c72279b86d3b17044"}, "spandex_datadog": {:hex, :spandex_datadog, "1.1.0", "8c84e2f6c4067edc2e920dd79242f7bb0d6403652a7e9bc42109007f76b9be25", [:mix], [{:msgpax, "~> 2.2.1", [hex: :msgpax, repo: "hexpm", optional: false]}, {:spandex, "~> 3.0", [hex: :spandex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f4c20d3e601cad869705d9789f17a9242f245ce0bf2579fc835e96a6834663e2"},