工厂克隆模式在最小化 gas 成本方面可能是有利的。然而,由于每个克隆都被部署到一个新的地址,因此有效地跟踪和监控这些合约可能是一个挑战。

本指南展示了如何使用 Defender 来监控工厂合约以及由它创建的克隆合约。监控自动化是通过以下 Defender 模块结构实现的:

在这种情况下,可以预先提供合约 ABI,因为克隆合约将具有相同的 ABI。或者,您可以使用 Etherscan 的 API,window=_blank 从给定地址的已验证合约动态检索 ABI。

生成 API 密钥

要以编程方式将合约添加到地址簿,https://www.npmjs.com/package/@openzeppelin/defender-sdk[sdk,window=_blank] 需要 API 密钥和密钥形式的凭据。在 API 密钥页面,window=_blank 中创建并复制凭据。

创建 API 凭据

现在,导航到 Defender 中的 Secrets,window=_blank 页面,创建一个名为 API_KEY 的新密钥,然后粘贴 API 密钥。创建另一个名为 API_SECRET 的密钥,然后粘贴 API 密钥。这些密钥将由 Action 安全地使用。

保存 API 凭据

创建 Action

导航到 Action 创建页面,window=_blank,输入名称,然后选择 Webhook 作为触发器。然后,粘贴以下 Action 代码并保存它:

const { Defender } = require('@openzeppelin/defender-sdk');

exports.handler = async function (event) {
  const creds = {
    apiKey: event.secrets.API_KEY,
    apiSecret: event.secrets.API_SECRET,
  }
  const client = new Defender(creds);

  const payload = event.request.body
  const matchReasons = payload.matchReasons
  const newCloneAddress = matchReasons[0].params._clone
  const newCloneAbi = `[
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "ValueChanged",
      "type": "event"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "initialize",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "retrieve",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "store",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ]`
  // 添加新的克隆合约
  await client.proposal.addContract({
    network: 'sepolia',
    address: newCloneAddress,
    name: `Clone ${newCloneAddress}`,
    abi: newCloneAbi,
  })
}
创建 Action

该 Action 现在已准备好被 Monitor 触发。

手动触发此 Action 将引发错误,因为 Action 依赖于 Monitor 提供的数据(例如新部署的克隆合约地址)。

创建 Monitor

此 Monitor 将监视工厂合约发出的指示已创建新克隆的事件。导航到 Monitor 创建页面,window=_blank,选择一个名称、风险类别,然后选择 Factory 合约(如果尚未添加,则添加工厂)。

Monitor 常规信息

保留 Transaction Filters 不变,然后继续到 Events 选项卡。在此处,选择用于克隆创建的事件名称,并将事件参数留空以捕获所有发出的事件。

Monitor 事件

最后,打开 Alerts 部分,然后在 Execute an Action 下拉列表中选择在上一步中创建的 Action。随意添加任何其他设置(如通知),然后保存 Monitor。

Monitor 警报

与任何 action 一样,此 Monitor 的触发将记录在 日志 中。

测试运行

要测试设置,请导航到 Transaction Proposals,window=_blank 以通过工厂手动创建克隆。选择工厂合约,然后调用使用任何所需参数创建克隆的函数。

用于创建克隆的 Transaction Proposal

然后,使用您喜欢的审批流程(如 Relayer 或 EOA 钱包)执行此交易。转到 Action 的运行历史记录以验证它是否由 Monitor 触发,并将克隆合约地址添加到 Defender。

Action 运行历史记录

为克隆创建 Monitor

现在您有一个克隆合约可以用作所有未来克隆合约的模板,是时候为它们创建一个 Monitor 了。导航到 Monitor 创建页面,window=_blank,选择一个名称、风险类别,然后选择克隆合约。

此外,您可以随意为交易、事件和函数或通知添加任何其他过滤器。保存 Monitor 并观察日志/通知,以验证 Monitor 是否按预期工作。

Monitor 克隆

自动将克隆添加到 Monitor

使用上一个 Monitor,您可以更新 Action 以将任何新创建的合约添加到 Monitor 正在监视的地址列表中。使用以下代码更新 Action 代码,将 monitorId 替换为在上一步中创建的 Monitor 的 ID:

const { Defender } = require('@openzeppelin/defender-sdk');

exports.handler = async function (event) {
  const creds = {
    apiKey: event.secrets.API_KEY,
    apiSecret: event.secrets.API_SECRET,
  }
  const client = new Defender(creds);

  const payload = event.request.body
  const matchReasons = payload.matchReasons
  const newCloneAddress = matchReasons[0].params._clone
  const newCloneAbi = `[
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "ValueChanged",
      "type": "event"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "initialize",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "retrieve",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "store",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ]`
  // 添加新的克隆合约
  await client.proposal.addContract({
    network: 'sepolia',
    address: newCloneAddress,
    name: `Clone ${newCloneAddress}`,
    abi: newCloneAbi,
  })

  // 将克隆合约添加到 Monitor
  const monitorId = 'REPLACE'
  const monitor = await client.monitor.get(monitorId)
  const subscribedAddresses = monitor.addressRules[0].addresses
  subscribedAddresses.push(newCloneAddress)
  await client.action.update(monitorId, { addresses: subscribedAddresses })
}

现在,当 Action 运行时,它不仅会将合约添加到 Defender,还会将其添加到 Monitor。

要验证,请执行另一次测试运行!