Skip to content

Commit

Permalink
chore: add 3 new ethernaut challenges
Browse files Browse the repository at this point in the history
  • Loading branch information
leovct committed Sep 30, 2024
1 parent 68eafec commit fa25019
Show file tree
Hide file tree
Showing 4 changed files with 339 additions and 3 deletions.
6 changes: 3 additions & 3 deletions doc/EthernautCTF.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
| 20 | [Denial](../src/EthernautCTF/Denial.sol) || [DenialExploit](../test/EthernautCTF/DenialExploit.t.sol) | - Always set the amount of gas when using a low-level call. It will prevent the external contract to consume all the gas.<br>- Check the return value of low-level calls, especially when the address is controlled by someone else. |
| 21 | [Shop](../src/EthernautCTF/Shop.sol) || [ShopExploit](../test/EthernautCTF/ShopExploit.t.sol) | - When calling an external contract, always check the returned value before using it!<br>- This challenge is very similar to challenge 11. |
| 22 | [Dex](../src/EthernautCTF/Dex.sol) || [DexExploit](../test/EthernautCTF/DexExploit.t.sol) | The contract uses a division operation to compute the swap amount which can be exploited because of a precision loss. Indeed, Solidity does not support floating points. |
| 23 | DexTwo || | |
| 23 | [DexTwo](../src/EthernautCTF/DexTwo.sol) || | |
| 24 | PuzzleWallet || | |
| 25 | Motorbike || | |
| 26 | DoubleEntry || | |
| 25 | [Motorbike](../src/EthernautCTF/Motorbike.sol) || | |
| 26 | [DoubleEntry](../src/EthernautCTF/DoubleEntry.sol) || | |
| 27 | GoodSamaritan || | |
| 28 | [GatekeeperThree](../src/EthernautCTF/GatekeeperThree.sol) || | |
| 29 | [Switch](../src/EthernautCTF/Switch.sol) || | |
Expand Down
73 changes: 73 additions & 0 deletions src/EthernautCTF/DexTwo.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import '@openzeppelin-08/token/ERC20/IERC20.sol';
import '@openzeppelin-08/token/ERC20/ERC20.sol';
import '@openzeppelin-08/access/Ownable.sol';

contract DexTwo is Ownable {
address public token1;
address public token2;

constructor() Ownable(msg.sender) {}

function setTokens(address _token1, address _token2) public onlyOwner {
token1 = _token1;
token2 = _token2;
}

function add_liquidity(
address token_address,
uint256 amount
) public onlyOwner {
IERC20(token_address).transferFrom(msg.sender, address(this), amount);
}

function swap(address from, address to, uint256 amount) public {
require(IERC20(from).balanceOf(msg.sender) >= amount, 'Not enough to swap');
uint256 swapAmount = getSwapAmount(from, to, amount);
IERC20(from).transferFrom(msg.sender, address(this), amount);
IERC20(to).approve(address(this), swapAmount);
IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
}

function getSwapAmount(
address from,
address to,
uint256 amount
) public view returns (uint256) {
return ((amount * IERC20(to).balanceOf(address(this))) /
IERC20(from).balanceOf(address(this)));
}

function approve(address spender, uint256 amount) public {
SwappableTokenTwo(token1).approve(msg.sender, spender, amount);
SwappableTokenTwo(token2).approve(msg.sender, spender, amount);
}

function balanceOf(
address token,
address account
) public view returns (uint256) {
return IERC20(token).balanceOf(account);
}
}

contract SwappableTokenTwo is ERC20 {
address private _dex;

constructor(
address dexInstance,
string memory name,
string memory symbol,
uint256 initialSupply
) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
_dex = dexInstance;
}

function approve(address owner, address spender, uint256 amount) public {
require(owner != _dex, 'InvalidApprover');
super._approve(owner, spender, amount);
}
}
142 changes: 142 additions & 0 deletions src/EthernautCTF/DoubleEntry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import '@openzeppelin-08/access/Ownable.sol';
import '@openzeppelin-08/token/ERC20/ERC20.sol';

interface DelegateERC20 {
function delegateTransfer(
address to,
uint256 value,
address origSender
) external returns (bool);
}

interface IDetectionBot {
function handleTransaction(address user, bytes calldata msgData) external;
}

interface IForta {
function setDetectionBot(address detectionBotAddress) external;
function notify(address user, bytes calldata msgData) external;
function raiseAlert(address user) external;
}

contract Forta is IForta {
mapping(address => IDetectionBot) public usersDetectionBots;
mapping(address => uint256) public botRaisedAlerts;

function setDetectionBot(address detectionBotAddress) external override {
usersDetectionBots[msg.sender] = IDetectionBot(detectionBotAddress);
}

function notify(address user, bytes calldata msgData) external override {
if (address(usersDetectionBots[user]) == address(0)) return;
try usersDetectionBots[user].handleTransaction(user, msgData) {
return;
} catch {}
}

function raiseAlert(address user) external override {
if (address(usersDetectionBots[user]) != msg.sender) return;
botRaisedAlerts[msg.sender] += 1;
}
}

