Files
DLE/docs.ru/back-docs/MULTICHAIN_GOVERNANCE_TOKEN_TRANSFER.md

44 KiB
Raw Permalink Blame History

English | Русский

Мультичейн-управление переводом токенов DLE

Обзор системы

Система мультичейн-управления DLE позволяет холдерам токенов создавать предложения по переводу токенов со своего кошелька на другой адрес (или в казну) через процесс голосования во всех сетях, где развернут контракт DLE. Каждая сеть имеет независимый кворум, но предложения координируются и отображаются как единое целое.

Архитектура

Мультичейн-контракты DLE

  • Один DLE может быть развернут в нескольких блокчейн-сетях (например, Sepolia, Arbitrum Sepolia, Base Sepolia)
  • Каждый контракт DLE в каждой сети работает независимо
  • Предложения создаются, голосуются и выполняются в каждой сети отдельно
  • ID предложений уникальны для каждой сети (предложение с ID=1 в Sepolia и предложение с ID=1 в Arbitrum Sepolia - это разные предложения)

Группировка предложений

  • Предложения с одинаковым описанием и инициатором группируются в одну карточку
  • Карточка отображает статус предложения во всех сетях DLE
  • Каждая сеть в карточке имеет свой собственный ID предложения, состояние и результаты голосования

Процесс перевода токенов

Этап 1: Создание предложения

Описание процесса

  1. Пользователь заполняет форму перевода токенов:

    • Описание предложения - текстовое описание цели перевода
    • Адрес получателя - адрес кошелька или казны, на который будут переведены токены
    • Количество токенов - количество DLE токенов для перевода
    • Длительность голосования - период времени, в течение которого можно голосовать
    • Ваш подключенный кошелек - автоматически заполняется адресом подключенного кошелька (токены будут отправлены с этого адреса)
  2. Система определяет все сети, где развернут контракт DLE

  3. Последовательное создание предложений в каждой сети:

    • Для каждой сети DLE:
      • Переключение MetaMask на соответствующую сеть
      • Задержка 1 секунда после переключения
      • Создание предложения в контракте DLE этой сети
      • Получение уникального ID предложения для этой сети
      • Задержка 3 секунды после подтверждения транзакции (5 секунд для Base Sepolia)
    • При ошибках RPC выполняется автоматический retry с экспоненциальной задержкой (до 3 попыток)
  4. Подписи в MetaMask:

    • Пользователь должен подписать транзакцию создания предложения в каждой сети DLE
    • Количество подписей = количество сетей DLE
    • Каждая подпись создает отдельное предложение в соответствующей сети

Технические детали

  • Функция контракта: createProposal(description, duration, operation, targetChains, timelockDelay)
    • Порядок параметров: description, duration, operation, targetChains (массив), timelockDelay
    • targetChains: Массив ID сетей, где будет выполнена операция (обычно [chainId] для текущей сети)
  • Операция: _transferTokens(sender, recipient, amount) - где sender = адрес инициатора предложения
    • Сигнатура: _transferTokens(address,address,uint256) - все три параметра обязательны!
    • sender получается автоматически из signer.getAddress() при создании предложения
  • ID предложения: Генерируется автоматически контрактом в каждой сети (начинается с 0, инкрементируется)
  • Группировка: Предложения с одинаковым description и initiator группируются в одну карточку

Кодирование операции

Операция для выполнения должна быть закодирована в формате ABI (Application Binary Interface) перед передачей в createProposal.

Для операции перевода токенов _transferTokens(address,address,uint256):

  1. Сигнатура функции: _transferTokens(address,address,uint256)
  2. Селектор функции: Первые 4 байта от keccak256(signature)
  3. Параметры:
    • sender - адрес отправителя (инициатора предложения)
    • recipient - адрес получателя токенов
    • amount - количество токенов (в wei, т.е. количество * 10^18)

Пример кодирования (JavaScript/ethers.js):

// Способ 1: Использование Interface (рекомендуется)
const functionSignature = '_transferTokens(address,address,uint256)';
const iface = new ethers.Interface([`function ${functionSignature}`]);
const encodedOperation = iface.encodeFunctionData('_transferTokens', [
  senderAddress,      // адрес инициатора
  recipientAddress,   // адрес получателя
  ethers.parseUnits(amount.toString(), 18) // количество в wei
]);

