When I try to test this contract with Hardhat I get the next error related to the "Approve Transaction with the 3 accounts" test. But I have no idea why this error is triggered.
Error: VM Exception while processing transaction: reverted with panic code 0x32 (Array accessed at an out-of-bounds or negative index)
Could someone help? Please.
Smart contract MultiSigWallet.sol
pragma solidity ^0.8.10;
contract MultiSigWallet {
event Deposit(address indexed sender, uint256 amount, uint256 balance);
event SubmitTransaction(uint256 indexed txId);
event ApproveTransaction(address indexed owner, uint256 indexed txId);
event RevokeTransaction(address indexed owner, uint256 indexed txId);
event ExecuteTransaction(uint256 indexed txId);
struct Transaction {
address payable to;
uint256 amount;
bytes data;
bool executed;
uint256 numConfirmations;
}
Transaction[] public transactions;
address[] owners;
mapping(address => bool) isOwner;
// mapping from tx index => owner => bool
mapping(uint256 => mapping(address => bool)) approved;
mapping(uint256 => bool) executed;
uint256 confirmationsRequired;
modifier onlyOwner() {
require(isOwner[msg.sender], "You are not an owner");
_;
}
modifier txNotApproved(uint256 _txId) {
require(
!approved[_txId][msg.sender],
"The transaction is already approved"
);
_;
}
modifier txNotExecuted(uint256 _txId) {
require(
!transactions[_txId].executed,
"The transaction has been already executed"
);
_;
}
modifier txExists(uint256 _txId) {
require(
transactions.length >= _txId,
"The transaction does not exists"
);
_;
}
constructor(address[] memory _owners, uint256 _confirmationsRequired) {
require(_owners.length > 0, "Owners needs to be greater than 0");
require(
_confirmationsRequired > 0,
"Owners required needs to be greater than 0"
);
for (uint256 i = 0; _owners.length > i; i++) {
address _owner = _owners[i];
require(_owner != address(0));
isOwner[_owners[i]] = true;
}
owners = _owners;
confirmationsRequired = _confirmationsRequired;
}
receive() external payable {
emit Deposit(msg.sender, msg.value, address(this).balance);
}
function submitTransaction(
uint256 _amount,
address payable _to,
bytes memory _data
) public onlyOwner {
transactions.push(
Transaction({
to: _to,
amount: _amount,
data: _data,
executed: false,
numConfirmations: 0
})
);
uint256 txIndex = transactions.length;
emit SubmitTransaction(txIndex);
}
function approveTransaction(uint256 _txId)
public
onlyOwner
txExists(_txId)
txNotApproved(_txId)
txNotExecuted(_txId)
{
transactions[_txId].numConfirmations += 1;
approved[_txId][msg.sender] = true;
emit ApproveTransaction(msg.sender, _txId);
}
function revokeConfirmation(uint256 _txId)
public
onlyOwner
txExists(_txId)
txNotExecuted(_txId)
{
require(
approved[_txId][msg.sender] = true,
"This Transaction is not approved by the msg.sender"
);
approved[_txId][msg.sender] = false;
emit RevokeTransaction(msg.sender, _txId);
}
function executeTransaction(uint256 _txId)
public
payable
onlyOwner
txNotExecuted(_txId)
txExists(_txId)
{
Transaction storage transaction = transactions[_txId];
require(
transaction.numConfirmations >= confirmationsRequired,
"Not enough confirmations from the Owners"
);
transaction.executed = true;
uint256 _transactionAmount = transaction.amount;
address payable _to = transaction.to;
_to.transfer(_transactionAmount);
emit ExecuteTransaction(_txId);
}
function getOwners() public view returns (address[] memory) {
return owners;
}
function checkExecuted(uint256 _txId) public view returns (bool) {
return executed[_txId];
}
function getTransactions() public view returns (uint256) {
return transactions.length;
}
function getConfirmations(uint256 _txId) public view returns (uint256) {
return transactions[_txId].numConfirmations;
}
}
Testing script (Hardhat)
const { ethers } = require("hardhat");
const { expect } = require("chai");
const { loadFixture } = require("ethereum-waffle");
const { TransactionDescription } = require("ethers/lib/utils");
// Ganache accounts (Testnet):
// 1: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
// 2: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
// 3: 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
// 4: 0x90F79bf6EB2c4f870365E785982E1f101E93b906
describe("MultiSigWallet", function () {
async function deploy() {
const MultiSigWallet = await ethers.getContractFactory("MultiSigWallet");
const multiSigWallet = await MultiSigWallet.deploy(
[
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
],
3
);
await multiSigWallet.deployed();
console.log("* MultiSigWallet contract has been deployed.");
console.log("* MultiSigWallet contract address:", multiSigWallet.address);
return { multiSigWallet };
}
it("Check the Owners array length", async () => {
const { multiSigWallet } = await loadFixture(deploy);
const owners = await multiSigWallet.getOwners();
expect(owners.length).to.equal(3);
});
it("Submit a new transaction", async () => {
const { multiSigWallet } = await loadFixture(deploy);
await multiSigWallet.submitTransaction(
6,
"0x90F79bf6EB2c4f870365E785982E1f101E93b906",
"0x6e65775f6d6963726f7761766500000000000000000000000000000000000000"
);
const transactions = await multiSigWallet.getTransactions();
expect(transactions).to.equal(1);
});
it("Transfer 5 ethers from the 3 owners to the contract and check SC balance", async () => {
const { multiSigWallet } = await loadFixture(deploy);
const [deployer, addr1, addr2] = await ethers.getSigners();
await deployer.sendTransaction({
to: multiSigWallet.address,
value: ethers.utils.parseEther("2.0"),
});
await addr1.sendTransaction({
to: multiSigWallet.address,
value: ethers.utils.parseEther("2.0"),
});
await addr2.sendTransaction({
to: multiSigWallet.address,
value: ethers.utils.parseEther("2.0"),
});
const ScBalance = await ethers.provider.getBalance(multiSigWallet.address);
expect(ScBalance).to.equal("6000000000000000000"); // 6000000000000000000 = 6 ethers
});
it("Approve Transaction with the 3 accounts", async () => {
const { multiSigWallet } = await loadFixture(deploy);
const [deployer, addr1, addr2] = await ethers.getSigners();
await multiSigWallet.connect(deployer).approveTransaction(1);
await multiSigWallet.connect(addr1).approveTransaction(1);
await multiSigWallet.connect(addr2).approveTransaction(1);
const confirmations = await multiSigWallet.getConfirmations(1);
expect(confirmations).to.equal(3);
});
});