"locked-ether" Woke detector

Woke is an existing tool for analyzing Solidity contracts. The project extends the tool with a new vulnerability/issue detector that can detect a case when a contract receives Ether but cannot send it

The problem "locked-ether" Woke detector solves

The project implements a new detector into the Woke tool (https://github.com/Ackee-Blockchain/woke). The detector can automatically detect and report a case when a contract can receive Ether but cannot send it to anyone. It also detects cases when a contract implements a function to send Ether but does not have any payable function to receive it.
The detector can be run using the

woke detect

command in a Solidity project root directory. It is possible to run only this detector with a


config file (located in the project root dir):

[detectors] only = ["locked-ether"]

There is a VS Code extension named

Tools for Solidity

(https://marketplace.visualstudio.com/items?itemName=AckeeBlockchain.tools-for-solidity) that uses Woke in the background and automatically runs detectors on every change to the code. Because of this, the detector implemented in this project can also be used by VS Code users and warn Solidity developers using the

Tools for Solidity

extension of locked Ether issues.

The detected "locked-ether" issues can save Ether that might be otherwise locked in a contract and lost forever (because of a missing function to withdraw it). Issues, when a contract can send Ether but cannot receive it, may also point to an implementation logic error or a bad practice.

Challenges we ran into

When implementing the detector, we discovered certain scenarios when the detector could report false-positive detections (the issue actually is not present) or may fail to find a "locked-ether" issue.

  1. A contract has a payable function, but it always reverts
    When a contract has payable functions, but all of them always revert the transaction, it cannot effectively receive Ether, and the issue should not be reported. We used control flow graphs generated by the Woke tool and checked every execution path of every payable function. When we found a revert statement in every execution path of a payable function, we ignored that function.

  2. A contract inherits a function that can send Ether from a base contract but overrides that function
    When a contract inherits a function sending Ether but overrides it, it cannot effectively send Ether using that function (unless the new implementation also allows sending Ether). Because of this, we first collected functions that receive/send Ether and then assigned them to contracts. When assigning to contracts, we looked at a contract that originally implemented the function and then discovered all contracts inheriting from this contract. When discovering contracts, we ensured that the function was not overridden by the given contract.

  3. Abstract contracts, libraries, and interfaces can only receive / only send Ether
    We excluded such contracts, libraries, and interfaces from the detection. We also ignored payable functions that do not have implementation.

We also identified some issues that could not be resolved in the limited hackathon time.

  1. A contract can receive Ether, does not implement a function to send Ether but performs a delegatecall in one of its functions
    In such case, a contract that is the destination of the delegatecall may send Ether of the origin contract. In a general case, it is not possible to identify the source code of the delegatecall destination contract, and so the detector cannot properly decide.

Technologies used