// Способ 2: Ручное кодирование
const functionSignature = '_transferTokens(address,address,uint256)';
const selectorBytes = ethers.keccak256(ethers.toUtf8Bytes(functionSignature));
const selector = '0x' + selectorBytes.slice(2, 10); // первые 4 байта

const abiCoder = ethers.AbiCoder.defaultAbiCoder();
const encodedParams = abiCoder.encode(
  ['address', 'address', 'uint256'],
  [senderAddress, recipientAddress, ethers.parseUnits(amount.toString(), 18)]
);

const encodedOperation = ethers.concat([selector, encodedParams]);

Важные моменты:

  • sender должен совпадать с адресом инициатора предложения (проверяется в контракте при выполнении)
  • amount ОБЯЗАТЕЛЬНО передается в wei (1 токен = 10^18 wei) - используйте ethers.parseUnits(amount.toString(), 18)
  • КРИТИЧЕСКИ ВАЖНО: sender должен определяться из signer.getAddress() при создании предложения в каждой сети отдельно, а не один раз до цикла
  • Операция кодируется для каждой сети отдельно с актуальным адресом signer для этой сети
  • Контракт декодирует операцию при выполнении и проверяет соответствие sender и initiator

КРИТИЧЕСКИ ВАЖНО - Правильная реализация:

// ✅ ПРАВИЛЬНО: Кодирование внутри цикла с актуальным адресом signer для каждой сети
async function createProposalsInAllChains(allChains, formData) {
  const results = [];
  
  for (let index = 0; index < allChains.length; index++) {
    const chainId = allChains[index];
    
    // 1. Переключаемся на нужную сеть
    await switchToVotingNetwork(chainId.toString());
    await new Promise(resolve => setTimeout(resolve, 1000)); // Задержка после переключения
    
    // 2. КРИТИЧЕСКИ ВАЖНО: Получаем адрес signer для текущей сети
    const provider = new ethers.BrowserProvider(window.ethereum);
    const signer = await provider.getSigner();
    const senderAddress = await signer.getAddress(); // Адрес инициатора из signer!
    
    // 3. Кодируем операцию с актуальным адресом signer для этой сети
    const transferCallData = encodeTransferTokensCall(
      senderAddress,        // адрес инициатора из signer (обязательно!)
      formData.recipient,   // адрес получателя
      formData.amount       // количество (будет сконвертировано в wei)
    );
    
    // 4. Создаем предложение
    const proposalData = {
      description: formData.description,
      duration: formData.duration,
      operation: transferCallData,
      targetChains: [chainId],
      timelockDelay: 0
    };
    
    await createProposal(contractAddress, proposalData);
  }
}

function encodeTransferTokensCall(sender, recipient, amount) {
  const functionSignature = '_transferTokens(address,address,uint256)';
  const iface = new ethers.Interface([`function ${functionSignature}`]);
  
  // КРИТИЧЕСКИ ВАЖНО: конвертируем amount в wei
  const amountInWei = ethers.parseUnits(amount.toString(), 18);
  
  const encodedCall = iface.encodeFunctionData('_transferTokens', [
    sender,        // адрес инициатора (обязательно! должен быть из signer.getAddress())
    recipient,     // адрес получателя
    amountInWei   // количество в wei (обязательно!)
  ]);
  
  return encodedCall;
}

// ❌ НЕПРАВИЛЬНО: Кодирование один раз до цикла
// const transferCallData = encodeTransferTokensCall(formData.sender, ...); // НЕПРАВИЛЬНО!
// for (const chainId of allChains) {
//   await createProposal(contractAddress, { operation: transferCallData, ... }); // sender может не совпасть!
// }

// ❌ НЕПРАВИЛЬНО: Отсутствует sender или amount не в wei
// const transferFunctionSelector = ethers.id("_transferTokens(address,uint256)"); // НЕПРАВИЛЬНО!
// const amount = transferData.amount; // НЕПРАВИЛЬНО! Нужна конвертация в wei

