Here is my Multi signature wallet.
I have only watched the first video when I made this.
Would appreciate your comments.
pragma solidity 0.7.5;
pragma abicoder v2;
// Define contract
contract MultiSigWallet {
// Creator of the contract / wallet
address owner;
// Co owners of the wallet
address[] coOwners;
// Number of signatures needed per request in order for it to be sent
uint numberOfRequiredSignatures;
// array of the pending transfer requests
TransferRequest[] TransferRequests;
// Total pending amount to be sent
uint pendingRequestAmount;
// Constructor
// _coOwners : The co owners of the wallet that can vote on transfer requests
// _numberOfRequiredSignatures: The number of approved voted needed for a request to be sent
constructor(address[] memory _coOwners, uint _numberOfRequiredSignatures){
// Check so that the _co owners does not contain duplicats
require(!ContainsDuplicate(_coOwners));
// Check so that the creator is not in the list of the co oweners
require(!ContainsAddress(_coOwners, msg.sender), "The creator should not be in the list of co owners");
// Check so that the required number of signatures is not greater than the number of possible signers
require(_numberOfRequiredSignatures > 0 && _numberOfRequiredSignatures <= _coOwners.length + 1, "Not enough owners compared to required number of votes");
// Assign variables
owner = msg.sender;
coOwners = _coOwners;
numberOfRequiredSignatures = _numberOfRequiredSignatures;
}
// Event to be fired when a new transfer request has been created
event OnTransferRequestCreated(uint indexed requestId, address recepient, uint amount);
// Event to be fired when a request recieves a vote
event OnTransferRequestVote(uint indexed requestId, bool approved);
// Event to be fired when a request is approved and transfered
event OnTransferRequestSuccess(uint indexed requestId, address recepient, uint amount);
// Event to be fired when a request has failed due to too many rejections
event OnTransferRequestFailed(uint indexed requestId, address recepient, uint amount);
// Event to be fired when money is deposited into the wallet
event OnDeposit(uint amount, uint balance);
// Definition of a transfer request
struct TransferRequest{
// Id of the request
uint requestId;
// The recepient of the funds
address payable recepient;
// The amount to be sent
uint amount;
// The owners who have voted for the request.
// Note: array is used instead of mapping due to a probable small size of total number of owners
address[] approvedByOwners;
// The owners who have voted against the request.
// Note: array is used instead of mapping due to a probable small size of total number of owners
address[] rejectedByOwners;
// Status of the request
// 0: pending
// 1: sent
// 2: rejected
uint status;
}
// checks if a list contains duplicate entrys
function ContainsDuplicate(address[] memory _list) private pure returns (bool){
// Return false if there are less than 2 elements in the list
if (_list.length < 2){
return false;
}
// Loop through the elements
for(uint i = 0; i < _list.length; i++){
// Loop through each element again
for (uint j = 0; j < _list.length; j++){
// Continue of the iteration variable is the same
if (i == j){
continue;
}
// Return true if the given element matches the compared one
if (_list[i] == _list[j]){
return true;
}
}
}
// If we get here then there are no duplicates so we return false
return false;
}
// This helper function checks if an address is present in an array
function ContainsAddress(address[] memory _list, address _other) private pure returns (bool){
// Loop through the array
for (uint i = 0; i < _list.length; i++ ){
// Return if the address is found in the list
if (_list[i] == _other){
return true;
}
}
// Return false if the address was not found
return false;
}
// Modifier that makes sure that a transfer request with a given id actually is present in the array
modifier requestExists(uint requestId) {
require(TransferRequests.length > requestId,"Request not found");
_;
}
// Modifier that makes sure that a transfer request has the status pending
modifier pendingRequest(uint requestId) {
require(TransferRequests[requestId].status == 0,"Request not in status pending");
_;
}
// Modifier that only allowes the owner or co owners to run the function
modifier onlyOwner {
require(msg.sender == owner || ContainsAddress(coOwners, msg.sender),"User is not authorized");
_;
}
// Modifier that only allowes the function to be run of the user has not already voted
modifier notVoted(uint _requestId) {
// Check if the users address exists in either that list of approvals or rejection for the given request
bool hasVoted = ContainsAddress(TransferRequests[_requestId].approvedByOwners, msg.sender) || ContainsAddress(TransferRequests[_requestId].rejectedByOwners, msg.sender);
// Require that the user has not voted already
require(!hasVoted,"User has already voted on this request");
_;
}
// Creates a new transfer request and adds it to the mapping
// _recepient: The receiving address
// _amount: The proposed amount to send
// Can only be called by owners
function CreateRequest(address payable _recepient, uint _amount) public onlyOwner{
// Check so that an amount is specified
require(_amount > 0, "A request must contain an amount");
// Check so that there are enough funds to send the amount taking in consideration the present requests
require(address(this).balance >= pendingRequestAmount + _amount, "Not enough funds to process this request.");
// Define empty addresses
address[] memory approved;
address[] memory rejected;
// Create the request and add it to the mapping
TransferRequests.push(TransferRequest(TransferRequests.length,_recepient, _amount,approved, rejected, 0));
// Increase the total requested amount
pendingRequestAmount += _amount;
// Send event
emit OnTransferRequestCreated(TransferRequests.length -1 , _recepient, _amount);
}
// Sends a request to the recepeint and then removes it from the mapping
// _requestId: The id of the request to be sent
// Can only be called by owners
function SendRequest(uint _requestId) private onlyOwner requestExists(_requestId) pendingRequest(_requestId){
// Remove the amount of the request from the sum of requested amounts
pendingRequestAmount -= TransferRequests[_requestId].amount;
// Mark the request as sent so that the function can only be called once
TransferRequests[_requestId].status = 1;
// Transfer the amount to the recepient
TransferRequests[_requestId].recepient.transfer(TransferRequests[_requestId].amount);
// Send event
emit OnTransferRequestSuccess(_requestId, TransferRequests[_requestId].recepient, TransferRequests[_requestId].amount);
// Make sure that the request can not be sent again
assert(TransferRequests[_requestId].status == 1);
}
// Adds a vote in favor for a given request
// _requestId: The id of the request to be approved
// Can only be called by owners who have not already voted on pending requests
function ApproveRequest(uint _requestId) public onlyOwner requestExists(_requestId) pendingRequest(_requestId) notVoted(_requestId) {
// Add the address of the user to the list of approvers on the request
TransferRequests[_requestId].approvedByOwners.push(msg.sender);
// Send event about the vote
emit OnTransferRequestVote(_requestId, true);
// Complete the transfer if enough votes now are present for the request
if (TransferRequests[_requestId].approvedByOwners.length >= numberOfRequiredSignatures){
SendRequest(_requestId);
}
}
// Adds a vote against a given request
// _requestId: The id of the request to be rejected
// Can only be called by owners who have not already voted on pending requests
function RejectRequest(uint _requestId) public onlyOwner requestExists(_requestId) pendingRequest(_requestId) notVoted(_requestId){
// Make sure the request is pending
require(TransferRequests[_requestId].status == 0);
// Add the address of the user to the list of party poopers
TransferRequests[_requestId].rejectedByOwners.push(msg.sender);
// Send event about the vote
emit OnTransferRequestVote(_requestId, true);
// Delete the request if there is now not enough voters left to send the request
if (coOwners.length + 1 - TransferRequests[_requestId].rejectedByOwners.length < numberOfRequiredSignatures){
// Send event the the request has failed
emit OnTransferRequestFailed(_requestId, TransferRequests[_requestId].recepient,TransferRequests[_requestId].amount);
// mark request as rejected
TransferRequests[_requestId].status = 2;
}
}
// Function to recieve a deposit to the contract
function Deposit() public payable onlyOwner {
require(msg.value > 0, "No funds recieved");
// Send event about the deposit
emit OnDeposit(msg.value, address(this).balance);
}
// Gets the current balance of the wallet
function GetBalance() public view returns (uint){
return address(this).balance;
}
// Gets the current balance of the wallet
function GetPendingRequestsSum() public view returns (uint){
return pendingRequestAmount;
}
// Gets the pending requests
function GetRequests() public view returns (TransferRequest[] memory){
return TransferRequests;
}
}