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