Другие поддерживаемые операции:

  • _addModule(bytes32,address) - добавление модуля
  • _removeModule(bytes32) - удаление модуля
  • _addSupportedChain(uint256) - добавление поддерживаемой сети
  • _removeSupportedChain(uint256) - удаление поддерживаемой сети
  • _updateVotingDurations(uint256,uint256) - обновление времени голосования
  • _updateQuorumPercentage(uint256) - обновление процента кворума
  • _updateDLEInfo(...) - обновление информации DLE
  • _setLogoURI(string) - обновление URI логотипа

Результат

  • Создано N предложений (по одному в каждой сети DLE)
  • Каждое предложение имеет уникальный ID в своей сети
  • Все предложения отображаются как одна карточка в интерфейсе
  • Карточка показывает статус предложения в каждой сети

Этап 2: Голосование

Описание процесса

  1. Пользователь видит карточку предложения с информацией о всех сетях DLE

  2. Пользователь выбирает голос "За" или "Против"

  3. Последовательное голосование во всех активных сетях:

    • Система определяет все активные цепочки (state === 0 или 'active', не выполнены, не отменены)
    • Для каждой активной сети:
      • Переключение MetaMask на соответствующую сеть
      • Задержка 1 секунда после переключения
      • Проверка баланса токенов в этой сети (балансы могут отличаться в разных сетях)
        • Если баланс отсутствует, сеть пропускается с предупреждением
        • Голосование продолжается в других сетях
      • Голосование с использованием уникального ID предложения для этой сети
      • Задержка 3 секунды после подтверждения транзакции (5 секунд для Base Sepolia)
  4. Подписи в MetaMask:

    • Пользователь должен подписать транзакцию голосования в каждой активной сети DLE
    • Количество подписей = количество активных сетей DLE
    • Каждая подпись регистрирует голос в соответствующей сети

Технические детали

  • Функция контракта: vote(proposalId, support) - где proposalId уникален для каждой сети
  • Проверка ID: Система использует chain.id (ID предложения из конкретной сети), а не общий ID группы
  • Проверка баланса: Баланс токенов проверяется в каждой сети отдельно перед голосованием
    • В мультичейн-системе балансы могут отличаться в разных сетях
    • Если в сети нет токенов, голосование в этой сети пропускается
    • Контракт также проверяет баланс через getPastVotes() и вернет ошибку, если токенов нет
  • Вес голоса: Зависит от баланса токенов голосующего в соответствующей сети
  • Независимые кворумы: Каждая сеть имеет свой собственный кворум

Результат

  • Голос зарегистрирован во всех активных сетях DLE
  • Каждая сеть обновляет свои счетчики голосов (forVotes, againstVotes)
  • Карточка предложения обновляется с новыми данными голосования

Этап 3: Выполнение предложения

Условия выполнения

КРИТИЧЕСКИ ВАЖНО: Предложение может быть выполнено только при условии, что кворум достигнут во всех сетях DLE, где предложение активно.

Условия для каждой сети:

  • Состояние предложения: ReadyForExecution (state === 5)
  • Кворум достигнут: forVotes >= quorumRequired
  • Большинство голосов "За": forVotes > againstVotes
  • Предложение не выполнено: executed === false
  • Предложение не отменено: canceled === false
  • Истек период голосования (если применимо)
  • Истек период timelock (если применимо)

Описание процесса

  1. Система проверяет, что предложение готово к выполнению во всех активных сетях DLE

  2. Последовательное выполнение во всех готовых сетях:

    • Для каждой сети, где предложение готово к выполнению:
      • Переключение MetaMask на соответствующую сеть
      • Задержка 1 секунда после переключения
      • Выполнение предложения с использованием уникального ID предложения для этой сети
      • Задержка 3 секунды после подтверждения транзакции (5 секунд для Base Sepolia)
  3. Подписи в MetaMask:

    • Пользователь должен подписать транзакцию выполнения в каждой готовой сети DLE
    • Количество подписей = количество готовых сетей DLE
    • Каждая подпись выполняет перевод токенов в соответствующей сети

