Missing Events on Price Change
Overview
- Severity: Low
- Confidence: Medium
- Affected Versions: All
What is the Missing Events on Price Change vulnerability?
In Ethereum, the event log serves as a mechanism for smart contracts to produce structured, filterable data that can be easily consumed by external services or front-end applications. When a smart contract executes an emit()
statement, it generates an event that gets stored in the event log as part of the transaction receipt. This is distinct from the blockchain's main ledger, and storing events is generally cheaper in terms of gas costs compared to storing data directly on the blockchain.
If a smart contract contains logic to set prices for a service or a digital asset, it is important that the contract emits an event when the price changes. This allows external services to monitor the contract and react to price changes in a timely manner. If the contract does not emit an event when the price changes, external services may not be able to detect the change, and may continue to use the old price, which could result in incorrect behavior or financial loss.
Further reading: A Guide to Events and Logs in Ethereum Smart Contracts
Technical example of vulnerable code
// SPDX-License-Identifier: Unlicense
pragma solidity 0.8.0;
contract NoEventsTokenPresale {
address owner;
mapping(address => uint256) public reservations;
uint256 currentTokenPrice = 1000;
constructor() {
owner = msg.sender;
}
function setTokenPrice(uint256 _updatedPrice) public {
require(owner == msg.sender);
currentTokenPrice = _updatedPrice;
}
function reserve() public payable {
// excess ether sent is retained by the contract as a fee
uint256 numTokensToReserve = msg.value / currentTokenPrice;
reservations[msg.sender] = numTokensToReserve;
}
// additional smart contract functionality
}
In the example above, the NoEventsTokenPresale
smart contract contains a function setTokenPrice()
that can be executed only by the contract's owner, which is designed to alter the reservation price for tokens that are being presold by the contract. However, the contract does not emit an event when the price is changed, so external services that are monitoring the contract may not be able to detect the change and may continue to use the old price, potentially causing issues when attempting to interact with the contract.
Technical example of how to fix the vulnerability
// SPDX-License-Identifier: Unlicense
pragma solidity 0.8.0;
contract TokenPresale {
address owner;
mapping(address => uint256) public reservations;
uint256 currentTokenPrice = 1000;
event TokenPriceUpdated(uint256 indexed _newTokenPrice);
constructor() {
owner = msg.sender;
}
function setTokenPrice(uint256 _updatedPrice) public {
require(owner == msg.sender);
currentTokenPrice = _updatedPrice;
emit TokenPriceUpdated(_updatedPrice);
}
function reserve() public payable {
// excess ether sent is retained by the contract as a fee
uint256 numTokensToReserve = msg.value / currentTokenPrice;
reservations[msg.sender] = numTokensToReserve;
}
// additional smart contract functionality
}
In the corrected example, the TokenPresale
smart contract now emits a TokenPriceUpdated
event when the price is changed, which allows external services to detect the change and react accordingly.