contract CryptoVault {
address public sweptTokensRecipient;
IERC20 public underlying;

constructor(address recipient) {
sweptTokensRecipient = recipient;
}

function setUnderlying(address latestToken) public {
require(address(underlying) == address(0), 'Already set');
underlying = IERC20(latestToken);
}

/*
...
*/

function sweepToken(IERC20 token) public {
require(token != underlying, "Can't transfer underlying token");
token.transfer(sweptTokensRecipient, token.balanceOf(address(this)));
}
}

contract LegacyToken is ERC20('LegacyToken', 'LGT'), Ownable(msg.sender) {
DelegateERC20 public delegate;

function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}

function delegateToNewContract(DelegateERC20 newContract) public onlyOwner {
delegate = newContract;
}

function transfer(address to, uint256 value) public override returns (bool) {
if (address(delegate) == address(0)) {
return super.transfer(to, value);
} else {
return delegate.delegateTransfer(to, value, msg.sender);
}
}
}

contract DoubleEntryPoint is
ERC20('DoubleEntryPointToken', 'DET'),
DelegateERC20,
Ownable(msg.sender)
{
address public cryptoVault;
address public player;
address public delegatedFrom;
Forta public forta;

constructor(
address legacyToken,
address vaultAddress,
address fortaAddress,
address playerAddress
) {
delegatedFrom = legacyToken;
forta = Forta(fortaAddress);
player = playerAddress;
cryptoVault = vaultAddress;
_mint(cryptoVault, 100 ether);
}

modifier onlyDelegateFrom() {
require(msg.sender == delegatedFrom, 'Not legacy contract');
_;
}

modifier fortaNotify() {
address detectionBot = address(forta.usersDetectionBots(player));

// Cache old number of bot alerts
uint256 previousValue = forta.botRaisedAlerts(detectionBot);

// Notify Forta
forta.notify(player, msg.data);

// Continue execution
_;

// Check if alarms have been raised
if (forta.botRaisedAlerts(detectionBot) > previousValue)
revert('Alert has been triggered, reverting');
}

function delegateTransfer(
address to,
uint256 value,
address origSender
) public override onlyDelegateFrom fortaNotify returns (bool) {
_transfer(origSender, to, value);
return true;
}
}
121 changes: 121 additions & 0 deletions src/EthernautCTF/Motorbike.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: MIT

pragma solidity <0.7.0;

import '@openzeppelin-06/utils/Address.sol';
import '@openzeppelin-06/proxy/Initializable.sol';

contract Motorbike {
// keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
bytes32 internal constant _IMPLEMENTATION_SLOT =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

struct AddressSlot {
address value;
}

// Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
constructor(address _logic) public {
require(
Address.isContract(_logic),
'ERC1967: new implementation is not a contract'
);
_getAddressSlot(_IMPLEMENTATION_SLOT).value = _logic;
(bool success, ) = _logic.delegatecall(
abi.encodeWithSignature('initialize()')
);
require(success, 'Call failed');
}

// Delegates the current call to `implementation`.
function _delegate(address implementation) internal virtual {
// solhint-disable-next-line no-inline-assembly
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}

// Fallback function that delegates calls to the address returned by `_implementation()`.
// Will run if no other function in the contract matches the call data
fallback() external payable virtual {
_delegate(_getAddressSlot(_IMPLEMENTATION_SLOT).value);
}

// Returns an `AddressSlot` with member `value` located at `slot`.
function _getAddressSlot(
bytes32 slot
) internal pure returns (AddressSlot storage r) {
assembly {
r_slot := slot
}
}
}

contract Engine is Initializable {
// keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
bytes32 internal constant _IMPLEMENTATION_SLOT =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

address public upgrader;
uint256 public horsePower;

struct AddressSlot {
address value;
}

function initialize() external initializer {
horsePower = 1000;
upgrader = msg.sender;
}

// Upgrade the implementation of the proxy to `newImplementation`
// subsequently execute the function call
function upgradeToAndCall(
address newImplementation,
bytes memory data
) external payable {
_authorizeUpgrade();
_upgradeToAndCall(newImplementation, data);
}

// Restrict to upgrader role
function _authorizeUpgrade() internal view {
require(msg.sender == upgrader, "Can't upgrade");
}

// Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
function _upgradeToAndCall(
address newImplementation,
bytes memory data
) internal {
// Initial upgrade and setup call
_setImplementation(newImplementation);
if (data.length > 0) {
(bool success, ) = newImplementation.delegatecall(data);
require(success, 'Call failed');
}
}

// Stores a new address in the EIP1967 implementation slot.
function _setImplementation(address newImplementation) private {
require(
Address.isContract(newImplementation),
'ERC1967: new implementation is not a contract'
);

AddressSlot storage r;
assembly {
r_slot := _IMPLEMENTATION_SLOT
}
r.value = newImplementation;
}
}

0 comments on commit fa25019

Please sign in to comment.