Технические детали

  • Функция контракта: executeProposal(proposalId) - где proposalId уникален для каждой сети
  • Операция перевода: _transferTokens(sender, recipient, amount)
    • sender = адрес инициатора предложения (проверяется в контракте)
    • recipient = адрес получателя из предложения
    • amount = количество токенов из предложения
  • Проверка безопасности: Контракт проверяет, что sender совпадает с initiator предложения
  • Перевод токенов: Токены переводятся с кошелька инициатора, а не с баланса контракта

Результат

  • Перевод токенов выполнен во всех готовых сетях DLE
  • Каждая сеть независимо выполняет перевод с кошелька инициатора
  • Карточка предложения обновляется, показывая статус "Выполнено" во всех сетях

Отмена предложения

Условия отмены

  • Только инициатор предложения может отменить его
  • Предложение должно быть активным (не выполнено, не отменено)
  • Отмена возможна в любой момент до выполнения

Процесс отмены

  1. Последовательная отмена во всех активных сетях:

    • Для каждой активной сети:
      • Переключение MetaMask на соответствующую сеть
      • Задержка 1 секунда после переключения
      • Отмена предложения с использованием уникального ID предложения для этой сети
      • Задержка 3 секунды после подтверждения транзакции
  2. Подписи в MetaMask:

    • Пользователь должен подписать транзакцию отмены в каждой активной сети DLE
    • Количество подписей = количество активных сетей DLE

Технические детали

  • Функция контракта: cancelProposal(proposalId, reason)
  • Проверка прав: Контракт проверяет, что вызывающий является инициатором предложения

Важные особенности системы

1. Уникальность ID предложений

  • Каждая сеть имеет свой собственный счетчик предложений
  • Предложение с ID=1 в Sepolia и предложение с ID=1 в Arbitrum Sepolia - это разные предложения
  • При группировке система сохраняет ID из каждой сети отдельно
  • При голосовании/выполнении/отмене используется правильный ID для каждой сети

2. Группировка предложений

  • Предложения группируются по ключу: ${description}_${initiator}
  • Одна карточка = одно логическое предложение во всех сетях
  • Карточка отображает:
    • Общее описание
    • Инициатора
    • Список сетей с их статусами
    • Результаты голосования по каждой сети
    • Общий статус (активно/выполнено/отменено)

3. Независимые кворумы

  • Каждая сеть имеет свой собственный кворум
  • Кворум рассчитывается на основе общего предложения токенов в соответствующей сети
  • Голосование в одной сети не влияет на кворум в другой сети
  • Для выполнения предложения кворум должен быть достигнут во всех сетях

4. Последовательное выполнение операций

  • Все операции (создание, голосование, выполнение, отмена) выполняются последовательно, а не параллельно
  • Это необходимо, так как MetaMask может работать только с одной сетью одновременно
  • Между операциями есть задержки для стабилизации MetaMask
  • КРИТИЧЕСКИ ВАЖНО: Использование Promise.all для параллельного выполнения недопустимо и приведет к ошибкам

5. Обработка ошибок

  • Retry для временных ошибок RPC:
    • Автоматический retry до 3 попыток
    • Экспоненциальная задержка (2s, 4s, 8s)
    • Только для retryable ошибок (Internal JSON-RPC error, rate limiting)
  • Ошибки в отдельных сетях:
    • Если операция не удалась в одной сети, процесс продолжается для других сетей
    • Пользователь получает сводку успешных и неудачных операций
  • Обработка отсутствия баланса:
    • Перед голосованием в каждой сети проверяется баланс токенов
    • Если в сети нет токенов, голосование в этой сети пропускается с предупреждением
    • Голосование продолжается в других сетях, где баланс есть
    • Контракт также проверяет баланс и вернет ошибку ErrNoPower, если токенов нет

6. Безопасность

  • Проверка инициатора: При выполнении контракт проверяет, что sender совпадает с initiator
  • Проверка баланса: Перед голосованием проверяется наличие токенов в каждой сети отдельно
    • Балансы могут отличаться в разных сетях
    • Если в сети нет токенов, голосование в этой сети пропускается
    • Контракт также проверяет баланс через getPastVotes() и вернет ошибку ErrNoPower, если токенов нет
  • Проверка состояния: Перед каждой операцией проверяется актуальное состояние предложения
  • Валидация данных: Все данные предложения валидируются перед отправкой в контракт

