139 lines
4.1 KiB
Elixir
139 lines
4.1 KiB
Elixir
defmodule BlockScoutWeb.CBSPoolView do
|
||
@moduledoc """
|
||
Blockscout 定制模块:CBS(CouponBackedSecurity)池详情页 (P1)
|
||
- 资产证券化池的底层券
|
||
- 份额持有人
|
||
- 收益分配记录
|
||
"""
|
||
|
||
alias Explorer.SmartContract.Reader
|
||
|
||
@cbs_abi [
|
||
%{
|
||
"name" => "getPoolInfo",
|
||
"type" => "function",
|
||
"stateMutability" => "view",
|
||
"inputs" => [%{"name" => "poolId", "type" => "uint256"}],
|
||
"outputs" => [
|
||
%{"name" => "totalShares", "type" => "uint256"},
|
||
%{"name" => "totalUnderlying", "type" => "uint256"},
|
||
%{"name" => "createdAt", "type" => "uint256"},
|
||
%{"name" => "active", "type" => "bool"}
|
||
]
|
||
},
|
||
%{
|
||
"name" => "getUnderlyingCoupons",
|
||
"type" => "function",
|
||
"stateMutability" => "view",
|
||
"inputs" => [%{"name" => "poolId", "type" => "uint256"}],
|
||
"outputs" => [%{"name" => "", "type" => "uint256[]"}]
|
||
},
|
||
%{
|
||
"name" => "getShareBalance",
|
||
"type" => "function",
|
||
"stateMutability" => "view",
|
||
"inputs" => [
|
||
%{"name" => "poolId", "type" => "uint256"},
|
||
%{"name" => "holder", "type" => "address"}
|
||
],
|
||
"outputs" => [%{"name" => "", "type" => "uint256"}]
|
||
}
|
||
]
|
||
|
||
# RevenueDistributed 事件
|
||
@revenue_distributed_topic "0x" <>
|
||
Base.encode16(:crypto.hash(:keccak256, "RevenueDistributed(uint256,uint256,uint256)"), case: :lower)
|
||
|
||
@doc "获取 CBS 池完整详情"
|
||
def get_pool_detail(pool_id, cbs_contract) do
|
||
with {:ok, info} <- get_pool_info(pool_id, cbs_contract),
|
||
{:ok, coupons} <- get_underlying_coupons(pool_id, cbs_contract) do
|
||
%{
|
||
pool_id: pool_id,
|
||
total_shares: info.total_shares,
|
||
total_underlying: info.total_underlying,
|
||
created_at: DateTime.from_unix!(info.created_at),
|
||
active: info.active,
|
||
underlying_coupons: coupons,
|
||
coupon_count: length(coupons),
|
||
distributions: get_distribution_history(pool_id, cbs_contract)
|
||
}
|
||
else
|
||
{:error, reason} -> {:error, reason}
|
||
end
|
||
end
|
||
|
||
@doc "获取池的收益分配历史"
|
||
def get_distribution_history(pool_id, _cbs_contract) do
|
||
# 查询 RevenueDistributed 事件日志
|
||
# 实际实现需要查询 Explorer.Chain.Log
|
||
[]
|
||
|> Enum.map(fn log ->
|
||
case log.first_topic do
|
||
@revenue_distributed_topic ->
|
||
%{
|
||
pool_id: decode_uint256(log.second_topic),
|
||
amount: decode_uint256_from_data(log.data, 0),
|
||
timestamp: decode_uint256_from_data(log.data, 1),
|
||
block_number: log.block_number,
|
||
tx_hash: log.transaction_hash
|
||
}
|
||
|
||
_ ->
|
||
nil
|
||
end
|
||
end)
|
||
|> Enum.filter(&(&1 != nil))
|
||
|> Enum.filter(&(&1.pool_id == pool_id))
|
||
end
|
||
|
||
@doc "解析 RevenueDistributed 事件"
|
||
def parse_distribution_event(log) do
|
||
case log.first_topic do
|
||
@revenue_distributed_topic ->
|
||
%{
|
||
pool_id: decode_uint256(log.second_topic),
|
||
amount: decode_uint256_from_data(log.data, 0),
|
||
share_count: decode_uint256_from_data(log.data, 1)
|
||
}
|
||
|
||
_ ->
|
||
nil
|
||
end
|
||
end
|
||
|
||
# ── 私有函数 ──
|
||
|
||
defp get_pool_info(pool_id, contract) do
|
||
case Reader.query_contract(contract, @cbs_abi, %{"getPoolInfo" => [pool_id]}) do
|
||
%{"getPoolInfo" => {:ok, [total_shares, total_underlying, created_at, active]}} ->
|
||
{:ok,
|
||
%{
|
||
total_shares: total_shares,
|
||
total_underlying: total_underlying,
|
||
created_at: created_at,
|
||
active: active
|
||
}}
|
||
|
||
_ ->
|
||
{:error, :contract_call_failed}
|
||
end
|
||
end
|
||
|
||
defp get_underlying_coupons(pool_id, contract) do
|
||
case Reader.query_contract(contract, @cbs_abi, %{"getUnderlyingCoupons" => [pool_id]}) do
|
||
%{"getUnderlyingCoupons" => {:ok, [coupon_ids]}} -> {:ok, coupon_ids}
|
||
_ -> {:error, :contract_call_failed}
|
||
end
|
||
end
|
||
|
||
defp decode_uint256(nil), do: 0
|
||
defp decode_uint256("0x" <> hex), do: String.to_integer(hex, 16)
|
||
|
||
defp decode_uint256_from_data(data, offset) do
|
||
data
|
||
|> String.slice(2 + offset * 64, 64)
|
||
|> String.to_integer(16)
|
||
end
|
||
end
|