(Part 11) Ethereum Solidity - Multisig Contract As Bank,k Multiple Users, And Where To Implement App Logic(PT 11)
Repository
https://github.com/igormuba/EthereumSolidityClasses/tree/master/class11
What Will I Learn?
- Make a multisig contract in the didactic form of a "bank"
- Basic multisig security
- Track, add and reduce the user stake (balance) of the multisig contract
Requirements
- Internet connection
- Code editor
- Browser
Difficulty
- Intermediate
Tutorial Contents
It is very likely that, whatever the dapp you are building, it will be designed to hold the balance of multiple users. On this tutorial I will show one simple implementation of a bank to show the pure form of how can you manage the balance of multiple users, allowing them to deposit Ethereum into the bank and withdraw their balance, but not more.
At this moment we won't worry about safe math and security, but on previous tutorials, I mention some security methods that can be useful for a wide range of applications. If you want to read more about security, please, check the tutorials below
https://steemit.com/utopian-io/@igormuba/part-6-ethereum-solidity-custom-varaible-functionalities-libraries-using-libraries-for-security-pt-6
https://steemit.com/utopian-io/@igormuba/part-4-ethereum-solidity-custom-access-modifiers-security-breach-alerts-assert-and-require-pt-4
https://steemit.com/utopian-io/@igormuba/part-2-ethereum-solidity-development-deploying-securiy-and-erc20-compliance-pt-2
I will, however, mention some basic security steps that are simple enough to keep them in mind on any contract you write, like the order at which balances update should take place.;
Base bank contract
As always, I am using the last version of solidity, the version 5, so the skeleton of the contract is
pragma solidity ^0.5.0;
contract bank{
}
Deposit and Withdrawal
Let us think for a moment what functionalities the bank contract will need to do the basic functionality:
- Deposit: receive an amount of value form the user and record on his account on the bank that his balance grew, so that he can withdraw it later
- Withdrawal: send Ethereum form the bank to the user address and reduce the value of his account so he can not withdraw more money than he has deposited
The deposit function can be simplified as a fallback function, I talk about fallback functions on this other tutorial
https://steemit.com/utopian-io/@igormuba/part-7-ethereum-solidity-fallback-ethereum-fractions-and-collateral-backed-contract-pt-7
But in short, a fallback function is the function that is executed when a call is made to the contract and there is no other function on the contract with the same signature, that is, no other function with the same name and that receives the same arguments.
Also, now, the withdrawal function needs to do two things, it needs to ensure that who withdrawals from the bank is a member and that he has enough balance on the bank so that his balance is equal or greater than the amount he wants to withdraw, also it needs to receive as an argument the amount the member of the bank wants to withdraw to, first, compare the value with the amount he has in the account, and then use the number to effectively send Ethereum from the contract to his balance.
The design I thought for them is
pragma solidity ^0.5.0;
contract bank{
function() external payable{ //deposit function is a fallback function
}
function withdrawal(uint amount) public isMember{ //withdrawal with functionalities described above
}
}
Modifier for members and member account
On the withdrawal, we have added a modifier isMember
to ensure that whoever wants to withdraw money from the bank is indeed a member that has balance in his account. Let us implement this modifier.
More about modifiers on my tutorial
https://steemit.com/utopian-io/@igormuba/part-4-ethereum-solidity-custom-access-modifiers-security-breach-alerts-assert-and-require-pt-4
What do our modifier need to do
- Check who wants to withdraw
- See if he has balance in the bank
The design I came out with is
modifier isMember(){
require(_members[msg.sender]>0); //if members balance is less than zero throws an error
_;
}
Now, we need something to hold the members account balance, for this, we can simply use a mapping to map an integer value (that will be his balance in wei) to the address of each user
mapping(address => uint) private _members; //maps a balance to an address
So the design of the bank contract should look like
pragma solidity ^0.5.0;
contract bank{
mapping(address => uint) private _members; //maps a balance to an address
modifier isMember(){
require(_members[msg.sender]>0); //if members balance is less than zero throws an error
_;
}
function() external payable{ //deposit function is a fallback function
}
function withdrawal(uint amount) public isMember{ //withdrawal with functionalities described above
}
}
Managing withdrawals and deposits
We have implemented the functions to manage deposits and withdrawals, but so far they still do nothing, the deposit one receives Ether but does not register the increment in the balance of the user, to fix it we can use the logic
function() external payable{
_members[msg.sender]+=msg.value; //adds the sent value to the member balance
}
And for the withdrawal function
function withdrawal(uint amount) public isMember{
require(_members[msg.sender]>=amount); //thows error if user withdraw more than balance
_members[msg.sender]-=amount; //reduces balance before sending value
msg.sender.transfer(amount); //send the withdraw requested
}
Notice that we use require(_members[msg.sender]>=amount)
to avoid attempts of withdrawing more than the user actually has in balance.
Also, notice that we reduce the balance of the user before sending the value. This step is very important and if you do this in other order you are susceptible to a hack like the DAO hack.
Read more about the DAO hack and the exploit they have used here
http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/
But in short, it was possible because the update of balances was done after the rest of the logic of a function, so be careful with the order you do things!
It is always recommended, when dealing with numbers and balances, to use a safe math library, like the one form Zepellin OS
https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol
So far our code looks like this
pragma solidity ^0.5.0;
contract bank{
mapping(address => uint) private _members; //maps a balance to an address
modifier isMember(){
require(_members[msg.sender]>0); //if members balance is less than zero throws an error
_;
}
function() external payable{
_members[msg.sender]+=msg.value; //adds the sent value to the member balance
}
function withdrawal(uint amount) public isMember{
require(_members[msg.sender]>=amount); //thows error if user withdraw more than balance
_members[msg.sender]-=amount; //reduces balance before sending value
msg.sender.transfer(amount); //send the withdraw requested
}
}
Checking balance
The last step, before doing the testings, is to implement a function to check the user balance, so we can see if it works
function myBalance() public isMember view returns (uint){
return _members[msg.sender];
}
Testing
Using the Ethereum Virtual Machine (testing environment) from http://remix.ethereum.org
I have deployed the contract, now if I send 1000000000000000000 wei (e Ethereum) to the contract let us see what happens
If you don't know how Ethereum fractions (like wei and gwei) work you can see it here
https://steemit.com/utopian-io/@igormuba/part-7-ethereum-solidity-fallback-ethereum-fractions-and-collateral-backed-contract-pt-7
The deposit was sent and the balance is in the bank, now I can make a withdrawal of half of that amount, 500000000000000000 wei
And the balance is updated.
If we try to withdraw more than we have on the bank we will get an error.
NOTE: The transactions on this contract do not take into account the miner fees. In reality, if you deposit 1 Ethereum you need more than 1 Ethereum on your wallet to pay for the fees to deposit, and you can't deposit all of you balance because to call the withdrawal function you need to pay from your caller wallet the fees for the miners
Making the numbers more readable
When implementing a dapp, it is recommended to keep the contract as simple as possible and keep all the conversion logic on the front end of the application, in this case, you could use a landing page with JavaScript logic to convert Ethereum to wei so the contract still works with wei value but the user can make the requests in Ether and fractions
Yes, working with wei is very annoying, it is a very small unit of measurement, the smallest one for Ethereum, just like Satoshi is the smallest unit of measurement for Bitcoin.
You can fix this by making the contract multiply the input for 1000000000000000000 (there are 18 zeroes there if you want to count, there you go, just saved you from the work of doing that), but it is not recommended at all
We could implement a function that works with those numbers, but keep in mind that currently Solidity does not have support for type casting and float.
You can implement a function
function EtherToWei(uint number) pure private returns (uint){
return number*1000000000000000000;
}
And then use that to convert all numbers, but honestly, the best way to do that is on the front end of your application, with JavaScript. The user is not supposed to have access to the contract unless he really knows what he is doing. So I do not recommend implementing unit conversion logic on the contract.
Curriculum
- (Part 10) Ethereum Solidity - Multiple inheritance, Diaomond Problem And Function Polymorphism(PT 10)
- (Part 9) Ethereum Solidity Assembly - Return, Memory, Hexadecimal, Pointers, And Memory Addressing(PT 9)
- (Part 8) Ethereum Solidity - Assembly, Reducing Costs And Creation Your Low-Level Expression(PT 8)
- (Part 7) Ethereum Solidity - Fallback, Ethereum Fractions, And Collateral Backed Contract(PT 7)
- (Part 6) Ethereum Solidity - Custom Variable Functionalities, Libraries, Using Libraries For Security(PT 6)
- (Part 5) Ethereum Solidity - Custom Access Modifiers, Security Breach Alerts, Assert And Require(PT 5)
- (Part 4) Ethereum Solidity Development - Inheritance, Working With Multiple Contracts And Simple Mutability(PT 4)
- (Part 3) Ethereum Solidity Development - Contract Mutability, DelegateCall And Calling Functions By Address(PT 3)
- (Part 2) Ethereum Solidity Development - Deploying, Security And ERC20 Compliance(PT 2)
- (Part 1) Ethereum Solidity Development - Getting Started + Lower Level Explanation (PT 1)
Beneficiaries
This post has as beneficiaries
@utopian.pay with 5%
using the SteemPeak beneficiary tool
Another beautiful tutorial, thank you.
I like the simplicity in delivering the ideas and concepts.
Couple of notes:
Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.
To view those questions and the relevant answers related to your post, click here.
Need help? Chat with us on Discord.
[utopian-moderator]
Hi, thank you for the review and for pointing where I can pay closer attention
Regarding the first point, I tried to approach the characteristic of multisig under the "bank" example to show how different wallets can have different stake on a contract balance, I will further cover this is the future but I need to think of better examples, though I think the bank example was good to show how different stakes can easily be defined on a contract
On your last point, according to the logic I have implement I don't think such error can occur because the only way this could happen is if the user tries to withdraw more than the contract has in balance but I can't think right now how could that happen without triggering errors before the change in balances.
That said, I have warned on the beginning of the tutorial that the goal of the tutorial is to demonstrate a sple implementation of a multisig wallet, not to go us on security or safe math. But if someone wants to be extra sure he can wrap every line of code inside a
require
funtion, that would roll back the function and everything in case an error occursPosted using Partiko Android
Thank you for your review, @mcfarhat! Keep up the good work!
Hi, @igormuba!
You just got a 0.26% upvote from SteemPlus!
To get higher upvotes, earn more SteemPlus Points (SPP). On your Steemit wallet, check your SPP balance and click on "How to earn SPP?" to find out all the ways to earn.
If you're not using SteemPlus yet, please check our last posts in here to see the many ways in which SteemPlus can improve your Steem experience on Steemit and Busy.
Hi @igormuba!
Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server
Hey, @igormuba!
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!
Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).
Want to chat? Join us on Discord https://discord.gg/h52nFrV.
Vote for Utopian Witness!
Congratulations @igormuba! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :
Click here to view your Board
If you no longer want to receive notifications, reply to this comment with the word
STOP
To support your work, I also upvoted your post!
Do not miss the last post from @steemitboard: