gcx/blockchain/explorer/cbs-pool-view.ex

139 lines
4.1 KiB
Elixir
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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