跳转到主要内容
在本教程中,我们将创建一个 Web3 Action,每次 UniswapV2Factory 部署新的 UniswapV2Pair 合约时都会运行。Web3 Action 将比较新部署合约的字节码与另一个 UniswapV2Pair 合约的字节码。在此示例中,我们将使用 USDC Pair 合约。 为了获取字节码,我们将使用 Node RPC 作为节点提供商。如果字节码匹配,Web3 Action 会将该 pair 的地址和合约的字节码存储在 Web3 Action 的 Storage 中。 如果您想跳过本教程,以下是完整的解决方案:
example.tsx



type UniswapPair = {
  token0: string;
  token1: string;
  pair: string;
};

const USDC_PAIR_CONTRACT_ADDRESS = '0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc';

export const onPairCreatedEventEmitted: ActionFn = async (context: Context, event: Event) => {
  try {
    const txEvent = event as TransactionEvent;
    const newPair = await getPairCreatedEvent(txEvent);

    const gatewayURL = context.gateways.getGateway(Network.MAINNET);
    const provider = new ethers.providers.JsonRpcProvider(gatewayURL);

    const usdcPairContract = await provider.getCode(USDC_PAIR_CONTRACT_ADDRESS);
    const newPairContract = await provider.getCode(newPair.pair);

    if (usdcPairContract === newPairContract) {
      const pairContracts = await context.storage.getJson('PairContracts');
      pairContracts[newPair.pair] = newPairContract;
      await context.storage.putJson('PairContracts', pairContracts);
    }
  } catch (error) {
    console.error(error);
  }
};

const getPairCreatedEvent = async (txEvent: TransactionEvent): Promise<UniswapPair> => {
  const i = new ethers.utils.Interface(UniswapV2FactoryAbi);
  const pairCreatedTopic = i.getEventTopic('PairCreated');

  const pairCreatedEventLog = txEvent.logs.find(log => {
    return log.topics.find(topic => topic == pairCreatedTopic) !== undefined;
  });

  if (pairCreatedEventLog == undefined) {
    throw Error('PairCreatedEvent missing');
  }

  return i.decodeEventLog(
    'PairCreated',
    pairCreatedEventLog.data,
    pairCreatedEventLog.topics,
  ) as unknown as UniswapPair;
};

前提条件

Web3 Actions 的触发机制仅在触发器定义中使用的合约已在 Tenderly 上验证时才有效。了解 如何在 Tenderly 上验证合约
在此示例中,我们将使用 UniswapV2FactoryUniswapV2Pair 合约。由于这两个合约已在 Tenderly 上公开验证,因此无需手动验证。 导航到 UniswapV2Factory 并点击 Add to Project 按钮将该合约添加到您的项目。对 UniswapV2Pair 执行相同的步骤。

步骤 1:定义触发器

每当 UniswapV2Factory 合约的 createPair 函数 被调用时,会发生以下情况: 我们可以定义触发器,让每次触及合约的事务调用某个发出特定事件的函数时都运行 Web3 Action。 在我们的示例中,我们将编写在 UniswapV2Factory 合约发出 PairCreated 事件时触发的规则。换言之,每当部署新的 UniswapV2Pair 合约时触发。 触发器的配置如下所示,需要保存到 tenderly.yaml 文件中:
example.yaml
account_id: ''
actions:
  YOUR_USERNAME/YOUR_PROJECT_SLUG:
    runtime: v2
    sources: actions
    specs:
      uniswapNewPair:
        description: Runs when a new pair is created on uniswap
        function: uniswapActions:onPairCreatedEventEmitted
        execution_type: parallel
        trigger:
          type: transaction
          transaction:
            status:
              - mined
            filters:
              - network: 1
                eventEmitted:
                  contract:
                    address: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f
                  name: PairCreated
project_slug: ''

步骤 2:定义 Web3 Action 逻辑

当我们的 Web3 Action 运行时,它接收两个输入:
  • 一个 context 对象,提供对 Node RPC 和 Storage 的访问
  • 一个 transactionEvent 负载,包含触发 Web3 Action 的事务相关数据
我们将使用 context 对象访问 gateways,它将作为 Ethers 的节点提供商,并使用 Storage 存储匹配的字节码。
了解 如何在 Web3 Actions 中使用 Node RPC 以访问任何 受支持的网络(包括 Mainnet),而无需管理 URL 或密钥。
至于 transactionEvent 负载,我们将使用它的 logs 属性查找对应于 PairCreated topic 的事件日志。

步骤 2.1:解码 PairCreated 事件

触发时,Web3 Action 需要在负载的日志中查找 PairCreated 事件。我们将这段代码放入 getPairCreatedEvent 函数。 要获取 PairCreated topic 的哈希值,我们需要使用 UniswapV2Factory 合约的 ABI。为此,前往 Tenderly 中的 UniswapV2Factory 合约,点击 View ABI 并将其复制/粘贴到 actions 目录中的本地文件。 现在我们可以通过以下代码获取该 topic 的引用:
example.tsx
const i = new ethers.utils.Interface(UniswapV2FactoryAbi);
const pairCreatedTopic = i.getEventTopic('PairCreated');
接下来,我们需要遍历事务日志并找到引用该 topic 的条目,如下所示。
example.tsx
const pairCreatedEventLog = txEvent.logs.find(log => {
  return log.topics.find(topic => topic == pairCreatedTopic) !== undefined;
});
最后,我们将使用 Ethers 将日志条目解码为 UniswapPair 类型。 getPairCreatedEvent 函数的结果是一个包含以下内容的对象:
  • 构成 pair 的代币地址 —— token0token1 属性
  • 已部署 UniswapV2Pair 合约的地址 —— pair 属性

步骤 3:比较字节码

现在我们有了新部署合约的地址(包含在 UniswapPair 类型的 pair 属性中),可以使用它获取合约的字节码。 我们将使用 USDC UniswapV2Pair 合约的字节码来判断新创建的合约字节码是否为有效的 UniswapV2Pair 合约。它们的字节码必须完全匹配。 要获取这些合约的字节码,我们需要使用 Ethers 的 getCode 函数。 要完成这些步骤,我们首先需要设置一个节点提供商,即 Node RPC:gateways 通过访问 context 对象的 gateways 属性,我们可以调用 getGateway() 函数轻松获取我们期望网络(在我们的例子中是 MAINNET)的 JSON-RPC URL,并将其用作 Ethers 的提供商。
example.tsx
const gatewayURL = context.gateways.getGateway(Network.MAINNET);
const provider = new ethers.providers.JsonRpcProvider(gatewayURL);
这将使我们能够获取新 pair 和 USDC pair 的字节码。
example.tsx
const usdcPairContract = await provider.getCode(USDC_PAIR_CONTRACT_ADDRESS);
const newPairContract = await provider.getCode(newPair.pair);
如果匹配,我们希望以 pair_address: bytecode 的格式在项目 Storage 中名为 PairContracts 的 JSON 属性下存储新 pair 的字节码:
example.tsx
if (usdcPairContract === newPairContract) {
  const pairContracts = await context.storage.getJson('PairContracts');
  pairContracts[newPair.pair] = newPairContract;
  await context.storage.putJson('PairContracts', pairContracts);
}
让 Web3 Action 运行一段时间后,我们可以检查项目的 Storage 并查看创建的 pairs: 键是新部署的 UniswapV2Pair 合约的地址,而值都相同,代表 UniswapV2Pair 合约的字节码。