it is possible, its just difficult to implement and can have weird side effects when your trying to update items in your struct. i have written a solution that does this before in this forum ill see if i can find it. just a heads up though the mapping is a beter approach. you could even change it if you like to map from a txId to an array of the approved users if thats more what your after.
great work that you go tthere in the end. this certainly is a challenging capstone project for a beginner solidity course. your final solution look spretty good too keep up the good work. one thing i notice is you have a couple of modifiers. you should only ever place code and abstract it into its own modifier if you use that code more than once in your contrct. if a piece of logic is only being used once theres no need to wrap it inside a modifier.
Here is my solution, I started with the basic requests then I thought, if I really have the basics I must be able to do something more so I added details like having a table that updates when a transaction is valid or not.
If itās possible to have a feedback on the work it would be great to note important points like gas consumption, or functions that could be simplified easily etc.
Anyway, thanks to all of you for this introduction to solidity, I had a lot of fun and I could also see another side of ethereum which is coding and I love it, I learned a lot thanks to you
> // SPDX-License-Identifier: UNLICENSED
>
> pragma solidity 0.7.5;
>
> pragma abicoder v2;
>
> /*------------------------description-------------------------
>
> This is a multi-sig portfolio, at the beginning you can choose how many owners you want.
>
> and you must define the number of approvals needed to validate a transaction
>
> After that you can deposit ETH, owner or not
>
> Only owner can request a transation and only onwers can validate it
>
> You can see the currentwaiting list by calling SeePendingTransaction
>
> You can see the current waiting list by calling SeePendingTransaction.
>
> You can see the trx refuse list by calling SeeVAnnulationTransaction
>
> You can see owners of this folio by calling SeeOwner
>
> You can see amount of this SC by calling ETHpool
>
> */
>
> //------------------------storage-declaration----------------------
>
> contract multiSigWallet{
> address [] owner;
> uint nbValidation;
> mapping(address => mapping(uint=> bool)) vote;
> mapping (address => uint)balance;
> mapping (address => bool)answer; // need to use false in entry to say no otherwise remix take true
>
>
> struct transaction{
>
> uint txId;
> uint validations;
> address payable to;
> uint amount;
> uint pending; // 0 invalidate // 1 accepted// 2 // refuse
> }
>
> transaction [] newTransaction;
> transaction [] transactionEnd;
>
> //
> //------------------------event-declaration-------------------------
>
> event _transferRequest(address sender, address payable indexed deposited, uint indexed amount);
> event _addTransaction(address payable indexed deposited, uint indexed amount,uint pending);
>
> //
> //------------------------Main-part---------------------------------
> constructor(address [] memory _owner, uint _nbValidation){
> // check if multiple owners are the same , if it's case, It returns an error
> address [] memory onlyOne = _owner; // we will make copy of the owners array and verify each position if a owner is 2x times in the array
> uint check; // In case of each owner is unique we validated the array owners and the nb of validations needed for a transaction
>
> /*---check owner array---*/
>
> for(uint i; i< _owner.length; i++){
> for(uint j; j< _owner.length; j++){
> if(onlyOne[i] == _owner[j]) check+=1;
> }
> }
> /*---validations---*/
>
> require(check == _owner.length,"error mutliple same owner");
> owner = _owner;
> require(_nbValidation <= owner.length,"exceed amount of owners");
> nbValidation = _nbValidation;
>
> }
>
> modifier onlyOwner{
>
> address ownable; // Create a modifier for being sure function who dispose of this "OnlyOwner" will be accesible only for the owners wallet
> for(uint i =0; i < owner.length; i++){
> if(msg.sender == owner[i]) ownable = msg.sender;
> }
> require(msg.sender == ownable,"You are not an owner");
> _;
> }
>
> function transferRequest(address payable _to, uint _amount) onlyOwner public{
> // This function will add a transaction requested by one of owners wallet
> require(balance[address(this)] >= _amount,"not enough money"); // This function takes 2 parameters(address to send and the amount) //Checks if the amount is available for this request and take back the potential amount from the contrat
> balance[address(this)] -=_amount; // Add the transaction to the "wainting list of transaction who waiting for validation"
> newTransaction.push(transaction(newTransaction.length,0,_to,_amount,0));
> emit _transferRequest(msg.sender,_to,_amount);
>
> }
>
>
>
> function validationTransaction(uint _index, bool _answer)public onlyOwner{
>
> require(vote[msg.sender][_index] == false,"already validated"); // This function is for the validation transaction previously requested
> require(_index < newTransaction.length,"This trx doesn't exist"); // First check if a trx is not yet already vaidated by one of wallet owners & if trx exists
> // If not already validate now we say thats'it by forcing "vote" to "true" and increase validations,
> vote[msg.sender][_index] = true; // to check if the nbVlidation requires are reach.
> newTransaction[_index].validations++; // take the response of the validator TRUE = YES FALSE = NO
> answer[msg.sender] = _answer; // At final check if the trx is approved else refuse trx
> if(newTransaction[_index].validations == nbValidation ){
> if(trxApproved() == nbValidation) executeTransaction(newTransaction,_index);
> else refuseTransaction(newTransaction,_index);
> }
> }
>
> function executeTransaction( transaction [] memory,uint _index ) private{
>
> newTransaction[_index].to.transfer(newTransaction[_index].amount); //This function is executed if owners validate the trx requested
> updateTrxExecuted(_index); // Execute the transaction selected by the index from the transaction requested list (SeePendingTransaction)
> updatePendingTransaction(_index); // make an update since the trx is executed of the owner vote, the transaction requested list, -->
> updateVote(_index); //--> the pending trx from the struct (transation)
> emit _addTransaction(transactionEnd[_index].to,transactionEnd[_index].amount,transactionEnd[_index].pending);
>
> }
>
>
>
> function refuseTransaction( transaction [] memory,uint _index) private{
>
> balance[address(this)] += newTransaction[_index].amount; //This function is executed if owners refuse the trx requested
> updateTrxRefuse(_index); // Refund the SC balance with the amount from the requested trx
> updatePendingTransaction(_index); // Refuse the transaction selected by the index from the transaction requested list (SeePendingTransaction)
> updateVote(_index); // make an update since the trx is refused of the owner vote, the transaction requested list, -->
> emit _addTransaction(transactionEnd[_index].to,transactionEnd[_index].amount,transactionEnd[_index].pending); //--> the pending trx from the struct (transation)
>
> }
>
> function trxApproved()private view returns(uint){
>
> uint cnt; //This function give us the "approval/refuse" of the owners about a requested transaction
> for(uint i = 0; i<= owner.length-1; i++){ // Everytime this function is call, It count the number of approval transaction and return it
> // with this return we can check if the majority validate or refuse a specific trx
> if(answer[owner[i]] == true) cnt++;
> }
> return cnt;
> }
>
> function updatePendingTransaction(uint _index) private returns(transaction [] memory){
> // This function make an update of the array after a trx is validated or refused
> for(uint i =_index; i<newTransaction.length-1;i++){ //Because at the time is validated or not, this trx has been treated
> newTransaction[i] = newTransaction[i+1]; // the logic behind is to copy the actual array from the trx selected and cut the last one position
> newTransaction[i].txId =i;
> }
> newTransaction.pop();
> return newTransaction;
> }
>
> function updateTrxExecuted(uint _index) private{
>
> transaction memory trxSent = newTransaction[_index]; // This function update the transaction.pending to 1 because executed
> trxSent.pending =1;
> transactionEnd.push(trxSent);
> }
>
> function updateTrxRefuse(uint _index) private{
>
> transaction memory trxSent = newTransaction[_index]; // This function update the transaction.pending to 2 because refused
> trxSent.pending =2;
> transactionEnd.push(trxSent);
> }
>
> function updateVote(uint _index) private{
>
> vote[owner[i]][_index] = false;
> }
>
> function SeeValidateTransaction() public view onlyOwner returns(transaction [] memory){
>
> uint _index; // This function will return all transation who are validated tracking by transation.pending
> for(uint i=0; i< transactionEnd.length;i++){
> if(transactionEnd[i].pending == 1) _index++;
> }
>
> transaction [] memory validateArray = new transaction[](_index);
> _index =0;
>
> for(uint i=0; i< transactionEnd.length;i++){
> if(transactionEnd[i].pending == 1) {
> validateArray[_index] = transactionEnd[i];
> _index++;
> }
> }
> return validateArray;
> }
>
> function SeeAnnulationTransaction() public view onlyOwner returns(transaction [] memory){
>
> uint _index; // This function will return all transation who are refused tracking by transation.pending
> for(uint i=0; i< transactionEnd.length;i++){
> if(transactionEnd[i].pending == 2) _index++;
> }
>
> transaction [] memory validateArray = new transaction[](_index);
> _index =0;
> for(uint i=0; i< transactionEnd.length;i++){
> if(transactionEnd[i].pending == 2) {
> validateArray[_index] = transactionEnd[i];
> _index++;
> }
> }
> return validateArray;
> }
>
> function seeOwner()public view returns(address []memory){
> return owner;
> }
>
> function deposit() external payable{
> balance[address(this)] += msg.value;
> }
>
> function ETHpool() public onlyOwner view returns(uint){
> return balance[address(this)];
> }
>
> function SeePendingTransaction() public view onlyOwner returns(transaction [] memory){
> return newTransaction;
> }
> }
pragma solidity 0.8.7;
pragma abicoder v2;
contract Wallet {
address[] public owners;
uint limit;
struct Transfer{
uint amount;
address payable reciever;
uint approvals;
bool hasBeenSent;
uint id;
}
event TransferRequestCreated(uint _id, uint _amount, address _initiator, address _reciever);
event approvalRecieved(uint _id, uint _approvals, address _approver);
event TransferApproved(uint _id);
Transfer[] transferRequests;
mapping(address => mapping(uint => bool)) approvals;
modifier onlyOwners {
bool owner = false;
for(uint i=0; i<owners.length;i++){
if(owners[i] == msg.sender){
owner = true;
}
}
require(owner == true);
_;
}
constructor(address[] memory _owners, uint _limit){
require(_owners[0] != _owners[1], "the addresses must be different");
require(_owners[1] != _owners[2], "the addresses must be different");
owners = _owners;
limit = _limit;
}
function deposit() public payable returns (uint) {
}
function getBalance() public view onlyOwners returns(uint) {
return msg.sender.balance;
}
function createTransfer(uint _amount, address payable _reciever) public onlyOwners {
require(msg.sender.balance >= _amount, "balance must be greater than the send amount");
emit TransferRequestCreated(transferRequests.length, _amount, msg.sender, _reciever);
transferRequests.push(
Transfer(_amount, _reciever, 0, false, transferRequests.length)
);
}
function approve(uint _id) public onlyOwners {
require(approvals[msg.sender][_id] == false);
require(transferRequests[_id].hasBeenSent == false);
approvals[msg.sender][_id] = true;
transferRequests[_id].approvals++;
emit approvalRecieved(_id, transferRequests[_id].approvals, msg.sender);
if(transferRequests[_id].approvals >= limit){
transferRequests[_id].hasBeenSent = true;
transferRequests[_id].reciever.transfer(transferRequests[_id].amount);
emit TransferApproved(_id);
}
}
function getTransferRequests() public view returns (Transfer[] memory){
return transferRequests;
}
}
Thanks, I will keep the better approach in mind, however it might be interesting to see how that could have been done. if you canāt find it no worries
Iām getting this error when my approval count is getting reached. Iām adding owners dynamically and updating limit depending upon the number of owners.
pragma solidity 0.7.5;
contract MultiSig{
address[] owners;
uint public limit;
address payable contractBalance;
struct Transfer{
uint id;
uint approvalCount;
address[] approvers;
uint amount;
address payable recipient;
}
modifier ownerRestricted(){
bool isOwner = false;
for (uint i = 0; i < owners.length; i++ ){
if(msg.sender == owners[i]){
isOwner = true;
}
}
require(isOwner == true );
_;
}
Transfer[] transfers;
function depositEther( ) public payable{
contractBalance.transfer(msg.value);
}
function addOwners(address _owner ) public {
bool alreayAnOwner = true;
for(uint i = 0; i < owners.length ; i++ ){
if( owners[i] == _owner ){
alreayAnOwner = false;
}
}
require(alreayAnOwner == true, "Already an owner");
owners.push(_owner);
changeLimit();
}
function changeLimit() private{
if( owners.length %2 == 0 ){
limit = ( owners.length / 2 );
}
else{
limit = ( owners.length / 2 ) + 1;
}
}
function createTransfer(uint _amount, address payable _receiver) public ownerRestricted{
address[] memory empty;
transfers.push( Transfer (transfers.length,0, empty ,_amount, _receiver));
}
function approveTransfer( uint _id ) public ownerRestricted{
bool alreadyNotApproved = true;
for( uint i = 0; i < transfers[_id].approvers.length; i++ ){
if(transfers[_id].approvers[i] == msg.sender){
alreadyNotApproved = false;
}
}
if( transfers[_id].approvers.length == 0 ){
alreadyNotApproved = true;
}
require(alreadyNotApproved == true, " Here");
transfers[_id].approvers.push(msg.sender);
transfers[_id].approvalCount++;
if( transfers[_id].approvalCount == limit){
transfers[_id].recipient.transfer(transfers[_id].amount );
}
}
function getTransfer(uint _id) public view returns( uint, uint, address[] memory, uint, address ){
return (transfers[_id].id, transfers[_id].approvalCount,transfers[_id].approvers, transfers[_id].amount, transfers[_id].recipient);
}
function getBalanceOfContract() public view ownerRestricted returns (uint){
return contractBalance.balance;
}
}
I did my multisig wallet before looking at the āProject Assistance.ā Had to re-write some of my wallet when I found the two additional requirements in the template:
- An owner should not be able to vote twice.
- An owner should not be able to vote on a transfer request that has already been sent.
Hereās my updated multisig wallet.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
pragma abicoder v2;
/**
* @title Multisig Wallet
* @dev Multisig Wallet Smart Contract where multiple signatures/approvals
* are needed for an outgoing transfer to take place. Anyone can deposit funds.
* As soon as we want to spend funds, it requires the specified number of approvals.
*/
contract Multisig_Wallet {
//State VARs
uint requiredSignatures;
//Arrays
address[] public owners;
Transfer[] transferRequests; //Store all transfer request
//Only allow people in the owners list to continue the execution of transfers.
modifier onlyOwners(){
bool owner = false;
for(uint i=0; i<owners.length;i++){
if (owners[i] == msg.sender){
owner = true;
}
}
require(owner == true);
_;
}
//Constructor
constructor(address[] memory _owners, uint _requiredSignatures) {
owners = _owners;
requiredSignatures = _requiredSignatures;
}
//Mappings
mapping (address => uint) balance;
mapping(address => mapping(uint => bool)) approvals; //each address points to a mapping where you can input the ID of the request and it returns true/false
//Struct - Keeps record of address, approvals, amounts.
struct Transfer{
uint amount; //amount to be transferred
address payable receiver; //payable receiving address
uint approvals; //current number of signed approvals
bool hasBeenSent; //flag whether transfer has already been sent
uint id;
}
//event logs
event deposited (uint indexed amount, address indexed depositedTo);
event transferedAmount(address indexed from, address indexed to, uint indexed amount);
event TransferRequestCreated(uint _id, uint _amount, address _initiator, address _receiver);
event ApprovalReceived(uint _id, uint _approvals, address _approver);
event TransferApproved(uint _id);
/**
* @dev A method to make a deposit.
* @return balance of the sender.
*/
function deposit() public payable returns (uint) {
balance[msg.sender] += msg.value;
emit deposited (msg.value, msg.sender); //log event
return balance[msg.sender];
}
/**
* @dev A method that creates an instance of the Transfer struct and push it into the
* transferRequests array
*/
function createTransfer(uint _amount, address payable _receiver) public onlyOwners{
require(balance[msg.sender]>= _amount,"ERROR1 createTransfer function: Insufficient balance to transfer funds."); //Check balance of msg.sender
require(msg.sender != _receiver,"ERROR 2 createTransfer function: The sender cannot be the recipient.");
emit TransferRequestCreated(transferRequests.length, _amount, msg.sender, _receiver);
transferRequests.push(
Transfer(_amount, _receiver, 0, false, transferRequests.length)
);
}
/**
* @dev A method to get the sender's balance.
* @return balance of the sender.
*/
function getBalance() public view returns (uint){
return balance[msg.sender];
}
/**
* @dev A method to approve a transfer by an owner. It does the following:
* - Sets your approval for one of the transfer requests.
* - Updates the Transfer object.
* - Updates the mapping to record the approval for the msg.sender.
* - When the amount of approvals for a transfer has reached the requiredSignatures, this function sends the transfer to the recipient.
* - An owner is not allowed to vote twice.
* - An owner is not allowed to vote on a tranfer request that has already been sent.
*/
function approve(uint _id) public onlyOwners {
require(approvals[msg.sender][_id] == false, "ERROR 3_approve function: Owners are not allowed to vote twice.");
require(transferRequests[_id].hasBeenSent == false, "ERROR 4_approve function: Owners are not allowed to vote on a transfer request that has already been sent.");
approvals[msg.sender][_id] = true;
transferRequests[_id].approvals++;
emit ApprovalReceived(_id, transferRequests[_id].approvals, msg.sender);
if(transferRequests[_id].approvals >= requiredSignatures){
transferRequests[_id].hasBeenSent = true;
transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
emit TransferApproved(_id);
}
}
/**
* @dev A method to get transfer requests array information.
* @return transferRequest array.
*/
function getTransferRequests() public view returns (Transfer[] memory){
return transferRequests;
}
}
I have watched the video Project intro. I donāt understand what struct test means. I hv tried to do some research. But I canāt find what a struct test is. Can someone explain please?
Is not about what struct test means, itās just an struct
object with name test
(it could be call what ever you want, but for the example porpuse, it just call test
).
You might want to refresh what struct
is: https://academy.moralis.io/lessons/structs-3
Carlos Z
pragma solidity 0.7.5;
pragma abicoder v2;
contract Wallet{
address[] owners;
uint approvalThreshold;
modifier onlyOwner {
bool isOwner = false;
for (uint i=0; i < owners.length; i++){
if(msg.sender==owners[i]){
isOwner = true;
}
}
require(isOwner, "You must be an owner to do this");
_;
}
constructor(address[] memory _owners, uint _approvalThreshold){
owners = _owners;
approvalThreshold = _approvalThreshold;
}
mapping (address => uint) balance;
event depositCompleted(uint amount, address indexed depositedTo);
event transferCreated(uint transferID, address indexed initiator, address recipient, uint amount);
event transferExecuted(uint transferID, address initiator, address indexed recipient, uint indexed amount, address[] transferApprovals);
struct Transfer {
uint transferID;
address initiator;
address payable recipient;
uint transferAmount;
bool transferSent;
address[] transferApprovals;
}
Transfer[] transfers;
function deposit() public payable returns(uint){
require(msg.value > 0, "Please deposit something");
balance[msg.sender] += msg.value;
emit depositCompleted(msg.value, msg.sender);
return balance[msg.sender];
}
function viewAddressBalance() public view returns(uint){
return balance[msg.sender];
}
function viewWalletBalance() public view returns(uint){
return address(this).balance;
}
function createTransfer(address payable _recipient, uint _transferAmount) public onlyOwner {
require(address(this).balance >= _transferAmount, "Insufficient balance");
require(_transferAmount > 0, "Transfer something please");
transfers.push(Transfer(transfers.length, msg.sender, _recipient, _transferAmount, false, new address[](0x0)));
emit transferCreated(transfers.length, msg.sender, _recipient, _transferAmount);
}
function viewTransfer(uint _transferID) public view returns(Transfer memory){
return(transfers[_transferID]);
}
function approveTransfer(uint _transferID) public onlyOwner {
require(transfers[_transferID].transferSent != true, "You cannot approve a completed transfer");
bool signatory = false;
for (uint i=0; i < transfers[_transferID].transferApprovals.length; i++){
if(msg.sender==transfers[_transferID].transferApprovals[i]){
signatory = true;
}
}
require(signatory != true, "You have already approved this transaction");
transfers[_transferID].transferApprovals.push(msg.sender);
if((transfers[_transferID].transferApprovals.length) == approvalThreshold){
executeTransfer(_transferID);
}
}
function executeTransfer(uint _transferID) private onlyOwner {
uint oldBalance = address(this).balance;
(transfers[_transferID].recipient).transfer((transfers[_transferID].transferAmount));
assert(address(this).balance == oldBalance - transfers[_transferID].transferAmount);
transfers[_transferID].transferSent = true;
emit transferExecuted(_transferID, transfers[_transferID].initiator, transfers[_transferID].recipient,
transfers[_transferID].transferAmount, (transfers[_transferID].transferApprovals));
}
}
Is this final project code still working today? I tried making my own solution and kept getting the same error, so I decided to test the final project code and I get the same error there.
I can always approve the first transfer, but when I switch to another owner account and try to approve the transfer again it fails. Im 100% sure Im approving with the owners accounts
āThe transaction has been reverted to the initial state.
Note: The called function should be payable if you send value and the value you send should be less than your current balance.
Debug the transaction to get more information.ā
I made it so that anyone can make a transfer request but only owners can approve them
pragma solidity 0.8.16;
import "./ownable.sol";
contract Wallet is Ownable {
struct Transfer{
uint id;
uint amount;
address payable receiver;
uint approvals;
bool isDone;
}
Transfer[] requests;
mapping(uint => mapping(address => bool)) approvals;
constructor(address[] memory _owners, uint _approvalLimit) {
owners = _owners;
approvalLimit = _approvalLimit;
}
function deposit() public payable {}
function getTransferRequests() public view returns (Transfer[] memory){
return requests;
}
function requestTransfer(uint _amount, address payable _addressTo) public {
requests.push(Transfer(requests.length, _amount, _addressTo, 0, false));
}
function approveTransfer(uint _id) public onlyOwners{
require(approvals[_id][msg.sender] == false, "Transfer already approved by this address.");
require(requests[_id].isDone == false, "Transfer is already done.");
requests[_id].approvals++;
approvals[_id][msg.sender] = true;
if(requests[_id].approvals >= approvalLimit){
(bool success, ) = requests[_id].receiver.call{value: requests[_id].amount}("");
require(success, "Transaction failed.");
requests[_id].isDone = true;
}
}
}
Could you please also share the ownable
contract?
Carlos Z
contract Ownable {
address[] owners;
uint public approvalLimit;
modifier onlyOwners () {
require(isOwner(msg.sender));
_;
}
function isOwner(address _wallet) private view returns (bool){
for(uint i = 0; i < owners.length; i++){
if(owners[i] == _wallet) return true;
}
return false;
}
}
You contract does worked for me.
- I deployed the contract with 3 addresses and approval limit of 3.
["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db"]
- Account 1 deposit 1 eth.
- Account 1 request to transfer 0.1eth to Account 2.
- Account 1 sign transaction id 0.
- Account 2 and 3 sign transaction id 0.
- Requested transaction
isDone
changed to true.
Carlos Z
Good, it works for me too
This has been a fun exercise and has helped me learn more about using solidity through practice.
Being able to use mappings within a struct seems like an easier way to solve this but is not supported in newer versions of solidity. I found that out after google searching and testing things out. Is there a way to try to do that or do you just have to accept this and always assume you can try to wrap mappings with structs.
pragma solidity 0.8.16;
pragma abicoder v2;
contract Multisig {
// Declare variables
address[] private owners;
mapping(address => bool) public isOwner;
uint signatories;
struct Transaction {
address payable to;
uint value;
bool executed;
uint numberOfSignatures;
uint id;
}
Transaction[] public transactions;
mapping(address => mapping(uint => bool)) approvalSignatures;
// Define Constructor
constructor(address[] memory _owners, uint _signatories){
require( _owners.length > 1, "minimal number of owners (2) required");
require( _signatories > 1 && _signatories <= _owners.length, "invalid number of required signatories");
for (uint 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);
}
signatories = _signatories;
}
//Should only allow people in the owners list to continue the execution.
modifier onlyOwners(){
require(isOwner[msg.sender] , "not owner"); // Modifier only executes if owner
_;
}
// Retrieve Owners
function getOwners() public view returns ( address[] memory ){
return owners;
}
//Define Events
event Deposit(address indexed sender, uint amount, uint balance);
event SubmitTransaction(
address indexed owner,
uint indexed txIndex,
address indexed to,
uint value
);
event ApproveTransaction(address indexed owner, uint indexed txIndex);
event ExecuteTransaction(address indexed owner, uint indexed txIndex);
event RevokeApproval(address indexed owner, uint indexed txIndex);
// Retrieve Required number of signatories
function getSignaturesRequired() public view returns (uint ){
return signatories;
}
// Enable Depostis
function deposit() public payable {}
//Submit a Transaction
function submitTransaction(address payable _to, uint _value) public onlyOwners{
uint _txIndex = transactions.length;
transactions.push(
Transaction(_to, _value, false, 0 , _txIndex )
);
emit SubmitTransaction(msg.sender, _txIndex, _to, _value );
}
//Approve Transaction
function approveTransaction(uint _txID ) public onlyOwners{
require(approvalSignatures[msg.sender][_txID] == false);
require(transactions[_txID].executed == false);
approvalSignatures[msg.sender][_txID] = true;
transactions[_txID].numberOfSignatures++;
emit ApproveTransaction(msg.sender, _txID);
}
//Execute a Transaction
function executeTransaction(uint _txID ) public onlyOwners{
require(transactions[_txID].executed == false);
require(transactions[_txID].numberOfSignatures == 2);
transactions[_txID].to.transfer(transactions[_txID].value);
transactions[_txID].executed = true;
emit ExecuteTransaction(msg.sender, _txID);
}
//Revoke Approval
function revokeApproval(uint _txID ) public onlyOwners{
require(transactions[_txID].executed == false);
require(transactions[_txID].numberOfSignatures >= 1);
require(approvalSignatures[msg.sender][_txID] == true);
approvalSignatures[msg.sender][_txID] = false;
transactions[_txID].numberOfSignatures--;
emit RevokeApproval(msg.sender, _txID);
}
}
Hi everyone, here is my code. Itās completely different than Filipās but it works. I did not use double mapping even though double mapping seems useful.
The only thing that i canāt figure out is it seems i cannot call some of the view functions from other functions. For example, every time the transaction is approved, I want the āpending transactionsā list to refresh automatically without having to click on the button āgetPendingTxā each time.
Does anyone know how to do that? It seemed simple, but for some reason it doesnāt work the way i wanted it.
pragma solidity 0.7.5;
pragma abicoder v2;
// SPDX-License-Identifier: UNLICENSED
contract myWallet {
address[] owners;
address transactionSender;
mapping(address=>uint) ownersContribution;
uint txCount;
struct Transaction{
address from;
address payable to;
uint amount;
uint txId;
uint txApproved;
address approver1;
address approver2;
}
Transaction[] pendingTx;
Transaction[] completedTx;
mapping(uint=>Transaction) TxId;
modifier onlyOwner{
require(msg.sender==owners[0] || msg.sender==owners[1] || msg.sender==owners[2]);
_;
}
constructor(){
owners.push(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
owners.push(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2);
owners.push(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db);
txCount=0;
}
function getOwners() public view returns(address[] memory){
return(owners);
}
function Deposit() public payable {
ownersContribution[msg.sender]+=msg.value;
}
function getWalletBalance() public view returns(uint){
return(address(this).balance);
}
function getOwnersContribution() public view returns(uint, uint, uint){
return(ownersContribution[0x5B38Da6a701c568545dCfcB03FcB875f56beddC4], ownersContribution[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2], ownersContribution[0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db]);
}
function ApproveTransfer(uint _txId)public onlyOwner returns(Transaction[] memory){
require(TxId[_txId].txApproved<2, "transaction already approved");
require(msg.sender!=TxId[_txId].from, "you cannot approve your own transaction");
require((msg.sender!=TxId[_txId].approver1) && (msg.sender!=TxId[_txId].approver2), "you already approved this transaction");
if(TxId[_txId].txApproved==0){
TxId[_txId].approver1=msg.sender;
}
else{
TxId[_txId].approver2=msg.sender;
}
TxId[_txId].txApproved+=1;
if(TxId[_txId].txApproved==2){
sendFunds(TxId[_txId].to, TxId[_txId].amount);
completedTx.push(TxId[_txId]);
remove(getIndex(TxId[_txId].txId));
getCompletedTx();
getPendingTransaction();
}
return(pendingTx);
}
function Transfer(address payable recepient, uint amount) public onlyOwner {
pendingTx.push(Transaction(msg.sender, recepient, amount, txCount, 0,address(0), address(0) ));
TxId[txCount]=Transaction(msg.sender, recepient, amount, txCount, 0,address(0), address(0));
txCount++;
getCompletedTx();
getPendingTransaction();
}
function getPendingTransaction() public view returns(Transaction[] memory){
return(pendingTx);
}
function sendFunds(address payable _to, uint _amount) private {
_to.transfer(_amount);
}
function getCompletedTx() public view returns(Transaction[] memory){
return(completedTx);
}
function getIndex(uint _txId)public view returns(uint){
uint index;
for (uint i=0; i<pendingTx.length; i++){
if(pendingTx[i].txId==_txId){
index=i;
}
}
return(index);
}
function remove(uint _index) private {
for (uint i = _index; i<pendingTx.length-1; i++){
pendingTx[i] = pendingTx[i+1];
}
delete pendingTx[pendingTx.length-1];
pendingTx.pop();
}
}
@mcgrane5
I am a bit new to programming (even after the Javascript course). Here is my wallet, and it probably looks funny to more experienced programmers!
contract MultiSigWallet {
address owner1 = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
address owner2 = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
address owner3 = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;
string approveTx1[1];
string approveTx2[1];
string approveTx3[1];
mapping(address => uint) balance;
event depositDone(uint amount, address indexed depositedTo);
function deposit() public payable returns (uint) {
balance[msg.sender] += msg.value;
emit depositDone(msg.value, msg.sender);
return balance[msg.sender];
}
function approve(string _yesOrNo) private returns(string) {
require(msg.sender = owner1);
approveTx1.push(_yesOrNo);
}
function approve(string _yesOrNo) private returns(string) {
require(msg.sender = owner2);
approveTx2.push(_yesOrNo);
}
function approve(string _yesOrNo) private returns(string) {
require(msg.sender = owner3);
approveTx3.push(_yesOrNo);
}
function withdraw(uint amount) public onlyOwner returns (uint){
require(balance[msg.sender] >= amount);
require(
approveTx1[yes] && approveTx2[yes]
|| approveTx1[yes] && approveTx3[yes]
|| approveTx2[yes] && approveTx3[yes]
);
msg.sender.transfer(amount);
return balance[msg.sender];
}
function getBalance() public view returns (uint){
return balance[msg.sender];
}
function transfer(address recipient, uint amount) public {
require(balance[msg.sender] >= amount, "Balance not sufficient");
require(msg.sender != recipient, "Don't transfer money to yourself");
uint previousSenderBalance = balance[msg.sender];
_transfer(msg.sender, recipient, amount);
govermentInstance.addTransaction(msg.sender, recipient, amount);
assert(balance[msg.sender] == previousSenderBalance - amount);
}
function _transfer(address from, address to, uint amount) private {
balance[from] -= amount;
balance[to] += amount;
}
}
pragma solidity 0.7.5;
pragma abicoder v2;
// 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2, 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db
contract MultiSig {
struct Transaction {
uint id;
address multisig0;
address multisig1;
address recipient;
uint amount;
uint confs;
}
Transaction[] private transactions;
uint private balance;
uint private pendingBalance;
address[3] private multisig;
event newTransaction(uint id, address signatory, address to, uint amount);
event newTransactionConf(uint id, address signatory);
event paymentTransfer(uint id, address to, uint amount);
constructor(address _multisig0, address _multisig1, address _multisig2) {
require(_multisig0 != _multisig1 && _multisig0 != _multisig2 && _multisig1 != _multisig2, "Must have 3 different signatory addresses.");
multisig = [_multisig0, _multisig1, _multisig2];
balance = 0;
pendingBalance = 0;
}
modifier onlyOwner() {
require(msg.sender == multisig[0] || msg.sender == multisig[1] || msg.sender == multisig[2], "Function not initiated by contract owner.");
_;
}
function setMultisig(uint _index, address _newMultisig) public onlyOwner {
multisig[_index] = _newMultisig;
}
function getMultisig() public view returns(address[3] memory)
{
return multisig;
}
function getBalance() public view returns(uint) {
return balance;
}
function getPendingBalance() public view returns(uint) {
return pendingBalance;
}
function receiveFunds() public payable{
balance += msg.value;
pendingBalance += msg.value;
}
function createTransaction(address _recipient, uint _amount) public onlyOwner {
Transaction memory _newTransaction;
require(msg.sender != _recipient, "Cannot send to signatories.");
require(balance >= _amount && pendingBalance >= _amount, "Not enough funds to transact.");
_newTransaction.id = transactions.length;
_newTransaction.multisig0 = msg.sender;
_newTransaction.multisig1 = address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
_newTransaction.recipient = _recipient;
_newTransaction.amount = _amount;
_newTransaction.confs = 1;
transactions.push(_newTransaction);
pendingBalance -= _amount;
emit newTransaction(_newTransaction.id, msg.sender, _newTransaction.recipient, _newTransaction.amount);
}
function confirmTransaction(uint _id) public onlyOwner {
require(transactions[_id].confs < 2, "Transaction already confirmed.");
require(transactions[_id].multisig0 != address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4), "!!Error: MultiSig0 is a 0 address!!");
require(transactions[_id].multisig1 == address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4), "!!Error: MultiSig1 is not a 0 address!!");
require(msg.sender != transactions[_id].multisig0, "Transaction already signed by this signatory.");
transactions[_id].multisig1 = msg.sender;
transactions[_id].confs = 2;
emit newTransactionConf(_id, msg.sender);
processTransaction(transactions[_id].id, transactions[_id].recipient, transactions[_id].amount);
}
function getTransaction(uint _id) public view returns(uint id, address multisig0, address multisig1, address recipient, uint amount, uint confs) {
return(transactions[_id].id, transactions[_id].multisig0, transactions[_id].multisig1, transactions[_id].recipient, transactions[_id].amount, transactions[_id].confs);
}
function processTransaction(uint _id, address _recipient, uint _amount) public payable onlyOwner {
address payable sendTransfer = address(uint160(_recipient));
balance -= _amount;
sendTransfer.transfer(_amount);
emit paymentTransfer(_id, _recipient, _amount);
}
function toWei(uint _ether) public pure returns(uint) {
uint _wei = _ether * 1e18;
return _wei;
}
}