Files
DLE/docs/MULTICHAIN_GOVERNANCE_TOKEN_TRANSFER.md

662 lines
44 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Мультичейн-управление переводом токенов 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):**
```javascript
// Способ 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`
**КРИТИЧЕСКИ ВАЖНО - Правильная реализация:**
```javascript
// ✅ ПРАВИЛЬНО: Кодирование внутри цикла с актуальным адресом 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: Создание предложения).
### Структура данных предложения
```javascript
{
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
```solidity
function createProposal(
string memory _description,
uint256 _duration,
bytes memory _operation,
uint256[] memory _targetChains,
uint256 _timelockDelay
) public returns (uint256 proposalId)
```
#### vote
```solidity
function vote(uint256 _proposalId, bool _support) public
```
#### executeProposal
```solidity
function executeProposal(uint256 _proposalId) public
```
#### cancelProposal
```solidity
function cancelProposal(uint256 _proposalId, string memory _reason) public
```
#### _transferTokens (internal)
```solidity
function _transferTokens(
address _sender,
address _recipient,
uint256 _amount
) internal
```
---
## Примеры использования
### Пример 0: Кодирование операции перевода токенов
Перед созданием предложения необходимо закодировать операцию. **КРИТИЧЕСКИ ВАЖНО:** Используйте правильную сигнатуру и конвертируйте amount в wei.
```javascript
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 обеспечивает децентрализованное управление переводом токенов через независимые кворумы в каждой сети, при этом предоставляя единый интерфейс для управления предложениями во всех сетях одновременно.
**Важно:** Все критические ошибки в кодировании операций исправлены. Код соответствует документации и контракту.