Перейти к основному содержанию
В этом туториале мы создадим Web3 Action, который будет запускаться каждый раз, когда UniswapV2Factory развёртывает новый контракт UniswapV2Pair. Web3 Action будет сравнивать байткод нового развёрнутого контракта с байткодом другого контракта UniswapV2Pair. В данном случае мы используем контракт пары USDC. Для получения байткодов мы будем использовать Node RPC в качестве нашего провайдера ноды. Если байткоды совпадают, Web3 Action сохранит адрес пары и байткод контракта в Storage вашего Web3 Action. Если вы хотите пропустить туториал, вот полное решение:
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 здесь.
В этом примере мы будем использовать контракты UniswapV2Factory и UniswapV2Pair. Поскольку оба контракта публично верифицированы в Tenderly, нет необходимости верифицировать их вручную. Перейдите к UniswapV2Factory и нажмите кнопку Add to Project, чтобы добавить контракт в ваш проект. Повторите тот же шаг для UniswapV2Pair.

Шаг 1: Определите триггер

Всякий раз, когда вызывается функция createPair контракта UniswapV2Factory, происходит следующее:
  • Создаётся новый контракт UniswapV2Pair для пары токенов (если такого ещё не существует)
  • Генерируется событие PairCreated
Мы можем определить триггер для запуска 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 для хранения совпавших байткодов.
Узнайте, как использовать Node RPC в Web3 Actions для доступа к любой поддерживаемой сети, включая Mainnet, без хлопот с управлением URL или секретными ключами.
Что касается полезной нагрузки transactionEvent, мы используем её свойство logs, чтобы найти лог события, соответствующий topic PairCreated.

Шаг 2.1: Декодируйте событие PairCreated

При срабатывании Web3 Action нужно найти событие PairCreated в логах полезной нагрузки. Этот код мы поместим в функцию getPairCreatedEvent. Чтобы получить хеш topic’а PairCreated, нам нужен ABI контракта UniswapV2Factory. Для этого перейдите к контракту UniswapV2Factory в Tenderly, нажмите 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;
});
Наконец, декодируем запись лога в тип UniswapPair с помощью Ethers. Результат функции getPairCreatedEvent — это объект, содержащий следующее:
  • Адреса токенов, составляющих пару, — свойства token0 и token1
  • Адрес развёрнутого контракта UniswapV2Pair — свойство pair

Шаг 3: Сравните байткоды

Теперь, когда у нас есть адрес вновь развёрнутого контракта (содержащийся в свойстве pair типа UniswapPair), мы можем использовать его для получения байткода контракта. Мы будем использовать байткод контракта USDC UniswapV2Pair, чтобы определить, является ли байткод вновь созданного контракта валидным контрактом UniswapV2Pair. Их байткоды должны совпадать в точности. Чтобы получить байткоды этих контрактов, нам нужно использовать функцию getCode Ethers. Для выполнения этих шагов нам сначала нужно настроить провайдера ноды, Node RPC: gateways. Обратившись к свойству gateways объекта context, мы можем легко получить JSON-RPC URL для нужной сети (MAINNET в нашем случае), вызвав функцию getGateway() и используя её как провайдер для Ethers.
example.tsx
const gatewayURL = context.gateways.getGateway(Network.MAINNET);
const provider = new ethers.providers.JsonRpcProvider(gatewayURL);
Это позволит нам получить байткод как новой пары, так и пары USDC.
example.tsx
const usdcPairContract = await provider.getCode(USDC_PAIR_CONTRACT_ADDRESS);
const newPairContract = await provider.getCode(newPair.pair);
Если есть совпадение, мы хотим сохранить байткод новой пары в формате pair_address: bytecode под свойством JSON PairContracts в Storage нашего проекта:
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 проекта и увидеть созданные пары: Ключи — это адреса вновь развёрнутых контрактов UniswapV2Pair, а значения все одинаковые и представляют собой байткод контракта UniswapV2Pair.