ваше сообщение коммита
This commit is contained in:
661
docs/MULTICHAIN_GOVERNANCE_TOKEN_TRANSFER.md
Normal file
661
docs/MULTICHAIN_GOVERNANCE_TOKEN_TRANSFER.md
Normal file
@@ -0,0 +1,661 @@
|
||||
# Мультичейн-управление переводом токенов 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 обеспечивает децентрализованное управление переводом токенов через независимые кворумы в каждой сети, при этом предоставляя единый интерфейс для управления предложениями во всех сетях одновременно.
|
||||
|
||||
**Важно:** Все критические ошибки в кодировании операций исправлены. Код соответствует документации и контракту.
|
||||
|
||||
Reference in New Issue
Block a user