7. Перевод токенов

  • Источник токенов: Токены переводятся с кошелька инициатора предложения, а не с баланса контракта
  • Получатель: Может быть любой адрес (кошелек или казна)
  • Количество: Указывается инициатором при создании предложения
  • Проверка баланса: Контракт проверяет достаточность баланса инициатора перед выполнением

Пользовательский интерфейс

Карточка предложения

  • Заголовок: Описание предложения
  • Инициатор: Адрес создателя предложения
  • Список сетей:
    • Название сети
    • Статус (Активно/Выполнено/Отменено/Истекло)
    • ID предложения в этой сети
    • Результаты голосования (За/Против)
    • Кворум (достигнут/не достигнут)
  • Действия:
    • Голосовать "За" / "Против" (если активно)
    • Выполнить (если готово к выполнению)
    • Отменить (если инициатор)

Индикаторы статуса

  • Активно: Предложение открыто для голосования
  • Готово к выполнению: Кворум достигнут во всех сетях
  • Выполнено: Перевод токенов выполнен во всех сетях
  • Отменено: Предложение отменено инициатором
  • Истекло: Истек период голосования

Технические детали реализации

Кодирование операций

Подробное описание процесса кодирования операций для создания предложений см. в разделе Кодирование операции (Этап 1: Создание предложения).

Структура данных предложения

{
  id: number,                    // ID группы (из первой сети)
  description: string,           // Описание предложения
  initiator: address,            // Адрес инициатора
  deadline: number,              // Дедлайн голосования
  chains: [                      // Массив данных по каждой сети
    {
      id: number,                // УНИКАЛЬНЫЙ ID для этой сети
      chainId: number,            // ID сети (11155111, 421614, 84532)
      networkName: string,        // Название сети
      contractAddress: address,   // Адрес контракта DLE в этой сети
      state: number,              // Состояние (0=Active, 3=Executed, 4=Canceled, 5=ReadyForExecution)
      forVotes: bigint,          // Голоса "За"
      againstVotes: bigint,      // Голоса "Против"
      quorumRequired: bigint,    // Требуемый кворум
      executed: boolean,         // Выполнено
      canceled: boolean,          // Отменено
      transactionHash: string     // Хеш транзакции создания
    }
  ],
  createdAt: number,             // Время создания
  uniqueId: string              // Уникальный ключ группировки
}

Функции контракта DLE

createProposal

function createProposal(
    string memory _description,
    uint256 _duration,
    bytes memory _operation,
    uint256[] memory _targetChains,
    uint256 _timelockDelay
) public returns (uint256 proposalId)

vote

function vote(uint256 _proposalId, bool _support) public

executeProposal

function executeProposal(uint256 _proposalId) public

cancelProposal

function cancelProposal(uint256 _proposalId, string memory _reason) public

_transferTokens (internal)

function _transferTokens(
    address _sender,
    address _recipient,
    uint256 _amount
) internal

Примеры использования

Пример 0: Кодирование операции перевода токенов

Перед созданием предложения необходимо закодировать операцию. КРИТИЧЕСКИ ВАЖНО: Используйте правильную сигнатуру и конвертируйте amount в wei.

import { ethers } from 'ethers';

// Получаем адрес инициатора из signer
const signer = await provider.getSigner();
const sender = await signer.getAddress(); // Адрес инициатора (обязательно!)

// Параметры перевода
const recipient = '0x1234567890123456789012345678901234567890'; // Получатель
const amount = 100; // 100 токенов (в обычных единицах, не в wei)

// Кодирование операции
const functionSignature = '_transferTokens(address,address,uint256)';
const iface = new ethers.Interface([`function ${functionSignature}`]);

// КРИТИЧЕСКИ ВАЖНО: конвертируем amount в wei
const amountInWei = ethers.parseUnits(amount.toString(), 18); // 100 * 10^18 wei

