А с сегодняшнего дня мы потратим пару постов на то, чтобы посмотреть на простой код мультисиг кошелька и разобраться, что же он делает.
Я потихоньку буду разворачивать весь код, чтобы вы успевали следить за мыслью. Итак, начнем.
Давайте рассуждать, что нам вообще потребуется:
1. Во-первых, это некоторый набор владельцев кошелька, которые смогут одабривать транзакции.
2. Также нам потребуется наперёд знать, сколько человек будет одабривать действия, и проверка, что у пользователя действительно есть права на это.
За это, как раз, и будут отвечать эти переменные:
address[] public owners;
mapping(address => bool) public isOwner;
uint256 public numConfirmationsRequired;
3. Теперь подумаем о форме транзакций. В больших проектах может потребоваться больше полей, но для самой базы будет достаточно этих: кто инициировал, на какую сумму, есть ли дополнительные данные, статус и количество подписей для одобрения. Создадим структуру и поместим ее в массив:
struct Transaction {
address to;
uint256 value;
bytes data;
bool executed;
uint256 numConfirmations;
}
Transaction[] public transactions;4. Ну, и само собой, некоторый набор модификаторов для быстрой валидации владельцев и статуса транзакций:
modifier onlyOwner() {
require(isOwner[msg.sender], "not owner");
_;
}
modifier txExists(uint256 _txIndex) {
require(_txIndex < transactions.length, "tx does not exist");
_;
}
modifier notExecuted(uint256 _txIndex) {
require(!transactions[_txIndex].executed, "tx already executed");
_;
}
modifier notConfirmed(uint256 _txIndex) {
require(!isConfirmed[_txIndex][msg.sender], "tx already confirmed");
_;
}5. И для того, чтобы можно было отслеживать, кто подписал транзакцию, а кто нет, мы введем маппинг:
mapping(uint256 => mapping(address => bool)) public isConfirmed;
6. И в конструкторе нам потребуется сделать несколько проверок и записать изначальные данные в переменные состояния:
constructor(address[] memory _owners, uint256 _numConfirmationsRequired) {
require(_owners.length > 0, "owners required");
require(
_numConfirmationsRequired > 0
&& _numConfirmationsRequired <= _owners.length,
"invalid number of required confirmations"
);
for (uint256 i = 0; i < _owners.length; i++) {
address owner = _owners[i];
require(owner != address(0), "invalid owner");
require(!isOwner[owner], "owner not unique");
isOwner[owner] = true;
owners.push(owner);
}
numConfirmationsRequired = _numConfirmationsRequired;
}Тут мы проверяем, что добавляются владельцы кошелька, проверяется количество для подписывающих, делаются записи в маппинги и массив, ну, и в конце, устанавливается количество подписантов.
В итоге мы получаем базовый набор контракта: