mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2024-12-23 15:24:09 +03:00
Security additions + edits (#2281)
* Edits with a focus on security risks * Modified example * Flip sign - example of the risk of Solidity code when published to an append only ledger * Fixed formatting
This commit is contained in:
parent
bf32d58d75
commit
eaf0307775
@ -8,7 +8,7 @@ contributors:
|
|||||||
|
|
||||||
Solidity lets you program on [Ethereum](https://www.ethereum.org/), a
|
Solidity lets you program on [Ethereum](https://www.ethereum.org/), a
|
||||||
blockchain-based virtual machine that allows the creation and
|
blockchain-based virtual machine that allows the creation and
|
||||||
execution of smart contracts, without needing centralized or trusted parties.
|
execution of smart contracts, without requiring centralized or trusted parties.
|
||||||
|
|
||||||
Solidity is a statically typed, contract programming language that has
|
Solidity is a statically typed, contract programming language that has
|
||||||
similarities to Javascript and C. Like objects in OOP, each contract contains
|
similarities to Javascript and C. Like objects in OOP, each contract contains
|
||||||
@ -18,8 +18,17 @@ global variables.
|
|||||||
|
|
||||||
Some Ethereum contract examples include crowdfunding, voting, and blind auctions.
|
Some Ethereum contract examples include crowdfunding, voting, and blind auctions.
|
||||||
|
|
||||||
|
There is a high risk and high cost of errors in Solidity code, so you must be very careful to test
|
||||||
|
and slowly rollout. WITH THE RAPID CHANGES IN ETHEREUM, THIS DOCUMENT IS UNLIKELY TO STAY UP TO
|
||||||
|
DATE, SO YOU SHOULD FOLLOW THE SOLIDITY CHAT ROOM AND ETHEREUM BLOG FOR THE LATEST. ALL CODE HERE IS
|
||||||
|
PROVIDED AS IS, WITH SUBSTANTIAL RISK OF ERRORS OR DEPRECATED CODE PATTERNS.
|
||||||
|
|
||||||
|
Unlike other code, you may also need to add in design patterns like pausing, deprecation, and
|
||||||
|
throttling usage to reduce risk. This document primarily discusses syntax, and so excludes many
|
||||||
|
popular design patterns.
|
||||||
|
|
||||||
As Solidity and Ethereum are under active development, experimental or beta
|
As Solidity and Ethereum are under active development, experimental or beta
|
||||||
features are explicitly marked, and subject to change. Pull requests welcome.
|
features are typically marked, and subject to change. Pull requests welcome.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// First, a simple Bank contract
|
// First, a simple Bank contract
|
||||||
@ -40,6 +49,7 @@ contract SimpleBank { // CamelCase
|
|||||||
// Declare state variables outside function, persist through life of contract
|
// Declare state variables outside function, persist through life of contract
|
||||||
|
|
||||||
// dictionary that maps addresses to balances
|
// dictionary that maps addresses to balances
|
||||||
|
// always be careful about overflow attacks with numbers
|
||||||
mapping (address => uint) private balances;
|
mapping (address => uint) private balances;
|
||||||
|
|
||||||
// "private" means that other contracts can't directly query balances
|
// "private" means that other contracts can't directly query balances
|
||||||
@ -49,7 +59,7 @@ contract SimpleBank { // CamelCase
|
|||||||
// 'public' makes externally readable (not writeable) by users or contracts
|
// 'public' makes externally readable (not writeable) by users or contracts
|
||||||
|
|
||||||
// Events - publicize actions to external listeners
|
// Events - publicize actions to external listeners
|
||||||
event DepositMade(address accountAddress, uint amount);
|
event LogDepositMade(address accountAddress, uint amount);
|
||||||
|
|
||||||
// Constructor, can receive one or many variables here; only one allowed
|
// Constructor, can receive one or many variables here; only one allowed
|
||||||
function AcmeBank() {
|
function AcmeBank() {
|
||||||
@ -65,7 +75,7 @@ contract SimpleBank { // CamelCase
|
|||||||
// no "this." or "self." required with state variable
|
// no "this." or "self." required with state variable
|
||||||
// all values set to data type's initial value by default
|
// all values set to data type's initial value by default
|
||||||
|
|
||||||
DepositMade(msg.sender, msg.value); // fire event
|
LogDepositMade(msg.sender, msg.value); // fire event
|
||||||
|
|
||||||
return balances[msg.sender];
|
return balances[msg.sender];
|
||||||
}
|
}
|
||||||
@ -76,11 +86,14 @@ contract SimpleBank { // CamelCase
|
|||||||
/// @return The balance remaining for the user
|
/// @return The balance remaining for the user
|
||||||
function withdraw(uint withdrawAmount) public returns (uint remainingBal) {
|
function withdraw(uint withdrawAmount) public returns (uint remainingBal) {
|
||||||
if(balances[msg.sender] >= withdrawAmount) {
|
if(balances[msg.sender] >= withdrawAmount) {
|
||||||
|
// Note the way we deduct the balance right away, before sending - due to
|
||||||
|
// the risk of a recursive call that allows the caller to request an amount greater
|
||||||
|
// than their balance
|
||||||
balances[msg.sender] -= withdrawAmount;
|
balances[msg.sender] -= withdrawAmount;
|
||||||
|
|
||||||
if (!msg.sender.send(withdrawAmount)) {
|
if (!msg.sender.send(withdrawAmount)) {
|
||||||
// to be safe, may be sending to contract that
|
// increment back only on fail, as may be sending to contract that
|
||||||
// has overridden 'send' which may then fail
|
// has overridden 'send' on the receipt end
|
||||||
balances[msg.sender] += withdrawAmount;
|
balances[msg.sender] += withdrawAmount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,8 +163,10 @@ address public owner;
|
|||||||
|
|
||||||
// All addresses can be sent ether
|
// All addresses can be sent ether
|
||||||
owner.send(SOME_BALANCE); // returns false on failure
|
owner.send(SOME_BALANCE); // returns false on failure
|
||||||
if (owner.send) {} // typically wrap in 'if', as contract addresses have
|
if (owner.send) {} // REMEMBER: wrap in 'if', as contract addresses have
|
||||||
// functions have executed on send and can fail
|
// functions executed on send and these can fail
|
||||||
|
// Also, make sure to deduct balances BEFORE attempting a send, as there is a risk of a recursive
|
||||||
|
// call that can drain the contract
|
||||||
|
|
||||||
// can override send by defining your own
|
// can override send by defining your own
|
||||||
|
|
||||||
@ -351,8 +366,11 @@ function b() {
|
|||||||
// access events from outside blockchain (with lightweight clients)
|
// access events from outside blockchain (with lightweight clients)
|
||||||
// typically declare after contract parameters
|
// typically declare after contract parameters
|
||||||
|
|
||||||
|
// Typically, capitalized - and add Log in front to be explicit and prevent confusion
|
||||||
|
// with a function call
|
||||||
|
|
||||||
// Declare
|
// Declare
|
||||||
event Sent(address from, address to, uint amount); // note capital first letter
|
event LogSent(address indexed from, address indexed to, uint amount); // note capital first letter
|
||||||
|
|
||||||
// Call
|
// Call
|
||||||
Sent(from, to, amount);
|
Sent(from, to, amount);
|
||||||
@ -396,7 +414,10 @@ onlyIfState(State.A)
|
|||||||
modifier checkValue(uint amount) {
|
modifier checkValue(uint amount) {
|
||||||
_
|
_
|
||||||
if (msg.value > amount) {
|
if (msg.value > amount) {
|
||||||
msg.sender.send(amount - msg.value);
|
uint amountToRefund = amount - msg.value;
|
||||||
|
if (!msg.sender.send(amountToRefund)) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,6 +430,21 @@ modifier checkValue(uint amount) {
|
|||||||
// Syntax same as javascript, but no type conversion from non-boolean
|
// Syntax same as javascript, but no type conversion from non-boolean
|
||||||
// to boolean (comparison operators must be used to get the boolean val)
|
// to boolean (comparison operators must be used to get the boolean val)
|
||||||
|
|
||||||
|
// For loops that are determined by user behavior, be careful - as contracts have a maximal
|
||||||
|
// amount of gas for a block of code - and will fail if that is exceeded
|
||||||
|
// For example:
|
||||||
|
for(uint x = 0; x < refundAddressList.length; x++) {
|
||||||
|
if (!refundAddressList[x].send(SOME_AMOUNT)) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two errors above:
|
||||||
|
// 1. A failure on send stops the loop from completing, tying up money
|
||||||
|
// 2. This loop could be arbitrarily long (based on the amount of users who need refunds), and
|
||||||
|
// therefore may always fail as it exceeds the max gas for a block
|
||||||
|
// Instead, you should let people withdraw individually from their subaccount, and mark withdrawn
|
||||||
|
|
||||||
|
|
||||||
// 7. OBJECTS/CONTRACTS
|
// 7. OBJECTS/CONTRACTS
|
||||||
|
|
||||||
@ -587,13 +623,13 @@ contract CrowdFunder {
|
|||||||
address public fundRecipient; // creator may be different than recipient
|
address public fundRecipient; // creator may be different than recipient
|
||||||
uint public minimumToRaise; // required to tip, else everyone gets refund
|
uint public minimumToRaise; // required to tip, else everyone gets refund
|
||||||
string campaignUrl;
|
string campaignUrl;
|
||||||
|
byte constant version = 1;
|
||||||
|
|
||||||
// Data structures
|
// Data structures
|
||||||
enum State {
|
enum State {
|
||||||
Fundraising,
|
Fundraising,
|
||||||
ExpiredRefundPending,
|
ExpiredRefund,
|
||||||
Successful,
|
Successful
|
||||||
ExpiredRefundComplete
|
|
||||||
}
|
}
|
||||||
struct Contribution {
|
struct Contribution {
|
||||||
uint amount;
|
uint amount;
|
||||||
@ -604,11 +640,11 @@ contract CrowdFunder {
|
|||||||
State public state = State.Fundraising; // initialize on create
|
State public state = State.Fundraising; // initialize on create
|
||||||
uint public totalRaised;
|
uint public totalRaised;
|
||||||
uint public raiseBy;
|
uint public raiseBy;
|
||||||
|
uint public completeAt;
|
||||||
Contribution[] contributions;
|
Contribution[] contributions;
|
||||||
|
|
||||||
event fundingReceived(address addr, uint amount, uint currentTotal);
|
event LogFundingReceived(address addr, uint amount, uint currentTotal);
|
||||||
event allRefundsSent();
|
event LogWinnerPaid(address winnerAddress);
|
||||||
event winnerPaid(address winnerAddress);
|
|
||||||
|
|
||||||
modifier inState(State _state) {
|
modifier inState(State _state) {
|
||||||
if (state != _state) throw;
|
if (state != _state) throw;
|
||||||
@ -620,10 +656,13 @@ contract CrowdFunder {
|
|||||||
_
|
_
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait 6 months after final contract state before allowing contract destruction
|
||||||
modifier atEndOfLifecycle() {
|
modifier atEndOfLifecycle() {
|
||||||
if(state != State.ExpiredRefundComplete && state != State.Successful) {
|
if(!((state == State.ExpiredRefund || state == State.Successful) &&
|
||||||
|
completeAt + 6 months < now)) {
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
_
|
||||||
}
|
}
|
||||||
|
|
||||||
function CrowdFunder(
|
function CrowdFunder(
|
||||||
@ -651,9 +690,10 @@ contract CrowdFunder {
|
|||||||
);
|
);
|
||||||
totalRaised += msg.value;
|
totalRaised += msg.value;
|
||||||
|
|
||||||
fundingReceived(msg.sender, msg.value, totalRaised);
|
LogFundingReceived(msg.sender, msg.value, totalRaised);
|
||||||
|
|
||||||
checkIfFundingCompleteOrExpired();
|
checkIfFundingCompleteOrExpired();
|
||||||
|
return contributions.length - 1; // return id
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkIfFundingCompleteOrExpired() {
|
function checkIfFundingCompleteOrExpired() {
|
||||||
@ -663,9 +703,9 @@ contract CrowdFunder {
|
|||||||
|
|
||||||
// could incentivize sender who initiated state change here
|
// could incentivize sender who initiated state change here
|
||||||
} else if ( now > raiseBy ) {
|
} else if ( now > raiseBy ) {
|
||||||
state = State.ExpiredRefundPending;
|
state = State.ExpiredRefund; // backers can now collect refunds by calling getRefund(id)
|
||||||
refundAll();
|
|
||||||
}
|
}
|
||||||
|
completeAt = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
function payOut()
|
function payOut()
|
||||||
@ -676,22 +716,27 @@ contract CrowdFunder {
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
winnerPaid(fundRecipient);
|
|
||||||
|
LogWinnerPaid(fundRecipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
function refundAll()
|
function getRefund(id)
|
||||||
public
|
public
|
||||||
inState(State.ExpiredRefundPending)
|
inState(State.ExpiredRefund)
|
||||||
{
|
{
|
||||||
uint length = contributions.length;
|
if (contributions.length <= id || id < 0 || contributions[id].amount == 0 ) {
|
||||||
for (uint i = 0; i < length; i++) {
|
throw;
|
||||||
if(!contributions[i].contributor.send(contributions[i].amount)) {
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
allRefundsSent();
|
uint amountToRefund = contributions[id].amount;
|
||||||
state = State.ExpiredRefundComplete;
|
contributions[id].amount = 0;
|
||||||
|
|
||||||
|
if(!contributions[id].contributor.send(amountToSend)) {
|
||||||
|
contributions[id].amount = amountToSend;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeContract()
|
function removeContract()
|
||||||
@ -700,13 +745,13 @@ contract CrowdFunder {
|
|||||||
atEndOfLifecycle()
|
atEndOfLifecycle()
|
||||||
{
|
{
|
||||||
selfdestruct(msg.sender);
|
selfdestruct(msg.sender);
|
||||||
|
// creator gets all money that hasn't be claimed
|
||||||
}
|
}
|
||||||
|
|
||||||
function () { throw; }
|
function () { throw; }
|
||||||
}
|
}
|
||||||
// ** END EXAMPLE **
|
// ** END EXAMPLE **
|
||||||
|
|
||||||
|
|
||||||
// 10. OTHER NATIVE FUNCTIONS
|
// 10. OTHER NATIVE FUNCTIONS
|
||||||
|
|
||||||
// Currency units
|
// Currency units
|
||||||
@ -732,8 +777,14 @@ sha3("ab", "cd");
|
|||||||
ripemd160("abc");
|
ripemd160("abc");
|
||||||
sha256("def");
|
sha256("def");
|
||||||
|
|
||||||
|
// 11. SECURITY
|
||||||
|
|
||||||
// 11. LOW LEVEL FUNCTIONS
|
// Bugs can be disastrous in Ethereum contracts - and even popular patterns in Solidity,
|
||||||
|
// may be found to be antipatterns
|
||||||
|
|
||||||
|
// See security links at the end of this doc
|
||||||
|
|
||||||
|
// 12. LOW LEVEL FUNCTIONS
|
||||||
// call - low level, not often used, does not provide type safety
|
// call - low level, not often used, does not provide type safety
|
||||||
successBoolean = someContractAddress.call('function_name', 'arg1', 'arg2');
|
successBoolean = someContractAddress.call('function_name', 'arg1', 'arg2');
|
||||||
|
|
||||||
@ -742,7 +793,7 @@ successBoolean = someContractAddress.call('function_name', 'arg1', 'arg2');
|
|||||||
someContractAddress.callcode('function_name');
|
someContractAddress.callcode('function_name');
|
||||||
|
|
||||||
|
|
||||||
// 12. STYLE NOTES
|
// 13. STYLE NOTES
|
||||||
// Based on Python's PEP8 style guide
|
// Based on Python's PEP8 style guide
|
||||||
|
|
||||||
// Quick summary:
|
// Quick summary:
|
||||||
@ -753,7 +804,7 @@ someContractAddress.callcode('function_name');
|
|||||||
// else should be placed on own line
|
// else should be placed on own line
|
||||||
|
|
||||||
|
|
||||||
// 13. NATSPEC COMENTS
|
// 14. NATSPEC COMENTS
|
||||||
// used for documentation, commenting, and external UIs
|
// used for documentation, commenting, and external UIs
|
||||||
|
|
||||||
// Contract natspec - always above contract definition
|
// Contract natspec - always above contract definition
|
||||||
@ -773,9 +824,8 @@ someContractAddress.callcode('function_name');
|
|||||||
- [Solidity Docs](https://solidity.readthedocs.org/en/latest/)
|
- [Solidity Docs](https://solidity.readthedocs.org/en/latest/)
|
||||||
- [Solidity Style Guide](https://ethereum.github.io/solidity//docs/style-guide/): Ethereum's style guide is heavily derived from Python's [pep8](https://www.python.org/dev/peps/pep-0008/) style guide.
|
- [Solidity Style Guide](https://ethereum.github.io/solidity//docs/style-guide/): Ethereum's style guide is heavily derived from Python's [pep8](https://www.python.org/dev/peps/pep-0008/) style guide.
|
||||||
- [Browser-based Solidity Editor](http://chriseth.github.io/browser-solidity/)
|
- [Browser-based Solidity Editor](http://chriseth.github.io/browser-solidity/)
|
||||||
- [Gitter Chat room](https://gitter.im/ethereum/solidity)
|
- [Gitter Solidity Chat room](https://gitter.im/ethereum/solidity)
|
||||||
- [Modular design strategies for Ethereum Contracts](https://docs.erisindustries.com/tutorials/solidity/)
|
- [Modular design strategies for Ethereum Contracts](https://docs.erisindustries.com/tutorials/solidity/)
|
||||||
- Editor Snippets ([Ultisnips format](https://gist.github.com/nemild/98343ce6b16b747788bc))
|
|
||||||
|
|
||||||
## Sample contracts
|
## Sample contracts
|
||||||
- [Dapp Bin](https://github.com/ethereum/dapp-bin)
|
- [Dapp Bin](https://github.com/ethereum/dapp-bin)
|
||||||
@ -783,13 +833,24 @@ someContractAddress.callcode('function_name');
|
|||||||
- [ConsenSys Contracts](https://github.com/ConsenSys/dapp-store-contracts)
|
- [ConsenSys Contracts](https://github.com/ConsenSys/dapp-store-contracts)
|
||||||
- [State of Dapps](http://dapps.ethercasts.com/)
|
- [State of Dapps](http://dapps.ethercasts.com/)
|
||||||
|
|
||||||
|
## Security
|
||||||
|
- [Thinking About Smart Contract Security](https://blog.ethereum.org/2016/06/19/thinking-smart-contract-security/)
|
||||||
|
- [Smart Contract Security](https://blog.ethereum.org/2016/06/10/smart-contract-security/)
|
||||||
|
- [Hacking Distributed Blog](http://hackingdistributed.com/)
|
||||||
|
|
||||||
## Information purposefully excluded
|
## Information purposefully excluded
|
||||||
- Libraries
|
- Libraries
|
||||||
|
|
||||||
## Style
|
## Style
|
||||||
- Python's [PEP8](https://www.python.org/dev/peps/pep-0008/) is used as the baseline style guide, including its general philosophy
|
- Python's [PEP8](https://www.python.org/dev/peps/pep-0008/) is used as the baseline style guide, including its general philosophy
|
||||||
|
|
||||||
|
## Editors
|
||||||
|
- [Vim Solidity](https://github.com/tomlion/vim-solidity)
|
||||||
|
- Editor Snippets ([Ultisnips format](https://gist.github.com/nemild/98343ce6b16b747788bc))
|
||||||
|
|
||||||
## Future To Dos
|
## Future To Dos
|
||||||
- New keywords: protected, inheritable
|
- New keywords: protected, inheritable
|
||||||
|
- List of common design patterns (throttling, RNG, version upgrade)
|
||||||
|
- Common security anti patterns
|
||||||
|
|
||||||
Feel free to send a pull request with any edits - or email nemild -/at-/ gmail
|
Feel free to send a pull request with any edits - or email nemild -/at-/ gmail
|
||||||
|
Loading…
Reference in New Issue
Block a user