const encodedOperation = iface.encodeFunctionData('_transferTokens', [
  sender,        // адрес инициатора (обязательно!)
  recipient,     // адрес получателя
  amountInWei    // количество в wei (обязательно!)
]);

// encodedOperation теперь можно использовать в createProposal
// Результат: 0x... (селектор функции + закодированные параметры)

// Создание предложения с правильным порядком параметров
const tx = await dle.createProposal(
  "Перевод 100 токенов в казну",  // description
  86400,                          // duration (1 день в секундах)
  encodedOperation,               // operation
  [chainId],                      // targetChains (массив!)
  0                               // timelockDelay
);

Результат: Закодированная операция в формате bytes, готовая для передачи в createProposal.

Частые ошибки:

  • Использование _transferTokens(address,uint256) - неправильная сигнатура
  • Отсутствие параметра sender - контракт не сможет проверить инициатора
  • Передача amount без конвертации в wei - неправильное количество токенов
  • Неправильный порядок параметров в createProposal - ошибка вызова контракта

Пример 1: Создание предложения в 3 сетях

  1. Пользователь создает предложение "Перевод 100 токенов в казну"
  2. Система определяет 3 сети: Sepolia, Arbitrum Sepolia, Base Sepolia
  3. Создаются 3 предложения:
    • Sepolia: ID=5
    • Arbitrum Sepolia: ID=3
    • Base Sepolia: ID=7
  4. Все 3 предложения отображаются как одна карточка

Пример 2: Голосование

  1. Пользователь голосует "За" за предложение
  2. Система голосует в 3 сетях:
    • Sepolia: vote(5, true)
    • Arbitrum Sepolia: vote(3, true)
    • Base Sepolia: vote(7, true)
  3. Каждое голосование требует отдельной подписи в MetaMask

Пример 3: Выполнение

  1. Кворум достигнут во всех 3 сетях
  2. Система выполняет предложение в 3 сетях:
    • Sepolia: executeProposal(5)
    • Arbitrum Sepolia: executeProposal(3)
    • Base Sepolia: executeProposal(7)
  3. В каждой сети токены переводятся с кошелька инициатора на адрес получателя

Ограничения и особенности

Ограничения

  • MetaMask может работать только с одной сетью одновременно
  • Операции выполняются последовательно, что может занять время при большом количестве сетей
  • Ошибки RPC могут потребовать ручного retry

Особенности

  • Если предложение не создано в одной из сетей (из-за ошибки), оно все равно может быть создано в других сетях
  • Голосование возможно только в тех сетях, где предложение активно
  • Выполнение возможно только если кворум достигнут во всех активных сетях

Безопасность

Защита от атак

  • Проверка инициатора при выполнении
  • Проверка баланса перед переводом
  • Независимые кворумы в каждой сети
  • Валидация всех входных данных

Рекомендации

  • Всегда проверяйте адрес получателя перед созданием предложения
  • Убедитесь, что у вас достаточно токенов для перевода
  • Проверяйте статус предложения перед голосованием/выполнением
  • Следите за транзакциями в каждой сети

Известные проблемы и исправления

Исправленные критические ошибки

1. Отсутствие конвертации amount в wei

Проблема: Параметр amount передавался без конвертации в wei, что приводило к неправильному количеству токенов при выполнении.

Исправление: Добавлена обязательная конвертация через ethers.parseUnits(amount.toString(), 18) перед кодированием операции.

Файл: frontend/src/views/smartcontracts/TransferTokensFormView.vue

Статус: Исправлено

2. Неправильная сигнатура функции _transferTokens

Проблема: Использовалась неправильная сигнатура _transferTokens(address,uint256) вместо _transferTokens(address,address,uint256), что приводило к ошибкам декодирования в контракте.

Исправление: Исправлена сигнатура функции и добавлен обязательный параметр sender (адрес инициатора).

Файл: frontend/src/utils/dle-contract.js (функция createTransferTokensProposal)

Статус: Исправлено

3. Неправильный порядок параметров в createProposal

Проблема: В функцию createProposal передавался governanceChainId вместо targetChains в 4-м параметре, что нарушало сигнатуру контракта.

