Please note, this content is no longer actively maintained.
The content of the SWC registry has not been thoroughly updated since 2020. It is known to be incomplete and may contain errors as well as crucial omissions.
For currently maintained guidance on known Smart Contract vulnerabilities written primarily as guidance for security reviewers, please see the EEA EthTrust Security Levels specification. As well as the latest release version, an Editor's draft is available, that represents the latest work of the group developing the specification.
General guidance for developers on what to consider to ensure security, that is currently maintained, is also available through the Smart Contract Security Verification Standard (SCSVS).
Title
Unexpected Ether balance
Relationships
- CWE-667: Improper Locking
- EEA EthTrust Security Levels:
- [S] No Exact Balance Check
- [M] Verify Exact Balance Checks
Description
Contracts can behave erroneously when they strictly assume a specific Ether balance. It is always possible to forcibly send ether to a contract (without triggering its fallback function), using selfdestruct, or by mining to the account. In the worst case scenario this could lead to DOS conditions that might render the contract unusable.
Remediation
Avoid strict equality checks for the Ether balance in a contract.
References
- Consensys Best Practices: Force Feeding
- Sigmaprime: Unexpected Ether
- Gridlock (a smart contract bug)
Samples
Lockdrop.sol
/**
* @source: https://github.com/hicommonwealth/edgeware-lockdrop/blob/93ecb524c9c88d25bab36278541f190fa9e910c2/contracts/Lockdrop.sol
*/
pragma solidity ^0.5.0;
contract Lock {
// address owner; slot #0
// address unlockTime; slot #1
constructor (address owner, uint256 unlockTime) public payable {
assembly {
sstore(0x00, owner)
sstore(0x01, unlockTime)
}
}
/**
* @dev Withdraw function once timestamp has passed unlock time
*/
function () external payable { // payable so solidity doesn't add unnecessary logic
assembly {
switch gt(timestamp, sload(0x01))
case 0 { revert(0, 0) }
case 1 {
switch call(gas, sload(0x00), balance(address), 0, 0, 0, 0)
case 0 { revert(0, 0) }
}
}
}
}
contract Lockdrop {
enum Term {
ThreeMo,
SixMo,
TwelveMo
}
// Time constants
uint256 constant public LOCK_DROP_PERIOD = 1 days * 92; // 3 months
uint256 public LOCK_START_TIME;
uint256 public LOCK_END_TIME;
// ETH locking events
event Locked(address indexed owner, uint256 eth, Lock lockAddr, Term term, bytes edgewareAddr, bool isValidator, uint time);
event Signaled(address indexed contractAddr, bytes edgewareAddr, uint time);
constructor(uint startTime) public {
LOCK_START_TIME = startTime;
LOCK_END_TIME = startTime + LOCK_DROP_PERIOD;
}
/**
* @dev Locks up the value sent to contract in a new Lock
* @param term The length of the lock up
* @param edgewareAddr The bytes representation of the target edgeware key
* @param isValidator Indicates if sender wishes to be a validator
*/
function lock(Term term, bytes calldata edgewareAddr, bool isValidator)
external
payable
didStart
didNotEnd
{
uint256 eth = msg.value;
address owner = msg.sender;
uint256 unlockTime = unlockTimeForTerm(term);
// Create ETH lock contract
Lock lockAddr = (new Lock).value(eth)(owner, unlockTime);
// ensure lock contract has all ETH, or fail
assert(address(lockAddr).balance == msg.value);
emit Locked(owner, eth, lockAddr, term, edgewareAddr, isValidator, now);
}
/**
* @dev Signals a contract's (or address's) balance decided after lock period
* @param contractAddr The contract address from which to signal the balance of
* @param nonce The transaction nonce of the creator of the contract
* @param edgewareAddr The bytes representation of the target edgeware key
*/
function signal(address contractAddr, uint32 nonce, bytes calldata edgewareAddr)
external
didStart
didNotEnd
didCreate(contractAddr, msg.sender, nonce)
{
emit Signaled(contractAddr, edgewareAddr, now);
}
function unlockTimeForTerm(Term term) internal view returns (uint256) {
if (term == Term.ThreeMo) return now + 92 days;
if (term == Term.SixMo) return now + 183 days;
if (term == Term.TwelveMo) return now + 365 days;
revert();
}
/**
* @dev Ensures the lockdrop has started
*/
modifier didStart() {
require(now >= LOCK_START_TIME);
_;
}
/**
* @dev Ensures the lockdrop has not ended
*/
modifier didNotEnd() {
require(now <= LOCK_END_TIME);
_;
}
/**
* @dev Rebuilds the contract address from a normal address and transaction nonce
* @param _origin The non-contract address derived from a user's public key
* @param _nonce The transaction nonce from which to generate a contract address
*/
function addressFrom(address _origin, uint32 _nonce) public pure returns (address) {
if(_nonce == 0x00) return address(uint160(uint256(keccak256(abi.encodePacked(byte(0xd6), byte(0x94), _origin, byte(0x80))))));
if(_nonce <= 0x7f) return address(uint160(uint256(keccak256(abi.encodePacked(byte(0xd6), byte(0x94), _origin, uint8(_nonce))))));
if(_nonce <= 0xff) return address(uint160(uint256(keccak256(abi.encodePacked(byte(0xd7), byte(0x94), _origin, byte(0x81), uint8(_nonce))))));
if(_nonce <= 0xffff) return address(uint160(uint256(keccak256(abi.encodePacked(byte(0xd8), byte(0x94), _origin, byte(0x82), uint16(_nonce))))));
if(_nonce <= 0xffffff) return address(uint160(uint256(keccak256(abi.encodePacked(byte(0xd9), byte(0x94), _origin, byte(0x83), uint24(_nonce))))));
return address(uint160(uint256(keccak256(abi.encodePacked(byte(0xda), byte(0x94), _origin, byte(0x84), uint32(_nonce)))))); // more than 2^32 nonces not realistic
}
/**
* @dev Ensures the target address was created by a parent at some nonce
* @param target The target contract address (or trivially the parent)
* @param parent The creator of the alleged contract address
* @param nonce The creator's tx nonce at the time of the contract creation
*/
modifier didCreate(address target, address parent, uint32 nonce) {
// Trivially let senders "create" themselves
if (target == parent) {
_;
} else {
require(target == addressFrom(parent, nonce));
_;
}
}
}