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.
307 lines
7.2 KiB
307 lines
7.2 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 address_hash do
|
|
{:ok, address_hash} =
|
|
"address_hash"
|
|
|> sequence(& &1)
|
|
|> Hash.Truncated.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_hash: insert(:address).hash,
|
|
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)
|
|
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,
|
|
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
|
|
type = internal_transaction_type()
|
|
|
|
internal_transaction_factory(type)
|
|
end
|
|
|
|
def internal_transaction_create_factory do
|
|
internal_transaction_factory(:create)
|
|
end
|
|
|
|
def internal_transaction_call_factory do
|
|
internal_transaction_factory(:call)
|
|
end
|
|
|
|
# TODO add reward and suicide
|
|
def internal_transaction_type do
|
|
Enum.random(~w(call create)a)
|
|
end
|
|
|
|
def log_factory do
|
|
%Log{
|
|
address_hash: insert(:address).hash,
|
|
data: data(:log_data),
|
|
first_topic: nil,
|
|
fourth_topic: nil,
|
|
index: 0,
|
|
second_topic: nil,
|
|
third_topic: nil,
|
|
transaction_hash: insert(:transaction).hash,
|
|
type: sequence("0x")
|
|
}
|
|
end
|
|
|
|
def public_key do
|
|
data(:public_key)
|
|
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_hash: insert(:address).hash,
|
|
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),
|
|
public_key: public_key(),
|
|
r: sequence(:transaction_r, & &1),
|
|
s: sequence(:transaction_s, & &1),
|
|
standard_v: Enum.random(0..3),
|
|
to_address_hash: insert(:address).hash,
|
|
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 internal_transaction_factory(:call = type) do
|
|
gas = Enum.random(21_000..100_000)
|
|
gas_used = Enum.random(0..gas)
|
|
|
|
transaction =
|
|
:transaction
|
|
|> insert()
|
|
|> with_block()
|
|
|
|
%InternalTransaction{
|
|
from_address_hash: insert(:address).hash,
|
|
to_address_hash: insert(:address).hash,
|
|
call_type: :delegatecall,
|
|
gas: gas,
|
|
gas_used: gas_used,
|
|
output: %Data{bytes: <<1>>},
|
|
# caller MUST suppy `index`
|
|
trace_address: [],
|
|
transaction_hash: transaction.hash,
|
|
type: type,
|
|
value: sequence("internal_transaction_value", &Decimal.new(&1))
|
|
}
|
|
end
|
|
|
|
defp internal_transaction_factory(:create = type) do
|
|
gas = Enum.random(21_000..100_000)
|
|
gas_used = Enum.random(0..gas)
|
|
|
|
transaction =
|
|
:transaction
|
|
|> insert()
|
|
|> with_block()
|
|
|
|
%InternalTransaction{
|
|
created_contract_code: data(:internal_transaction_created_contract_code),
|
|
created_contract_address_hash: insert(:address).hash,
|
|
from_address_hash: insert(:address).hash,
|
|
gas: gas,
|
|
gas_used: gas_used,
|
|
# caller MUST suppy `index`
|
|
init: data(:internal_transaction_init),
|
|
trace_address: [],
|
|
transaction_hash: transaction.hash,
|
|
type: type,
|
|
value: sequence("internal_transaction_value", &Decimal.new(&1))
|
|
}
|
|
end
|
|
|
|
defp price do
|
|
1..10_000
|
|
|> Enum.random()
|
|
|> Decimal.new()
|
|
|> Decimal.div(Decimal.new(100))
|
|
end
|
|
end
|
|
|