Merge pull request #2036 from poanetwork/stakes-tables
New tables for staking pools and delegatorspull/2086/head
commit
191e3a67c7
@ -0,0 +1,91 @@ |
|||||||
|
defmodule Explorer.Chain.Import.Runner.StakingPoolsDelegators do |
||||||
|
@moduledoc """ |
||||||
|
Bulk imports delegators to StakingPoolsDelegator tabe. |
||||||
|
""" |
||||||
|
|
||||||
|
require Ecto.Query |
||||||
|
|
||||||
|
alias Ecto.{Changeset, Multi, Repo} |
||||||
|
alias Explorer.Chain.{Import, StakingPoolsDelegator} |
||||||
|
|
||||||
|
import Ecto.Query, only: [from: 2] |
||||||
|
|
||||||
|
@behaviour Import.Runner |
||||||
|
|
||||||
|
# milliseconds |
||||||
|
@timeout 60_000 |
||||||
|
|
||||||
|
@type imported :: [StakingPoolsDelegator.t()] |
||||||
|
|
||||||
|
@impl Import.Runner |
||||||
|
def ecto_schema_module, do: StakingPoolsDelegator |
||||||
|
|
||||||
|
@impl Import.Runner |
||||||
|
def option_key, do: :staking_pools_delegators |
||||||
|
|
||||||
|
@impl Import.Runner |
||||||
|
def imported_table_row do |
||||||
|
%{ |
||||||
|
value_type: "[#{ecto_schema_module()}.t()]", |
||||||
|
value_description: "List of `t:#{ecto_schema_module()}.t/0`s" |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
@impl Import.Runner |
||||||
|
def run(multi, changes_list, %{timestamps: timestamps} = options) do |
||||||
|
insert_options = |
||||||
|
options |
||||||
|
|> Map.get(option_key(), %{}) |
||||||
|
|> Map.take(~w(on_conflict timeout)a) |
||||||
|
|> Map.put_new(:timeout, @timeout) |
||||||
|
|> Map.put(:timestamps, timestamps) |
||||||
|
|
||||||
|
multi |
||||||
|
|> Multi.run(:insert_staking_pools_delegators, fn repo, _ -> |
||||||
|
insert(repo, changes_list, insert_options) |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
@impl Import.Runner |
||||||
|
def timeout, do: @timeout |
||||||
|
|
||||||
|
@spec insert(Repo.t(), [map()], %{ |
||||||
|
optional(:on_conflict) => Import.Runner.on_conflict(), |
||||||
|
required(:timeout) => timeout, |
||||||
|
required(:timestamps) => Import.timestamps() |
||||||
|
}) :: |
||||||
|
{:ok, [StakingPoolsDelegator.t()]} |
||||||
|
| {:error, [Changeset.t()]} |
||||||
|
defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do |
||||||
|
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) |
||||||
|
|
||||||
|
{:ok, _} = |
||||||
|
Import.insert_changes_list( |
||||||
|
repo, |
||||||
|
changes_list, |
||||||
|
conflict_target: [:pool_address_hash, :delegator_address_hash], |
||||||
|
on_conflict: on_conflict, |
||||||
|
for: StakingPoolsDelegator, |
||||||
|
returning: [:pool_address_hash, :delegator_address_hash], |
||||||
|
timeout: timeout, |
||||||
|
timestamps: timestamps |
||||||
|
) |
||||||
|
end |
||||||
|
|
||||||
|
defp default_on_conflict do |
||||||
|
from( |
||||||
|
delegator in StakingPoolsDelegator, |
||||||
|
update: [ |
||||||
|
set: [ |
||||||
|
stake_amount: fragment("EXCLUDED.stake_amount"), |
||||||
|
ordered_withdraw: fragment("EXCLUDED.ordered_withdraw"), |
||||||
|
max_withdraw_allowed: fragment("EXCLUDED.max_withdraw_allowed"), |
||||||
|
max_ordered_withdraw_allowed: fragment("EXCLUDED.max_ordered_withdraw_allowed"), |
||||||
|
ordered_withdraw_epoch: fragment("EXCLUDED.ordered_withdraw_epoch"), |
||||||
|
inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", delegator.inserted_at), |
||||||
|
updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", delegator.updated_at) |
||||||
|
] |
||||||
|
] |
||||||
|
) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,97 @@ |
|||||||
|
defmodule Explorer.Chain.StakingPool do |
||||||
|
@moduledoc """ |
||||||
|
The representation of staking pool from POSDAO network. |
||||||
|
Staking pools might be candidate or validator. |
||||||
|
""" |
||||||
|
use Ecto.Schema |
||||||
|
import Ecto.Changeset |
||||||
|
|
||||||
|
alias Explorer.Chain.{ |
||||||
|
Address, |
||||||
|
Hash, |
||||||
|
StakingPoolsDelegator, |
||||||
|
Wei |
||||||
|
} |
||||||
|
|
||||||
|
@type t :: %__MODULE__{ |
||||||
|
staking_address_hash: Hash.Address.t(), |
||||||
|
mining_address_hash: Hash.Address.t(), |
||||||
|
banned_until: boolean, |
||||||
|
delegators_count: integer, |
||||||
|
is_active: boolean, |
||||||
|
is_banned: boolean, |
||||||
|
is_validator: boolean, |
||||||
|
likelihood: integer, |
||||||
|
staked_ratio: Decimal.t(), |
||||||
|
self_staked_amount: Wei.t(), |
||||||
|
staked_amount: Wei.t(), |
||||||
|
was_banned_count: integer, |
||||||
|
was_validator_count: integer, |
||||||
|
is_deleted: boolean |
||||||
|
} |
||||||
|
|
||||||
|
@attrs ~w( |
||||||
|
is_active delegators_count staked_amount self_staked_amount is_validator |
||||||
|
was_validator_count is_banned was_banned_count banned_until likelihood |
||||||
|
staked_ratio staking_address_hash mining_address_hash |
||||||
|
)a |
||||||
|
@req_attrs ~w( |
||||||
|
is_active delegators_count staked_amount self_staked_amount is_validator |
||||||
|
was_validator_count is_banned was_banned_count banned_until |
||||||
|
staking_address_hash mining_address_hash |
||||||
|
)a |
||||||
|
|
||||||
|
schema "staking_pools" do |
||||||
|
field(:banned_until, :integer) |
||||||
|
field(:delegators_count, :integer) |
||||||
|
field(:is_active, :boolean, default: false) |
||||||
|
field(:is_banned, :boolean, default: false) |
||||||
|
field(:is_validator, :boolean, default: false) |
||||||
|
field(:likelihood, :decimal) |
||||||
|
field(:staked_ratio, :decimal) |
||||||
|
field(:self_staked_amount, Wei) |
||||||
|
field(:staked_amount, Wei) |
||||||
|
field(:was_banned_count, :integer) |
||||||
|
field(:was_validator_count, :integer) |
||||||
|
field(:is_deleted, :boolean, default: false) |
||||||
|
has_many(:delegators, StakingPoolsDelegator, foreign_key: :pool_address_hash) |
||||||
|
|
||||||
|
belongs_to( |
||||||
|
:staking_address, |
||||||
|
Address, |
||||||
|
foreign_key: :staking_address_hash, |
||||||
|
references: :hash, |
||||||
|
type: Hash.Address |
||||||
|
) |
||||||
|
|
||||||
|
belongs_to( |
||||||
|
:mining_address, |
||||||
|
Address, |
||||||
|
foreign_key: :mining_address_hash, |
||||||
|
references: :hash, |
||||||
|
type: Hash.Address |
||||||
|
) |
||||||
|
|
||||||
|
timestamps(null: false, type: :utc_datetime_usec) |
||||||
|
end |
||||||
|
|
||||||
|
@doc false |
||||||
|
def changeset(staking_pool, attrs) do |
||||||
|
staking_pool |
||||||
|
|> cast(attrs, @attrs) |
||||||
|
|> cast_assoc(:delegators) |
||||||
|
|> validate_required(@req_attrs) |
||||||
|
|> validate_staked_amount() |
||||||
|
|> unique_constraint(:staking_address_hash) |
||||||
|
end |
||||||
|
|
||||||
|
defp validate_staked_amount(%{valid?: false} = c), do: c |
||||||
|
|
||||||
|
defp validate_staked_amount(changeset) do |
||||||
|
if get_field(changeset, :staked_amount) < get_field(changeset, :self_staked_amount) do |
||||||
|
add_error(changeset, :staked_amount, "must be greater than self_staked_amount") |
||||||
|
else |
||||||
|
changeset |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,64 @@ |
|||||||
|
defmodule Explorer.Chain.StakingPoolsDelegator do |
||||||
|
@moduledoc """ |
||||||
|
The representation of delegators from POSDAO network. |
||||||
|
Delegators make stakes on staking pools and withdraw from them. |
||||||
|
""" |
||||||
|
use Ecto.Schema |
||||||
|
import Ecto.Changeset |
||||||
|
|
||||||
|
alias Explorer.Chain.{ |
||||||
|
Address, |
||||||
|
Hash, |
||||||
|
StakingPool, |
||||||
|
Wei |
||||||
|
} |
||||||
|
|
||||||
|
@type t :: %__MODULE__{ |
||||||
|
pool_address_hash: Hash.Address.t(), |
||||||
|
delegator_address_hash: Hash.Address.t(), |
||||||
|
max_ordered_withdraw_allowed: Wei.t(), |
||||||
|
max_withdraw_allowed: Wei.t(), |
||||||
|
ordered_withdraw: Wei.t(), |
||||||
|
stake_amount: Wei.t(), |
||||||
|
ordered_withdraw_epoch: integer() |
||||||
|
} |
||||||
|
|
||||||
|
@attrs ~w( |
||||||
|
pool_address_hash delegator_address_hash max_ordered_withdraw_allowed |
||||||
|
max_withdraw_allowed ordered_withdraw stake_amount ordered_withdraw_epoch |
||||||
|
)a |
||||||
|
|
||||||
|
schema "staking_pools_delegators" do |
||||||
|
field(:max_ordered_withdraw_allowed, Wei) |
||||||
|
field(:max_withdraw_allowed, Wei) |
||||||
|
field(:ordered_withdraw, Wei) |
||||||
|
field(:ordered_withdraw_epoch, :integer) |
||||||
|
field(:stake_amount, Wei) |
||||||
|
|
||||||
|
belongs_to( |
||||||
|
:staking_pool, |
||||||
|
StakingPool, |
||||||
|
foreign_key: :pool_address_hash, |
||||||
|
references: :staking_address_hash, |
||||||
|
type: Hash.Address |
||||||
|
) |
||||||
|
|
||||||
|
belongs_to( |
||||||
|
:delegator_address, |
||||||
|
Address, |
||||||
|
foreign_key: :delegator_address_hash, |
||||||
|
references: :hash, |
||||||
|
type: Hash.Address |
||||||
|
) |
||||||
|
|
||||||
|
timestamps(null: false, type: :utc_datetime_usec) |
||||||
|
end |
||||||
|
|
||||||
|
@doc false |
||||||
|
def changeset(staking_pools_delegator, attrs) do |
||||||
|
staking_pools_delegator |
||||||
|
|> cast(attrs, @attrs) |
||||||
|
|> validate_required(@attrs) |
||||||
|
|> unique_constraint(:pool_address_hash, name: :pools_delegator_index) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,27 @@ |
|||||||
|
defmodule Explorer.Repo.Migrations.CreateStakingPools do |
||||||
|
use Ecto.Migration |
||||||
|
|
||||||
|
def change do |
||||||
|
create table(:staking_pools) do |
||||||
|
add(:is_active, :boolean, default: false, null: false) |
||||||
|
add(:is_deleted, :boolean, default: false, null: false) |
||||||
|
add(:delegators_count, :integer) |
||||||
|
add(:staked_amount, :numeric, precision: 100) |
||||||
|
add(:self_staked_amount, :numeric, precision: 100) |
||||||
|
add(:is_validator, :boolean, default: false, null: false) |
||||||
|
add(:was_validator_count, :integer) |
||||||
|
add(:is_banned, :boolean, default: false, null: false) |
||||||
|
add(:was_banned_count, :integer) |
||||||
|
add(:banned_until, :bigint) |
||||||
|
add(:likelihood, :decimal, precision: 5, scale: 2) |
||||||
|
add(:staked_ratio, :decimal, precision: 5, scale: 2) |
||||||
|
add(:staking_address_hash, :bytea) |
||||||
|
add(:mining_address_hash, :bytea) |
||||||
|
|
||||||
|
timestamps(null: false, type: :utc_datetime_usec) |
||||||
|
end |
||||||
|
|
||||||
|
create(index(:staking_pools, [:staking_address_hash], unique: true)) |
||||||
|
create(index(:staking_pools, [:mining_address_hash])) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,26 @@ |
|||||||
|
defmodule Explorer.Repo.Migrations.CreateStakingPoolsDelegator do |
||||||
|
use Ecto.Migration |
||||||
|
|
||||||
|
def change do |
||||||
|
create table(:staking_pools_delegators) do |
||||||
|
add(:delegator_address_hash, :bytea) |
||||||
|
add(:pool_address_hash, :bytea) |
||||||
|
add(:stake_amount, :numeric, precision: 100) |
||||||
|
add(:ordered_withdraw, :numeric, precision: 100) |
||||||
|
add(:max_withdraw_allowed, :numeric, precision: 100) |
||||||
|
add(:max_ordered_withdraw_allowed, :numeric, precision: 100) |
||||||
|
add(:ordered_withdraw_epoch, :integer) |
||||||
|
|
||||||
|
timestamps(null: false, type: :utc_datetime_usec) |
||||||
|
end |
||||||
|
|
||||||
|
create(index(:staking_pools_delegators, [:delegator_address_hash])) |
||||||
|
|
||||||
|
create( |
||||||
|
index(:staking_pools_delegators, [:delegator_address_hash, :pool_address_hash], |
||||||
|
unique: true, |
||||||
|
name: :pools_delegator_index |
||||||
|
) |
||||||
|
) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,32 @@ |
|||||||
|
defmodule Explorer.Chain.Import.Runner.StakingPoolsDelegatorsTest do |
||||||
|
use Explorer.DataCase |
||||||
|
|
||||||
|
import Explorer.Factory |
||||||
|
|
||||||
|
alias Ecto.Multi |
||||||
|
alias Explorer.Chain.Import.Runner.StakingPoolsDelegators |
||||||
|
alias Explorer.Chain.StakingPoolsDelegator |
||||||
|
|
||||||
|
describe "run/1" do |
||||||
|
test "insert new pools list" do |
||||||
|
delegators = |
||||||
|
[params_for(:staking_pools_delegator), params_for(:staking_pools_delegator)] |
||||||
|
|> Enum.map(fn param -> |
||||||
|
changeset = StakingPoolsDelegator.changeset(%StakingPoolsDelegator{}, param) |
||||||
|
changeset.changes |
||||||
|
end) |
||||||
|
|
||||||
|
assert {:ok, %{insert_staking_pools_delegators: list}} = run_changes(delegators) |
||||||
|
assert Enum.count(list) == Enum.count(delegators) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp run_changes(changes) do |
||||||
|
Multi.new() |
||||||
|
|> StakingPoolsDelegators.run(changes, %{ |
||||||
|
timeout: :infinity, |
||||||
|
timestamps: %{inserted_at: DateTime.utc_now(), updated_at: DateTime.utc_now()} |
||||||
|
}) |
||||||
|
|> Repo.transaction() |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,18 @@ |
|||||||
|
defmodule Explorer.Chain.StakingPoolTest do |
||||||
|
use Explorer.DataCase |
||||||
|
|
||||||
|
alias Explorer.Chain.StakingPool |
||||||
|
|
||||||
|
describe "changeset/2" do |
||||||
|
test "with valid attributes" do |
||||||
|
params = params_for(:staking_pool) |
||||||
|
changeset = StakingPool.changeset(%StakingPool{}, params) |
||||||
|
assert changeset.valid? |
||||||
|
end |
||||||
|
|
||||||
|
test "with invalid attributes" do |
||||||
|
changeset = StakingPool.changeset(%StakingPool{}, %{staking_address_hash: 0}) |
||||||
|
refute changeset.valid? |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,18 @@ |
|||||||
|
defmodule Explorer.Chain.StakingPoolsDelegatorTest do |
||||||
|
use Explorer.DataCase |
||||||
|
|
||||||
|
alias Explorer.Chain.StakingPoolsDelegator |
||||||
|
|
||||||
|
describe "changeset/2" do |
||||||
|
test "with valid attributes" do |
||||||
|
params = params_for(:staking_pools_delegator) |
||||||
|
changeset = StakingPoolsDelegator.changeset(%StakingPoolsDelegator{}, params) |
||||||
|
assert changeset.valid? |
||||||
|
end |
||||||
|
|
||||||
|
test "with invalid attributes" do |
||||||
|
changeset = StakingPoolsDelegator.changeset(%StakingPoolsDelegator{}, %{pool_address_hash: 0}) |
||||||
|
refute changeset.valid? |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue