mcpField
M
more-vaults

more-vaults

github.com/tradingstrategy-ai开发82967 次安装今天更新
代码仓库

关于 more-vaults

用于在现有代码库中添加 ERC-4626 金库协议支持的编码技能,涵盖 ABI 处理、协议识别、金库实现、测试与文档更新。

主要功能

  1. 01收集协议和合约信息
  2. 02添加并保存合约 ABI
  3. 03实现协议金库类
  4. 04添加协议识别与实例创建
  5. 05更新测试、文档和变更日志

适用场景

  1. 01集成新的金库协议
  2. 02添加协议专属的金库识别
  3. 03测试并记录金库支持

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tradingstrategy-ai/web3-ethereum-defi --skill more-vaults

安装后在 Claude Code 中输入/more-vaults确认已加载。

详细介绍

向协议添加更多 Vault

本技能将指导你向现有 Vault 添加更多智能合约类型。

必需输入

开始前,请向用户收集以下信息:

  1. Vault 协议名称:代码库中现有的 Vault 协议
  2. Vault 类名:此 Vault 智能合约类型使用的 Python 类名
  3. Vault 智能合约地址:区块链上一个示例 Vault 合约的地址
  4. 链:所用的区块链(Ethereum、Arbitrum、Base 等)
  5. 区块浏览器 URL:用于获取 ABI(例如 Etherscan、Arbiscan、Basescan)
  6. 单一 Vault 协议:某些协议,尤其是发行自有稳定币的协议,已知只有一个用于稳定币质押的 Vault。例如 Spark、Ethena 和 Cap 等协议。在这种情况下,后续应使用 HARDCODED_PROTOCOLS 分类,因为如果协议不需要,就没有必要创建复杂的 Vault 智能合约检测模式。

分步实施

步骤 1:下载并保存 ABI
  1. 从区块链浏览器获取 Vault 智能合约 ABI
  2. 重要:如果合约是代理合约,你需要的是实现合约的 ABI,而不是代理合约的 ABI
    • 检查合约是否有 implementation() 函数或类似函数
    • 使用区块浏览器的“Read as Proxy”功能获取实现合约地址
    • 下载实现合约的 ABI
  3. 创建 ABI 目录和文件:
    eth_defi/abi/{protocol_slug}/
    eth_defi/abi/{protocol_slug}/{ContractName}.json
    
  4. 参考 eth_defi/abi/lagoon/ 的目录结构
步骤 2:创建 Vault 类

更新现有 Vault 协议 Python 模块,加入类定义。

Vault 类应放在与现有模块相邻的新模块中:

"""Module docstring describing the protocol."""

import datetime
import logging

from eth_typing import BlockIdentifier

from eth_defi.erc_4626.vault import ERC4626Vault

logger = logging.getLogger(__name__)


class {VaultClassName}(ERC4626Vault):
    """Protocol vault support.

    Add few lines descriptiong of the protocol here, from the protocol documentation.

    - Add links to protocol documentation
    - Add links to example contracts on block explorers
    - Add links to github
    """

    def has_custom_fees(self) -> bool:
        """Whether this vault has deposit/withdrawal fees."""
        return False  # Adjust based on protocol

    def get_management_fee(self, block_identifier: BlockIdentifier) -> float:
        """Get the current management fee as a percent.

        :return:
            0.1 = 10%
        """
        # Implement based on protocol's fee structure
        # Generated: Human can add details later
        return None

    def get_performance_fee(self, block_identifier: BlockIdentifier) -> float | None:
        """Get the current performance fee as a percent.

        :return:
            0.1 = 10%
        """
        # Implement based on protocol's fee structure
        # Generated: Human can add details later
        return None

    def get_estimated_lock_up(self) -> datetime.timedelta | None:
        """Get estimated lock-up period if any."""
        return None

    def get_link(self, referral: str | None = None) -> str:
        """Get the vault's web UI link."""
        return f"https://protocol-url.com/vault/{self.vault_address}"

对于 get_link(),请查看协议网站,找到指向其 Vault 的直接链接 URL 模式。常见格式:

  • 使用地址
  • 使用链 ID 和地址,例如 Ethereum 的链 ID 为 1
  • 使用链名称和地址——使用 get_chain_name(chain_id).lower() 或类似方法
  • 对于只有一个 Vault 的协议,可以使用没有模式的单一链接
  • 如果无法确定,就链接到协议主页
步骤 3:添加协议识别探测

编辑 eth_defi/erc_4626/classification.py:

  1. 在 create_probe_calls() 中添加一个能够唯一识别此协议的探测调用:
    • 分析 ABI 和 Vault 实现智能合约源代码,找到此协议独有的函数
    • 查找 getProtocolSpecificData()、自定义角色常量等函数,并与 create_probe_calls() 中已实现的内容进行比较
    • 确保此调用不会与已配置的协议冲突
    • 也可以使用区块链浏览器的 Contract > Read contract 或 Contract Read contract as proxy,找出适合检测此类智能合约的 ABI 调用
    • 如果该协议是单一 Vault 协议,请在 classification.py 中使用 HARDCODED_PROTOCOLS

如果在 ABI 或 Vault 智能合约源代码中找不到此类访问器函数,请中断此技能并请求用户介入。

# {Protocol Name}
# {Block explorer link}
{protocol_slug}_call = EncodedCall.from_keccak_signature(
    address=address,
    signature=Web3.keccak(text="uniqueFunction()")[0:4],
    function="uniqueFunction",
    data=b"",
    extra_data=None,
)
yield {protocol_slug}_call
  1. 在 identify_vault_features() 中添加检测逻辑:
if calls["uniqueFunction"].success:
    features.add(ERC4626Feature.{protocol_slug}_like)
步骤 5:更新 create_vault_instance()

在 eth_defi/erc_4626/classification.py 中,为新协议在 create_vault_instance() 添加一个分支:

elif ERC4626Feature.{protocol_slug}_like in features:
    from eth_defi.erc_4626.vault_protocol.{protocol_slug}.vault import {ProtocolName}Vault

    return {ProtocolName}Vault(web3, spec, token_cache=token_cache, features=features)
步骤 5:更新测试文件

按照 tests/erc_4626/vault_protocol/test_plutus.py 和 tests/erc_4626/vault_protocol/test_goat.py 的模式更新 tests/erc_4626/vault_protocol/test_{protocol_slug}.py:

"""Test {Protocol Name} vault metadata"""

import os
from pathlib import Path

import pytest
from web3 import Web3
import flaky

from eth_defi.erc_4626.classification import create_vault_instance_autodetect
from eth_defi.erc_4626.core import get_vault_protocol_name
from eth_defi.erc_4626.vault_protocol.{protocol_slug}.vault import {ProtocolName}Vault
from eth_defi.provider.anvil import fork_network_anvil, AnvilLaunch
from eth_defi.provider.multi_provider import create_multi_provider_web3
from eth_defi.vault.base import VaultTechnicalRisk
from eth_defi.erc_4626.core import ERC4626Feature

JSON_RPC_{CHAIN} = os.environ.get("JSON_RPC_{CHAIN}")

pytestmark = pytest.mark.skipif(
    JSON_RPC_{CHAIN} is None,
    reason="JSON_RPC_{CHAIN} needed to run these tests"
)


@pytest.fixture(scope="module")
def anvil_{chain}_fork(request) -> AnvilLaunch:
    """Fork at a specific block for reproducibility"""
    launch = fork_network_anvil(JSON_RPC_{CHAIN}, fork_block_number={block_number})
    try:
        yield launch
    finally:
        launch.close()


@pytest.fixture(scope="module")
def web3(anvil_{chain}_fork):
    web3 = create_multi_provider_web3(anvil_{chain}_fork.json_rpc_url)
    return web3


@flaky.flaky
def test_{protocol_slug}(
    web3: Web3,
    tmp_path: Path,
):
    """Read {Protocol Name} vault metadata"""

    vault = create_vault_instance_autodetect(
        web3,
        vault_address="{vault_address}",
    )

    assert isinstance(vault, {ProtocolName}Vault)
    assert vault.get_protocol_name() == "{Protocol Name}"

    # Add assertation about vault feature flags here, like:
    # assert vault.features == {ERC4626Feature.goat_like}

    # Add assertions for fee data we know
    # assert vault.get_management_fee("latest") == ...
    # assert vault.get_performance_fee("latest") == ...

    # Add assertion for the protcol risk level
    # assert vault.get_risk() == VaultTechnicalRisk.unknown

  • 将测试文件更新为使用正确的区块链
  • 使用区块链浏览器获取最新区块号,并更新 fork_block_number
  • 运行测试时,如果用户没有为此链配置 JSON-RPC,请中断此技能,并告知用户更新其测试环境变量

添加后,运行测试模块并修复所有问题。

第 7 步:所有 Vault 协议相关测试

重新运行所有 Vault 协议测试,因为我们为该协议添加了更多 Vault。

第 8 步:格式化代码库

使用 poetry run ruff format 格式化新添加的文件。

第 9 步:更新文档

在 docs/source/vaults 中添加新创建的模块,并与现有模块并列。

记得更新 index.rst。

第 10 步:验证清单

实现后,请验证:

  • ABI 文件已正确放置在 eth_defi/abi/{protocol_slug}/
  • Vault 类继承自 ERC4626Vault
  • ERC4626Feature 枚举中包含新协议
  • get_vault_protocol_name() 返回正确的名称
  • create_probe_calls() 为该协议提供唯一的探测调用
  • identify_vault_features() 能正确识别该协议
  • create_vault_instance() 能创建正确的 Vault 类
  • 测试文件运行成功:source .local-test.env && poetry run pytest tests/erc_4626/vault_protocol/test_{protocol_slug}.py -v
  • API 文档已更新
  • 检查 API 文档中的主页链接是否指向正确的主页
  • 检查 API 文档中的 Twitter 链接是否有效,并指向与协议主页所列相同的 Twitter 账号

如果清单中有未通过的项目,请寻求人类协助。

第 12 步:变更日志

  • 更新 CHANGELOG.md 中的变更日志条目,并注明新增协议

第 12 步:拉取请求(可选)

全部完成后,可以创建拉取请求,但仅当用户要求时才创建。

gh pr create \
  --title "Add new vault protocol: {protocol name}" \
  --body  $'Protocol: {protocok name}\nHomepage: {homepage link}\nGithub: {github link}\nDocs: {docs link}\nExample contract: {blockchain explorer link}" \
  --base master

查找唯一的协议标识符

要查找能够唯一标识该协议的函数:

  1. 阅读 ABI 并查找:

    • 协议专属的角色常量(例如 Plutus 的 SAY_TRADER_ROLE())
    • 自定义 getter 函数(例如 IPOR 的 getPerformanceFeeData())
    • 协议注册表调用(例如 Morpho 的 MORPHO())
    • 唯一的配置函数
  2. 检查该函数是否确实唯一,确认其他协议中不存在该函数

  3. 如果没有唯一函数,有些协议可能需要根据名称进行检测:

    name = calls["name"].result
    if name:
        name = name.decode("utf-8", errors="ignore")
        if "ProtocolName" in name:
            features.add(ERC4626Feature.{protocol_slug}_like)
    

ABI 结构示例

ABI JSON 文件应包含合约的 ABI 数组。示例:

{
  "abi": [
    {
      "inputs": [],
      "name": "totalAssets",
      "outputs": [{ "type": "uint256" }],
      "stateMutability": "view",
      "type": "function"
    }
  ]
}

或者直接使用数组:

[
  {
    "inputs": [],
    "name": "totalAssets",
    "outputs": [{ "type": "uint256" }],
    "stateMutability": "view",
    "type": "function"
  }
]

评论

支持 Markdown