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