Thanks! You helped me see this in a different way. I also saw that I had my value types out of order in the double mapping. Here is the final project that checks out in my testing, but I welcome feedback as always:
Ownable.sol
pragma solidity 0.8.6;
contract Ownable {
address owner;
modifier onlyOwner {
require(msg.sender == owner);
_;
}
constructor() {
owner = msg.sender;
}
}
Authorizable.sol
pragma solidity 0.8.6;
import "./Ownable.sol";
contract Authorizable is Ownable {
address[] public authorized;
mapping(address => bool) public isAuthorized;
event addedAuthorized(address indexed addAuthorized);
event removedAuthorized(address indexed removeAuthorized);
modifier onlyAuthorized() {
require(isAuthorized[msg.sender] || owner == msg.sender, "Not authorized");
_;
}
function addAuthorized(address _toAdd) onlyOwner public {
isAuthorized[_toAdd] = true;
emit addedAuthorized(_toAdd);
}
function removeAuthorized(address _toRemove) onlyOwner public {
isAuthorized[_toRemove] = false;
emit removedAuthorized(_toRemove);
}
}
Wallet.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
pragma abicoder v2;
import "./Authorizable.sol";
contract Wallet is Authorizable {
// Individual deposits and deposit event log
mapping(address => uint) balance;
event depositCompleted(address from, uint amount);
// Minimum # of signatures for approval
uint public numSignaturesRequired;
// Struct of transaction
struct Transaction {
address payable to;
uint amount;
uint numSignatures;
bool executed;
uint txIndex;
}
// Logs
event Created(uint _txIndex, uint _amount, address _creator, address _to);
event Signed(uint _txIndex, uint _numSignatures, address _signer);
event Sent(uint _txIndex);
// Array of transfer requests
Transaction[] public transactionRequests;
// Storage of approvals
mapping(uint => mapping(address => bool)) public approvals;
// Modifier to ensure transaction exists
modifier txExists(uint _txIndex) {
require(_txIndex < transactionRequests.length, "Transaction does not exist");
_;
}
// Modifier to ensure transaction not confirmed
modifier notConfirmed(uint _txIndex) {
require(!approvals[_txIndex][msg.sender], "Transaction already confirmed");
_;
}
// Modifier to ensure transaction is not executed
modifier notExecuted(uint _txIndex) {
require(!transactionRequests[_txIndex].executed, "Transaction already executed");
_;
}
// Initalize authorized-owners list and limit
constructor(address[] memory _authorized, uint _numSignaturesRequired){
require(_authorized.length > 0, "Authorized signers required");
for (uint i = 0; i < _authorized.length; i++) {
address owner = _authorized[i];
require(owner != address(0), "Not authorized");
require(!isAuthorized[owner], "Signer not unique");
isAuthorized[owner] = true;
authorized.push(owner);
}
numSignaturesRequired = _numSignaturesRequired;
}
// Functions needed for transfer: create, sign and execute
function createTransfer(address payable _to, uint _amount) onlyAuthorized public {
emit Created(transactionRequests.length, _amount, msg.sender, _to);
transactionRequests.push(Transaction({
to: _to,
amount: _amount,
numSignatures: 0,
executed: false,
txIndex: transactionRequests.length
}));
}
function signTransfer(uint _txIndex) onlyAuthorized txExists(_txIndex) notConfirmed(_txIndex) notExecuted(_txIndex) public {
Transaction storage transaction = transactionRequests[_txIndex];
transaction.numSignatures += 1;
approvals[_txIndex][msg.sender] = true;
emit Signed(_txIndex, transactionRequests[_txIndex].numSignatures, msg.sender);
}
function executeTransfer(uint _txIndex) onlyAuthorized txExists(_txIndex) notExecuted(_txIndex) public {
emit Sent(_txIndex);
Transaction storage transaction = transactionRequests[_txIndex];
require(transaction.numSignatures >= numSignaturesRequired, "Cannot execute transaction");
transaction.executed = true;
transaction.to.transfer(transaction.amount);
}
function getAuthorized() public view returns (address[] memory) {
return authorized;
}
function getTransactionCount() public view returns (uint) {
return transactionRequests.length;
}
function getTransaction(uint _txIndex) public view returns (address to, uint amount, uint numSignatures, bool executed)
{
Transaction storage transaction = transactionRequests[_txIndex];
return (
transaction.to,
transaction.amount,
transaction.numSignatures,
transaction.executed
);
}
// Anyone can deposit
function deposit() public payable returns (uint){
balance[msg.sender] += msg.value;
emit depositCompleted(msg.sender, msg.value);
return balance[msg.sender];
}
//Return individual depositor balance
function getBalance() public view returns (uint){
return balance[msg.sender];
}
//Return contract balance
function getContractBalance() public view returns (uint){
return address(this).balance;
}
}