// Research — Acme Vaults (fictional)
PLACEHOLDER: Oracle staleness lets vault shares be minted at yesterday's price
Fake sample finding. A missing heartbeat check on a price feed allowed deposits to be valued against a stale round during volatility.
// Disclosure timeline
Finding confirmed
Reproduced on a fork against the deployed contracts.
Reported to the team
Private disclosure with a proof-of-concept.
Acknowledged
Severity agreed as critical.
Fix deployed
Staleness check and circuit breaker shipped.
Published
This write-up, after the fix was verified.
PLACEHOLDER BODY — every word on this page is sample content. It exists so the layout, table of contents, code blocks, callouts, tables, embeds and footnotes can be seen before real research is published.
Background
The contract below is a deliberately broken vault used throughout our sample content. It is not client code. The withdraw path makes an external call before updating state, which is the textbook reentrancy shape.
This pattern has been documented since 2016[1], and it still ships.
The vulnerable code
contract Vault {
mapping(address => uint256) public balances;
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
// state is updated after the external call
(bool ok, ) = msg.sender.call{value: amount}("");
balances[msg.sender] -= amount;
}
}Why it matters
Because the balance is decremented after the call returns, a receiver contract can call withdraw() again from its fallback while its balance is still unchanged.
The fix
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
(bool ok, ) = msg.sender.call{value: amount}("");
balances[msg.sender] -= amount;
}function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
(bool ok, ) = msg.sender.call{value: amount}("");
require(ok, "transfer failed");
}Severity rationale
| Factor | Assessment |
|---|---|
| Likelihood | High — any depositor can trigger it |
| Impact | Critical — total loss of funds |
| Complexity | Low — a 20-line attacker contract |
Disclosure
Reported privately, acknowledged within a day, and fixed in the following release. The timeline is listed above the article on research pages.
// Footnotes
- [1]Sample footnote. In a real article this would cite the commit, the advisory or the prior work being referenced. ↩
// References
// More research