Blockchain explorer for Ethereum based network and a tool for inspecting and analyzing EVM based blockchains.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
blockscout/apps/explorer/test/support/factory.ex

303 lines
7.7 KiB

defmodule Explorer.Factory do
use ExMachina.Ecto, repo: Explorer.Repo
require Ecto.Query
import Ecto.Query
import Kernel, except: [+: 2]
alias Explorer.Chain.Block.{Range, Reward}
alias Explorer.Chain.{
Address,
Block,
Data,
Hash,
InternalTransaction,
Log,
Transaction
}
alias Explorer.Market.MarketHistory
alias Explorer.Repo
@dialyzer {:nowarn_function, fields_for: 1}
def address_factory do
%Address{
hash: address_hash()
}
end
def contract_code_info do
%{
bytecode:
"0x6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820f65a3adc1cfb055013d1dc37d0fe98676e2a5963677fa7541a10386d163446680029",
name: "SimpleStorage",
source_code: """
pragma solidity ^0.4.24;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public constant returns (uint) {
return storedData;
}
}
""",
version: "v0.4.24+commit.e67f0147",
optimized: false
}
end
def address_hash do
{:ok, address_hash} =
"address_hash"
|> sequence(& &1)
|> Hash.Address.cast()
address_hash
end
def block_factory do
%Block{
number: sequence("block_number", & &1),
hash: block_hash(),
parent_hash: block_hash(),
nonce: sequence("block_nonce", & &1),
miner: build(:address),
difficulty: Enum.random(1..100_000),
total_difficulty: Enum.random(1..100_000),
size: Enum.random(1..100_000),
gas_limit: Enum.random(1..100_000),
gas_used: Enum.random(1..100_000),
timestamp: DateTime.utc_now()
}
end
def block_hash do
{:ok, block_hash} =
"block_hash"
|> sequence(& &1)
|> Hash.Full.cast()
block_hash
end
def with_block(%Transaction{index: nil} = transaction) do
with_block(transaction, insert(:block))
end
def with_block(transactions) when is_list(transactions) do
block = insert(:block)
with_block(transactions, block)
end
def with_block(%Transaction{} = transaction, %Block{} = block) do
with_block(transaction, block, [])
end
def with_block(transactions, %Block{} = block) when is_list(transactions) do
Enum.map(transactions, &with_block(&1, block))
end
def with_block(%Transaction{index: nil} = transaction, collated_params) when is_list(collated_params) do
block = insert(:block)
with_block(transaction, block, collated_params)
end
def with_block(
%Transaction{index: nil} = transaction,
%Block{hash: block_hash, number: block_number},
collated_params
)
when is_list(collated_params) do
next_transaction_index = block_hash_to_next_transaction_index(block_hash)
cumulative_gas_used = collated_params[:cumulative_gas_used] || Enum.random(21_000..100_000)
gas_used = collated_params[:gas_used] || Enum.random(21_000..100_000)
internal_transactions_indexed_at = collated_params[:internal_transactions_indexed_at]
status = collated_params[:status] || Enum.random(0..1)
transaction
|> Transaction.changeset(%{
block_hash: block_hash,
block_number: block_number,
cumulative_gas_used: cumulative_gas_used,
gas_used: gas_used,
index: next_transaction_index,
internal_transactions_indexed_at: internal_transactions_indexed_at,
status: status
})
|> Repo.update!()
|> Repo.preload(:block)
end
def data(sequence_name) do
unpadded =
sequence_name
|> sequence(& &1)
|> Integer.to_string(16)
unpadded_length = String.length(unpadded)
padded =
case rem(unpadded_length, 2) do
0 -> unpadded
1 -> "0" <> unpadded
end
{:ok, data} = Data.cast("0x#{padded}")
data
end
def internal_transaction_factory() do
gas = Enum.random(21_000..100_000)
gas_used = Enum.random(0..gas)
%InternalTransaction{
from_address: build(:address),
to_address: build(:address),
call_type: :delegatecall,
gas: gas,
gas_used: gas_used,
output: %Data{bytes: <<1>>},
# caller MUST suppy `index`
trace_address: [],
# caller MUST supply `transaction` because it can't be built lazily to allow overrides without creating an extra
# transaction
type: :call,
value: sequence("internal_transaction_value", &Decimal.new(&1))
}
end
def internal_transaction_create_factory() do
gas = Enum.random(21_000..100_000)
gas_used = Enum.random(0..gas)
contract_code = Map.fetch!(contract_code_info(), :bytecode)
%InternalTransaction{
created_contract_code: contract_code,
created_contract_address: build(:address, contract_code: contract_code),
from_address: build(:address),
gas: gas,
gas_used: gas_used,
# caller MUST suppy `index`
init: data(:internal_transaction_init),
trace_address: [],
# caller MUST supply `transaction` because it can't be built lazily to allow overrides without creating an extra
# transaction
type: :create,
value: sequence("internal_transaction_value", &Decimal.new(&1))
}
end
def log_factory do
%Log{
address: build(:address),
data: data(:log_data),
first_topic: nil,
fourth_topic: nil,
index: 0,
second_topic: nil,
third_topic: nil,
transaction: build(:transaction),
type: sequence("0x")
}
end
def market_history_factory do
%MarketHistory{
closing_price: price(),
opening_price: price(),
date: Date.utc_today()
}
end
def block_reward_factory do
# Generate ranges like 1 - 10,000; 10,001 - 20,000, 20,001 - 30,000; etc
x = sequence("block_range", & &1)
lower = x * Kernel.+(10_000, 1)
upper = Kernel.+(lower, 9_999)
wei_per_ether = Decimal.new(1_000_000_000_000_000_000)
reward_multiplier =
1..5
|> Enum.random()
|> Decimal.new()
reward = Decimal.mult(reward_multiplier, wei_per_ether)
%Reward{
block_range: %Range{from: lower, to: upper},
reward: reward
}
end
def transaction_factory do
%Transaction{
from_address: build(:address),
gas: Enum.random(21_000..100_000),
gas_price: Enum.random(10..99) * 1_000_000_00,
hash: transaction_hash(),
input: transaction_input(),
nonce: Enum.random(1..1_000),
r: sequence(:transaction_r, & &1),
s: sequence(:transaction_s, & &1),
to_address: build(:address),
v: Enum.random(27..30),
value: Enum.random(1..100_000)
}
end
def transaction_hash do
{:ok, transaction_hash} =
"transaction_hash"
|> sequence(& &1)
|> Hash.Full.cast()
transaction_hash
end
def transaction_input do
data(:transaction_input)
end
defmacrop left + right do
quote do
fragment("? + ?", unquote(left), unquote(right))
end
end
defmacrop coalesce(left, right) do
quote do
fragment("coalesce(?, ?)", unquote(left), unquote(right))
end
end
defp block_hash_to_next_transaction_index(block_hash) do
import Kernel, except: [+: 2]
Repo.one!(
from(
transaction in Transaction,
select: coalesce(max(transaction.index), -1) + 1,
where: transaction.block_hash == ^block_hash
)
)
end
defp price do
1..10_000
|> Enum.random()
|> Decimal.new()
|> Decimal.div(Decimal.new(100))
end
end