Исправление: Исправлен порядок параметров согласно сигнатуре контракта: description, duration, operation, targetChains, timelockDelay.

Файл: frontend/src/utils/dle-contract.js (функция createTransferTokensProposal)

Статус: Исправлено

4. Неправильное кодирование операции при создании предложений (КРИТИЧЕСКАЯ)

Проблема: Операция перевода токенов кодировалась один раз до цикла по сетям, используя адрес из formData.value.sender. По документации, sender должен определяться из signer.getAddress() при создании предложения в каждой сети, что гарантирует совпадение с инициатором предложения.

Исправление:

  • Кодирование операции перемещено внутрь цикла по сетям
  • Добавлено получение адреса signer для каждой сети через await signer.getAddress()
  • Добавлена проверка соответствия адреса signer адресу из формы
  • Операция теперь кодируется с актуальным адресом signer для каждой сети отдельно

Файл: frontend/src/views/smartcontracts/TransferTokensFormView.vue (функция submitForm)

Статус: Исправлено (2025-01-XX)

5. Параллельное выполнение в executeMultichainProposal (КРИТИЧЕСКАЯ)

Проблема: Функция executeMultichainProposal использовала Promise.all для параллельного выполнения операций во всех сетях одновременно. Это не работает с MetaMask, который может работать только с одной сетью одновременно.

Исправление:

  • Заменен Promise.all на последовательный цикл for
  • Добавлено переключение сетей через switchToVotingNetwork для каждой сети
  • Добавлены задержки после переключения сетей (1 секунда) и после подтверждения транзакций (3 секунды, 5 секунд для Base Sepolia)
  • Добавлена фильтрация только готовых к выполнению цепочек

Файл: frontend/src/composables/useProposals.js (функция executeMultichainProposal)

Статус: Исправлено (2025-01-XX)

6. Отсутствие переключения сетей в voteOnMultichainProposal

Проблема: Функция voteOnMultichainProposal не использовала переключение сетей и проверку баланса токенов, что требовалось по документации для корректной работы в мультичейн-среде.

Исправление:

  • Добавлено переключение сетей через switchToVotingNetwork для каждой сети
  • Добавлена проверка баланса токенов перед голосованием в каждой сети через checkTokenBalance
  • Добавлены задержки после переключения сетей (1 секунда) и после подтверждения транзакций (3 секунды, 5 секунд для Base Sepolia)
  • Добавлена фильтрация только активных цепочек
  • Добавлена обработка ошибок с пропуском сетей при отсутствии баланса

Файл: frontend/src/composables/useProposals.js (функция voteOnMultichainProposal)

Статус: Исправлено (2025-01-XX)

Текущая корректная реализация

Все функции кодирования операций и мультичейн-операции теперь используют:

  • Правильную сигнатуру: _transferTokens(address,address,uint256)
  • Все три параметра: sender, recipient, amount
  • Конвертацию amount в wei: ethers.parseUnits(amount.toString(), 18)
  • Правильный порядок параметров в createProposal
  • Определение sender из signer.getAddress() при создании предложения в каждой сети
  • Последовательное выполнение операций во всех мультичейн-функциях (не параллельное)
  • Переключение сетей перед каждой операцией в мультичейн-функциях
  • Проверку баланса токенов перед голосованием в каждой сети

Проверка корректности

Перед развертыванием убедитесь, что:

  1. Функция _transferTokens кодируется с тремя параметрами (sender, recipient, amount)
  2. amount всегда конвертируется в wei перед кодированием
  3. sender получается из signer.getAddress() при создании предложения в каждой сети и совпадает с инициатором предложения
  4. Порядок параметров в createProposal соответствует сигнатуре контракта
  5. Все мультичейн-операции (создание, голосование, выполнение) используют последовательное выполнение с переключением сетей
  6. При голосовании проверяется баланс токенов в каждой сети отдельно

Заключение

Система мультичейн-управления DLE обеспечивает децентрализованное управление переводом токенов через независимые кворумы в каждой сети, при этом предоставляя единый интерфейс для управления предложениями во всех сетях одновременно.

Важно: Все критические ошибки в кодировании операций исправлены. Код соответствует документации и контракту.