Here’s my crack at the Multisig Wallet after watching the first video. My implementation allows for any number of owners, but a limited number of paralell proposals. I found that in order to prevent the same owner approving the same transaction multiple times I have to keep track of the owners who already signed. This requires a mapping or a dynamic list. Neither can be assigned in dynamically (in a function), therefore I needed to create a constant (can be arbitrarily large if the deployer has the gas for it) number of mappings to keep track of the signatories to a proposal. I would appreciate any feedback from either @thecil or others.
pragma solidity 0.8.7;
pragma abicoder v2;
contract Pater{
address internal creator; //Good idea to not make the public directly
constructor(){
// This way the contract deployer is always the owner
creator = msg.sender;
}
modifier onlyCreator{
require(msg.sender==creator, "Access Denied");
_; // In practice: Run the function; Compiler code in practice it marks the point of editing
}
}
contract Filii is Pater{
mapping(address => bool) internal ownersMap; // Stores if a given addess is an owner
mapping(address => uint) internal ownersIdMap; // Stores the position of an owner in the ownersList
address[] internal ownersList; // The definitive list of owners
uint internal ownersNum = 0; // Total number of owners
uint internal reqNum = 0;
modifier onlyOwner{
require(ownersMap[msg.sender], "Only Owners can use this function.");
_; // In practice: Run the function; Compiler code in practice it marks the point of editing
}
function addOwner(address _newOwner) public onlyCreator {
require(!ownersMap[_newOwner],"Father, this one is already your son.");
ownersMap[_newOwner] = true;
ownersIdMap[_newOwner] = ownersList.length;
ownersList.push(_newOwner);
ownersNum += 1;
}
function removeOwner(address _fallenOwner) public onlyCreator {
require(ownersMap[_fallenOwner],"Father, this one is no son of yours, You can't banish him.");
ownersMap[_fallenOwner] = false;
delete ownersList[ownersIdMap[_fallenOwner]];
ownersNum -= 1;
}
function setRequired(uint _toSet) public onlyCreator {
require(_toSet<=ownersNum,"Father, not even angels can agree more than unanimously. Do not require that which is impossible.");
reqNum = _toSet;
}
function queryOwners() public view returns(address[] memory) {
return(ownersList);
}
}
contract Multisig is Filii{
struct Request {
address payable recipient;
uint amount;
uint numSupporter;
uint RqId;
bool completed;
//address[] supporters;
}
uint constant maxParallelProposals = 30; // Maximum number of paralel proposals with support in the contract
mapping(address => bool)[maxParallelProposals] private RequestsSupporters; // A predef. array of mappings. Each verify if an address supports the proposal
//propSlotsInuse = 0; // This may not be needeed
mapping(uint => uint) private RqId_to_PropSlot; // mapping of Request Ids to the supported proposal slots
uint[] freeSlots; // Array populated with Ids of the currently free proposal slots
Request[] private RequestsPending; // An array of all requests (pending and completed)
function generateSeries(uint _num) private {
uint i=0;
for(i = 0; i < _num; i++){
freeSlots.push(i);
}
}
constructor(){
generateSeries(maxParallelProposals);
}
event depositDone(uint amount, address indexed AddedTo);
function deposit() public payable returns(uint){
emit depositDone(msg.value, msg.sender);
return address(this).balance;
}
function payout(address payable _to, uint _amount) private {
require(_amount <= address(this).balance,"The contract does not currently hold sufficient balance to fund this request. Repeat attempt when sufficient balance is available.");
_to.transfer(_amount);
}
function assignPropSlot(uint _RqId) private {
uint length=freeSlots.length;
require(length>0,"No free slots left in the pending transactions queue. Try again once some of the pendings transactions were completed.");
RqId_to_PropSlot[_RqId] = freeSlots[length-1];
freeSlots.pop();
}
function createRequest(address payable _to, uint _amount) public onlyOwner{
uint _RqId = RequestsPending.length;
if (reqNum <= 1) {
payout(_to, _amount);
RequestsPending.push(Request(_to,_amount,1,_RqId,true));
}
else {
RequestsPending.push(Request(_to,_amount,1,_RqId,false));
assignPropSlot(_RqId);
RequestsSupporters[RqId_to_PropSlot[_RqId]][msg.sender]=true;
}
}
function clearPropSlot(uint _PropSlot) private{
uint i;
for (i=0; i< ownersList.length;i++){
RequestsSupporters[_PropSlot][ownersList[i]] = false;
}
}
function approveRequest(uint _RqId) public onlyOwner {
uint PropSlot = RqId_to_PropSlot[_RqId];
require(!RequestsSupporters[PropSlot][msg.sender],"You have already supported this proposal, no further action is required."); // For a successful approval, the owner cannot have laready supported the proposal
require(!RequestsPending[_RqId].completed,"This request has been already completed. No further approvals are required or possible.");
//if (RequestsSupporters[PropSlot][msg.sender] == false) {
// RequestsSupporters[PropSlot][msg.sender] = true;
// RequestsPending[_RqId].numSupporter+=1;
//}
RequestsSupporters[PropSlot][msg.sender] = true;
RequestsPending[_RqId].numSupporter+=1;
if (RequestsPending[_RqId].numSupporter >= reqNum) {
payout(RequestsPending[_RqId].recipient, RequestsPending[_RqId].amount);
RequestsPending[_RqId].completed = true;
// popping free slot back in freePropslots
clearPropSlot(PropSlot);
freeSlots.push(PropSlot);
}
}
function queryPendingRequests() public view returns(Request[] memory){
return RequestsPending;
}
}