From 210a1b4c148a26f83ae1ff48d7f8cc0c86afe103 Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sun, 14 Jul 2024 16:36:01 +0900 Subject: [PATCH 01/15] add token access control sample --- .gitmodules | 9 +++ access-control/token/.gitignore | 17 ++++++ access-control/token/LICENSE | 21 +++++++ access-control/token/README.md | 25 ++++++++ access-control/token/foundry.toml | 8 +++ access-control/token/lib/forge-std | 1 + .../token/lib/openzeppelin-contracts | 1 + access-control/token/remappings.txt | 3 + access-control/token/src/MockERC20.sol | 36 ++++++++++++ .../token/src/TokenAccessControlTrap.sol | 58 +++++++++++++++++++ access-control/token/test/MockERC20.t.sol | 41 +++++++++++++ .../token/test/TokenAccessControlTrap.t.sol | 52 +++++++++++++++++ 12 files changed, 272 insertions(+) create mode 100644 access-control/token/.gitignore create mode 100644 access-control/token/LICENSE create mode 100644 access-control/token/README.md create mode 100644 access-control/token/foundry.toml create mode 160000 access-control/token/lib/forge-std create mode 160000 access-control/token/lib/openzeppelin-contracts create mode 100644 access-control/token/remappings.txt create mode 100644 access-control/token/src/MockERC20.sol create mode 100644 access-control/token/src/TokenAccessControlTrap.sol create mode 100644 access-control/token/test/MockERC20.t.sol create mode 100644 access-control/token/test/TokenAccessControlTrap.t.sol diff --git a/.gitmodules b/.gitmodules index c0faed2..66e1d33 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,12 @@ [submodule "bridges/nomad/lib/forge-std"] path = bridges/nomad/lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "access-control/token/lib"] + path = access-control/token/lib + url = https://github.com/foundry-rs/forge-std +[submodule "access-control/token/lib/openzeppelin-contracts"] + path = access-control/token/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "access-control/token/lib/forge-std"] + path = access-control/token/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/access-control/token/.gitignore b/access-control/token/.gitignore new file mode 100644 index 0000000..631854d --- /dev/null +++ b/access-control/token/.gitignore @@ -0,0 +1,17 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +cache/ +out/ diff --git a/access-control/token/LICENSE b/access-control/token/LICENSE new file mode 100644 index 0000000..be55ad7 --- /dev/null +++ b/access-control/token/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Drosera + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/access-control/token/README.md b/access-control/token/README.md new file mode 100644 index 0000000..18fdeaa --- /dev/null +++ b/access-control/token/README.md @@ -0,0 +1,25 @@ +# Drosera Trap Foundry Template + +For quickly bootstrapping a new Drosera project. + +[![view - Documentation](https://img.shields.io/badge/view-Documentation-blue?style=for-the-badge)](https://dev.drosera.io "Project documentation") + +## Overview +This template shows an example of monitoring a Uniswap V3 liquidity pool. The UniswapV3PoolTrap smart contract checks to see if the balance of the pool has changed by more than 20% from the previous block. + +## Features +- example trap contract +- example trap test + +## Getting Started + +```bash +mkdir my-drosera-trap +cd my-drosera-trap +forge init trap-foundry-template +``` + +## Testing +```bash +forge test +``` diff --git a/access-control/token/foundry.toml b/access-control/token/foundry.toml new file mode 100644 index 0000000..528ea36 --- /dev/null +++ b/access-control/token/foundry.toml @@ -0,0 +1,8 @@ +[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 +[rpc_endpoints] +mainnet = "https://eth.llamarpc.com" diff --git a/access-control/token/lib/forge-std b/access-control/token/lib/forge-std new file mode 160000 index 0000000..07263d1 --- /dev/null +++ b/access-control/token/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 07263d193d621c4b2b0ce8b4d54af58f6957d97d diff --git a/access-control/token/lib/openzeppelin-contracts b/access-control/token/lib/openzeppelin-contracts new file mode 160000 index 0000000..dbb6104 --- /dev/null +++ b/access-control/token/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 diff --git a/access-control/token/remappings.txt b/access-control/token/remappings.txt new file mode 100644 index 0000000..2cfbfec --- /dev/null +++ b/access-control/token/remappings.txt @@ -0,0 +1,3 @@ +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ diff --git a/access-control/token/src/MockERC20.sol b/access-control/token/src/MockERC20.sol new file mode 100644 index 0000000..75bf402 --- /dev/null +++ b/access-control/token/src/MockERC20.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {AccessControlEnumerable} from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockERC20 is ERC20, AccessControlEnumerable { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + constructor(address account) ERC20("MyToken", "TKN") { + _grantRole(DEFAULT_ADMIN_ROLE, account); + _grantRole(MINTER_ROLE, account); + } + + function mint(address to, uint256 amount) public { + require( + hasRole(MINTER_ROLE, _msgSender()), + "MockERC20: must have minter role to mint" + ); + _mint(to, amount); + } + + function grantMinter(address account) public { + // vulnerable implementation + _grantRole(MINTER_ROLE, account); + } + + function getRoleAddresses(bytes32 role) public view returns (address[] memory) { + uint256 count = getRoleMemberCount(role); + address[] memory addresses = new address[](count); + for (uint256 i = 0; i < count; ++i) { + addresses[i] = getRoleMember(role, i); + } + return addresses; + } +} diff --git a/access-control/token/src/TokenAccessControlTrap.sol b/access-control/token/src/TokenAccessControlTrap.sol new file mode 100644 index 0000000..575c61f --- /dev/null +++ b/access-control/token/src/TokenAccessControlTrap.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; +import {MockERC20} from "./MockERC20.sol"; +import {console} from "forge-std/console.sol"; + +contract TokenAccessControlTrap { + MockERC20 public mockToken; + + struct CustomCollectStruct { + address[] minterAddresses; + } + + constructor(address _mockToken) { + mockToken = MockERC20(_mockToken); + } + + function collect() external view returns (CustomCollectStruct memory) { + address[] memory minterAddresses = mockToken.getRoleAddresses(mockToken.MINTER_ROLE()); + return CustomCollectStruct({minterAddresses: minterAddresses}); + } + + function isValid( + CustomCollectStruct[] calldata dataPoints + ) external pure returns (bool) { + if (dataPoints.length == 0) { + revert("DataPoints length should be greater than 0"); + } + + if (dataPoints.length == 1) { + return true; + } + + address[] memory oldList = dataPoints[0].minterAddresses; + address[] memory newList = dataPoints[1].minterAddresses; + // all minter addresses should be same at each block. + if (oldList.length != newList.length) { + return false; + } + + bool[] memory found = new bool[](oldList.length); + + for (uint256 i = 0; i < oldList.length; i++) { + bool matchFound = false; + for (uint256 j = 0; j < newList.length; j++) { + if (oldList[i] == newList[j] && !found[j]) { + found[j] = true; + matchFound = true; + break; + } + } + if (!matchFound) { + return false; + } + } + + return true; + } +} diff --git a/access-control/token/test/MockERC20.t.sol b/access-control/token/test/MockERC20.t.sol new file mode 100644 index 0000000..15fa2ca --- /dev/null +++ b/access-control/token/test/MockERC20.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "../src/MockERC20.sol"; +import "forge-std/console.sol"; + +contract MockERC20Test is Test { + MockERC20 public token; + address public minter; + address public user; + + function setUp() public { + minter = address(this); + user = address(0x123); + token = new MockERC20(minter); + } + + function testMinterRole() public view { + assertTrue(token.hasRole(token.MINTER_ROLE(), minter)); + } + + function testMint() public { + uint256 mintAmount = 1000 * 10 ** 18; + token.mint(user, mintAmount); + assertEq(token.balanceOf(user), mintAmount); + } + + function testMintFailWithoutRole() public { + uint256 mintAmount = 1000 * 10 ** 18; + vm.prank(user); + vm.expectRevert("MockERC20: must have minter role to mint"); + token.mint(user, mintAmount); + } + + function testMinterAddresses() public view { + address[] memory minterAddresses = token.getRoleAddresses(token.MINTER_ROLE()); + assertEq(minterAddresses.length, 1); + assertEq(minterAddresses[0], minter); + } +} diff --git a/access-control/token/test/TokenAccessControlTrap.t.sol b/access-control/token/test/TokenAccessControlTrap.t.sol new file mode 100644 index 0000000..3c857fa --- /dev/null +++ b/access-control/token/test/TokenAccessControlTrap.t.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {TokenAccessControlTrap} from "../src/TokenAccessControlTrap.sol"; +import {MockERC20} from "../src/MockERC20.sol"; + +/// @dev forge test --match-contract LocalTrapTest -vv +contract TokenAccessControlTrapTest is Test { + MockERC20 public mockToken; + // Trap Config lives on chain while the Trap itself lives off chain + address public trapConfig = address(0xDEADBEEF); + address public user; + + function setUp() public { + mockToken = new MockERC20(address(this)); + user = address(0x123); + } + + function test_TokenAccessControlTrap() external { + TokenAccessControlTrap.CustomCollectStruct[] + memory data = new TokenAccessControlTrap.CustomCollectStruct[](1); + address tokenAccessControlTrap = address( + new TokenAccessControlTrap(address(mockToken)) + ); + + data[0] = TokenAccessControlTrap(tokenAccessControlTrap).collect(); + bool isValid = TokenAccessControlTrap(tokenAccessControlTrap).isValid( + data + ); + assert(isValid); + + // Perform exploit + vm.prank(user); + mockToken.grantMinter(user); + + TokenAccessControlTrap.CustomCollectStruct[] + memory newData = new TokenAccessControlTrap.CustomCollectStruct[]( + data.length + 1 + ); + for (uint i = 0; i < data.length; i++) { + newData[i] = data[i]; + } + newData[data.length] = TokenAccessControlTrap(tokenAccessControlTrap) + .collect(); + isValid = TokenAccessControlTrap(tokenAccessControlTrap).isValid( + newData + ); + // Minter list changed, so it should be invalid. + assert(!isValid); + } +} From 8fee6657d0d00e47dfd84fc0e9698e77fbf69aaf Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sat, 20 Jul 2024 15:30:15 +0900 Subject: [PATCH 02/15] remove unused lib --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 66e1d33..ecefeff 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,9 +25,6 @@ [submodule "bridges/nomad/lib/forge-std"] path = bridges/nomad/lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "access-control/token/lib"] - path = access-control/token/lib - url = https://github.com/foundry-rs/forge-std [submodule "access-control/token/lib/openzeppelin-contracts"] path = access-control/token/lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts From f32bce36821742e48f4dd40a657366e1b292b412 Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sat, 20 Jul 2024 15:54:47 +0900 Subject: [PATCH 03/15] mv sandbox to sandbox/bridge --- .gitmodules | 8 ++++---- sandbox/{ => bridge}/README.md | 0 sandbox/{ => bridge}/foundry.toml | 0 sandbox/{ => bridge}/lib/forge-std | 0 sandbox/{ => bridge}/lib/openzeppelin-contracts | 0 sandbox/{ => bridge}/remappings.txt | 0 sandbox/{ => bridge}/src/protocols/MockBridge.sol | 0 sandbox/{ => bridge}/src/protocols/MyProtocol.sol | 0 sandbox/{ => bridge}/src/traps/ForkTrap.sol | 0 sandbox/{ => bridge}/src/traps/LocalTrap.sol | 0 sandbox/{ => bridge}/test/ForkTrap.t.sol | 0 sandbox/{ => bridge}/test/LocalTrap.t.sol | 0 12 files changed, 4 insertions(+), 4 deletions(-) rename sandbox/{ => bridge}/README.md (100%) rename sandbox/{ => bridge}/foundry.toml (100%) rename sandbox/{ => bridge}/lib/forge-std (100%) rename sandbox/{ => bridge}/lib/openzeppelin-contracts (100%) rename sandbox/{ => bridge}/remappings.txt (100%) rename sandbox/{ => bridge}/src/protocols/MockBridge.sol (100%) rename sandbox/{ => bridge}/src/protocols/MyProtocol.sol (100%) rename sandbox/{ => bridge}/src/traps/ForkTrap.sol (100%) rename sandbox/{ => bridge}/src/traps/LocalTrap.sol (100%) rename sandbox/{ => bridge}/test/ForkTrap.t.sol (100%) rename sandbox/{ => bridge}/test/LocalTrap.t.sol (100%) diff --git a/.gitmodules b/.gitmodules index ecefeff..8e37ceb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,8 +10,8 @@ [submodule "oracles/twap/lib/prb-math"] path = oracles/twap/lib/prb-math url = https://github.com/PaulRBerg/prb-math -[submodule "sandbox/lib/forge-std"] - path = sandbox/lib/forge-std +[submodule "sandbox/bridge/lib/forge-std"] + path = sandbox/bridge/lib/forge-std url = https://github.com/foundry-rs/forge-std [submodule "tokens/olympus-dao/lib/openzeppelin-contracts"] path = tokens/olympus-dao/lib/openzeppelin-contracts @@ -19,8 +19,8 @@ [submodule "tokens/olympus-dao/lib/forge-std"] path = tokens/olympus-dao/lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "sandbox/lib/openzeppelin-contracts"] - path = sandbox/lib/openzeppelin-contracts +[submodule "sandbox/bridge/lib/openzeppelin-contracts"] + path = sandbox/bridge/lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts [submodule "bridges/nomad/lib/forge-std"] path = bridges/nomad/lib/forge-std diff --git a/sandbox/README.md b/sandbox/bridge/README.md similarity index 100% rename from sandbox/README.md rename to sandbox/bridge/README.md diff --git a/sandbox/foundry.toml b/sandbox/bridge/foundry.toml similarity index 100% rename from sandbox/foundry.toml rename to sandbox/bridge/foundry.toml diff --git a/sandbox/lib/forge-std b/sandbox/bridge/lib/forge-std similarity index 100% rename from sandbox/lib/forge-std rename to sandbox/bridge/lib/forge-std diff --git a/sandbox/lib/openzeppelin-contracts b/sandbox/bridge/lib/openzeppelin-contracts similarity index 100% rename from sandbox/lib/openzeppelin-contracts rename to sandbox/bridge/lib/openzeppelin-contracts diff --git a/sandbox/remappings.txt b/sandbox/bridge/remappings.txt similarity index 100% rename from sandbox/remappings.txt rename to sandbox/bridge/remappings.txt diff --git a/sandbox/src/protocols/MockBridge.sol b/sandbox/bridge/src/protocols/MockBridge.sol similarity index 100% rename from sandbox/src/protocols/MockBridge.sol rename to sandbox/bridge/src/protocols/MockBridge.sol diff --git a/sandbox/src/protocols/MyProtocol.sol b/sandbox/bridge/src/protocols/MyProtocol.sol similarity index 100% rename from sandbox/src/protocols/MyProtocol.sol rename to sandbox/bridge/src/protocols/MyProtocol.sol diff --git a/sandbox/src/traps/ForkTrap.sol b/sandbox/bridge/src/traps/ForkTrap.sol similarity index 100% rename from sandbox/src/traps/ForkTrap.sol rename to sandbox/bridge/src/traps/ForkTrap.sol diff --git a/sandbox/src/traps/LocalTrap.sol b/sandbox/bridge/src/traps/LocalTrap.sol similarity index 100% rename from sandbox/src/traps/LocalTrap.sol rename to sandbox/bridge/src/traps/LocalTrap.sol diff --git a/sandbox/test/ForkTrap.t.sol b/sandbox/bridge/test/ForkTrap.t.sol similarity index 100% rename from sandbox/test/ForkTrap.t.sol rename to sandbox/bridge/test/ForkTrap.t.sol diff --git a/sandbox/test/LocalTrap.t.sol b/sandbox/bridge/test/LocalTrap.t.sol similarity index 100% rename from sandbox/test/LocalTrap.t.sol rename to sandbox/bridge/test/LocalTrap.t.sol From 94db78a7927c01870680dc31137f3f2b20955cee Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sat, 20 Jul 2024 16:02:56 +0900 Subject: [PATCH 04/15] mv access-control sample to sandbox --- .gitmodules | 4 ++-- .../token => sandbox/token-access-control}/.gitignore | 0 .../token => sandbox/token-access-control}/LICENSE | 0 .../token => sandbox/token-access-control}/README.md | 0 .../token => sandbox/token-access-control}/foundry.toml | 0 .../token => sandbox/token-access-control}/lib/forge-std | 0 .../token-access-control}/lib/openzeppelin-contracts | 0 .../token => sandbox/token-access-control}/remappings.txt | 0 .../token => sandbox/token-access-control}/src/MockERC20.sol | 0 .../token-access-control}/src/TokenAccessControlTrap.sol | 0 .../token-access-control}/test/MockERC20.t.sol | 0 .../token-access-control}/test/TokenAccessControlTrap.t.sol | 0 12 files changed, 2 insertions(+), 2 deletions(-) rename {access-control/token => sandbox/token-access-control}/.gitignore (100%) rename {access-control/token => sandbox/token-access-control}/LICENSE (100%) rename {access-control/token => sandbox/token-access-control}/README.md (100%) rename {access-control/token => sandbox/token-access-control}/foundry.toml (100%) rename {access-control/token => sandbox/token-access-control}/lib/forge-std (100%) rename {access-control/token => sandbox/token-access-control}/lib/openzeppelin-contracts (100%) rename {access-control/token => sandbox/token-access-control}/remappings.txt (100%) rename {access-control/token => sandbox/token-access-control}/src/MockERC20.sol (100%) rename {access-control/token => sandbox/token-access-control}/src/TokenAccessControlTrap.sol (100%) rename {access-control/token => sandbox/token-access-control}/test/MockERC20.t.sol (100%) rename {access-control/token => sandbox/token-access-control}/test/TokenAccessControlTrap.t.sol (100%) diff --git a/.gitmodules b/.gitmodules index 8e37ceb..ffce263 100644 --- a/.gitmodules +++ b/.gitmodules @@ -26,8 +26,8 @@ path = bridges/nomad/lib/forge-std url = https://github.com/foundry-rs/forge-std [submodule "access-control/token/lib/openzeppelin-contracts"] - path = access-control/token/lib/openzeppelin-contracts + path = sandbox/token-access-control/lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts [submodule "access-control/token/lib/forge-std"] - path = access-control/token/lib/forge-std + path = sandbox/token-access-control/lib/forge-std url = https://github.com/foundry-rs/forge-std diff --git a/access-control/token/.gitignore b/sandbox/token-access-control/.gitignore similarity index 100% rename from access-control/token/.gitignore rename to sandbox/token-access-control/.gitignore diff --git a/access-control/token/LICENSE b/sandbox/token-access-control/LICENSE similarity index 100% rename from access-control/token/LICENSE rename to sandbox/token-access-control/LICENSE diff --git a/access-control/token/README.md b/sandbox/token-access-control/README.md similarity index 100% rename from access-control/token/README.md rename to sandbox/token-access-control/README.md diff --git a/access-control/token/foundry.toml b/sandbox/token-access-control/foundry.toml similarity index 100% rename from access-control/token/foundry.toml rename to sandbox/token-access-control/foundry.toml diff --git a/access-control/token/lib/forge-std b/sandbox/token-access-control/lib/forge-std similarity index 100% rename from access-control/token/lib/forge-std rename to sandbox/token-access-control/lib/forge-std diff --git a/access-control/token/lib/openzeppelin-contracts b/sandbox/token-access-control/lib/openzeppelin-contracts similarity index 100% rename from access-control/token/lib/openzeppelin-contracts rename to sandbox/token-access-control/lib/openzeppelin-contracts diff --git a/access-control/token/remappings.txt b/sandbox/token-access-control/remappings.txt similarity index 100% rename from access-control/token/remappings.txt rename to sandbox/token-access-control/remappings.txt diff --git a/access-control/token/src/MockERC20.sol b/sandbox/token-access-control/src/MockERC20.sol similarity index 100% rename from access-control/token/src/MockERC20.sol rename to sandbox/token-access-control/src/MockERC20.sol diff --git a/access-control/token/src/TokenAccessControlTrap.sol b/sandbox/token-access-control/src/TokenAccessControlTrap.sol similarity index 100% rename from access-control/token/src/TokenAccessControlTrap.sol rename to sandbox/token-access-control/src/TokenAccessControlTrap.sol diff --git a/access-control/token/test/MockERC20.t.sol b/sandbox/token-access-control/test/MockERC20.t.sol similarity index 100% rename from access-control/token/test/MockERC20.t.sol rename to sandbox/token-access-control/test/MockERC20.t.sol diff --git a/access-control/token/test/TokenAccessControlTrap.t.sol b/sandbox/token-access-control/test/TokenAccessControlTrap.t.sol similarity index 100% rename from access-control/token/test/TokenAccessControlTrap.t.sol rename to sandbox/token-access-control/test/TokenAccessControlTrap.t.sol From bd5100216269f295670a6c64fd239ff060e843c1 Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sat, 20 Jul 2024 16:18:19 +0900 Subject: [PATCH 05/15] fix readme --- sandbox/token-access-control/README.md | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/sandbox/token-access-control/README.md b/sandbox/token-access-control/README.md index 18fdeaa..997678a 100644 --- a/sandbox/token-access-control/README.md +++ b/sandbox/token-access-control/README.md @@ -1,25 +1,9 @@ -# Drosera Trap Foundry Template +# Token Access Control Example -For quickly bootstrapping a new Drosera project. +- `TokenAccessControlTrap` monitors the list of addresses with the token minter role and checks for any changes. It is used in case of an access control exploit. -[![view - Documentation](https://img.shields.io/badge/view-Documentation-blue?style=for-the-badge)](https://dev.drosera.io "Project documentation") +## Running the Examples -## Overview -This template shows an example of monitoring a Uniswap V3 liquidity pool. The UniswapV3PoolTrap smart contract checks to see if the balance of the pool has changed by more than 20% from the previous block. - -## Features -- example trap contract -- example trap test - -## Getting Started - -```bash -mkdir my-drosera-trap -cd my-drosera-trap -forge init trap-foundry-template -``` - -## Testing ```bash forge test ``` From beaaa53a01b530bce134d07f7b71896c0e140cb3 Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sat, 20 Jul 2024 21:14:17 +0900 Subject: [PATCH 06/15] add approve example base --- .gitmodules | 10 ++++++++-- sandbox/token-approve/.gitignore | 17 +++++++++++++++++ sandbox/token-approve/LICENSE | 21 +++++++++++++++++++++ sandbox/token-approve/README.md | 9 +++++++++ sandbox/token-approve/foundry.toml | 8 ++++++++ sandbox/token-approve/remappings.txt | 3 +++ sandbox/token-approve/src/MockERC20.sol | 18 ++++++++++++++++++ 7 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 sandbox/token-approve/.gitignore create mode 100644 sandbox/token-approve/LICENSE create mode 100644 sandbox/token-approve/README.md create mode 100644 sandbox/token-approve/foundry.toml create mode 100644 sandbox/token-approve/remappings.txt create mode 100644 sandbox/token-approve/src/MockERC20.sol diff --git a/.gitmodules b/.gitmodules index ffce263..6ed91f1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,9 +25,15 @@ [submodule "bridges/nomad/lib/forge-std"] path = bridges/nomad/lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "access-control/token/lib/openzeppelin-contracts"] +[submodule "sandbox/token-access-control/lib/openzeppelin-contracts"] path = sandbox/token-access-control/lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts -[submodule "access-control/token/lib/forge-std"] +[submodule "sandbox/token-access-control/lib/forge-std"] path = sandbox/token-access-control/lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "sandbox/token-approve/lib/openzeppelin-contracts"] + path = sandbox/token-approve/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "sandbox/token-approve/lib/forge-std"] + path = sandbox/token-approve/lib/forge-std + url = https://github.com/foundry-rs/forge-std \ No newline at end of file diff --git a/sandbox/token-approve/.gitignore b/sandbox/token-approve/.gitignore new file mode 100644 index 0000000..631854d --- /dev/null +++ b/sandbox/token-approve/.gitignore @@ -0,0 +1,17 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +cache/ +out/ diff --git a/sandbox/token-approve/LICENSE b/sandbox/token-approve/LICENSE new file mode 100644 index 0000000..be55ad7 --- /dev/null +++ b/sandbox/token-approve/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Drosera + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sandbox/token-approve/README.md b/sandbox/token-approve/README.md new file mode 100644 index 0000000..997678a --- /dev/null +++ b/sandbox/token-approve/README.md @@ -0,0 +1,9 @@ +# Token Access Control Example + +- `TokenAccessControlTrap` monitors the list of addresses with the token minter role and checks for any changes. It is used in case of an access control exploit. + +## Running the Examples + +```bash +forge test +``` diff --git a/sandbox/token-approve/foundry.toml b/sandbox/token-approve/foundry.toml new file mode 100644 index 0000000..528ea36 --- /dev/null +++ b/sandbox/token-approve/foundry.toml @@ -0,0 +1,8 @@ +[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 +[rpc_endpoints] +mainnet = "https://eth.llamarpc.com" diff --git a/sandbox/token-approve/remappings.txt b/sandbox/token-approve/remappings.txt new file mode 100644 index 0000000..2cfbfec --- /dev/null +++ b/sandbox/token-approve/remappings.txt @@ -0,0 +1,3 @@ +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ diff --git a/sandbox/token-approve/src/MockERC20.sol b/sandbox/token-approve/src/MockERC20.sol new file mode 100644 index 0000000..be4fd47 --- /dev/null +++ b/sandbox/token-approve/src/MockERC20.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {AccessControlEnumerable} from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockERC20 is ERC20, AccessControlEnumerable { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + constructor(address initialSupply) ERC20("MyToken", "TKN") { + _grantRole(MINTER_ROLE, msg.sender); + _mint(msg.sender, initialSupply); + } + + function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { + _mint(to, amount); + } +} From b388cbcd020130433938b837672e01eb842e105b Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sat, 20 Jul 2024 21:14:41 +0900 Subject: [PATCH 07/15] forge install: openzeppelin-contracts v5.0.2 --- sandbox/token-approve/lib/openzeppelin-contracts | 1 + 1 file changed, 1 insertion(+) create mode 160000 sandbox/token-approve/lib/openzeppelin-contracts diff --git a/sandbox/token-approve/lib/openzeppelin-contracts b/sandbox/token-approve/lib/openzeppelin-contracts new file mode 160000 index 0000000..dbb6104 --- /dev/null +++ b/sandbox/token-approve/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 From 70edcb302c781455034d9afbfa1dc21bd2854367 Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sat, 20 Jul 2024 21:15:25 +0900 Subject: [PATCH 08/15] forge install: forge-std v1.9.1 --- .gitmodules | 2 +- sandbox/token-approve/lib/forge-std | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 sandbox/token-approve/lib/forge-std diff --git a/.gitmodules b/.gitmodules index 6ed91f1..1922754 100644 --- a/.gitmodules +++ b/.gitmodules @@ -36,4 +36,4 @@ url = https://github.com/OpenZeppelin/openzeppelin-contracts [submodule "sandbox/token-approve/lib/forge-std"] path = sandbox/token-approve/lib/forge-std - url = https://github.com/foundry-rs/forge-std \ No newline at end of file + url = https://github.com/foundry-rs/forge-std diff --git a/sandbox/token-approve/lib/forge-std b/sandbox/token-approve/lib/forge-std new file mode 160000 index 0000000..07263d1 --- /dev/null +++ b/sandbox/token-approve/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 07263d193d621c4b2b0ce8b4d54af58f6957d97d From f578cf23778f1d332dca7f6486890d69dbde5ce0 Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sat, 20 Jul 2024 21:26:21 +0900 Subject: [PATCH 09/15] fix msg sender --- sandbox/token-access-control/src/MockERC20.sol | 6 +++--- sandbox/token-access-control/test/MockERC20.t.sol | 2 +- .../token-access-control/test/TokenAccessControlTrap.t.sol | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sandbox/token-access-control/src/MockERC20.sol b/sandbox/token-access-control/src/MockERC20.sol index 75bf402..d41cd90 100644 --- a/sandbox/token-access-control/src/MockERC20.sol +++ b/sandbox/token-access-control/src/MockERC20.sol @@ -7,9 +7,9 @@ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MockERC20 is ERC20, AccessControlEnumerable { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - constructor(address account) ERC20("MyToken", "TKN") { - _grantRole(DEFAULT_ADMIN_ROLE, account); - _grantRole(MINTER_ROLE, account); + constructor() ERC20("MyToken", "TKN") { + _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); + _grantRole(MINTER_ROLE, _msgSender()); } function mint(address to, uint256 amount) public { diff --git a/sandbox/token-access-control/test/MockERC20.t.sol b/sandbox/token-access-control/test/MockERC20.t.sol index 15fa2ca..c98b118 100644 --- a/sandbox/token-access-control/test/MockERC20.t.sol +++ b/sandbox/token-access-control/test/MockERC20.t.sol @@ -13,7 +13,7 @@ contract MockERC20Test is Test { function setUp() public { minter = address(this); user = address(0x123); - token = new MockERC20(minter); + token = new MockERC20(); } function testMinterRole() public view { diff --git a/sandbox/token-access-control/test/TokenAccessControlTrap.t.sol b/sandbox/token-access-control/test/TokenAccessControlTrap.t.sol index 3c857fa..d4731dc 100644 --- a/sandbox/token-access-control/test/TokenAccessControlTrap.t.sol +++ b/sandbox/token-access-control/test/TokenAccessControlTrap.t.sol @@ -13,7 +13,7 @@ contract TokenAccessControlTrapTest is Test { address public user; function setUp() public { - mockToken = new MockERC20(address(this)); + mockToken = new MockERC20(); user = address(0x123); } From 185cd38b744effe4844aa46b09468c6e4ea5ed69 Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sat, 20 Jul 2024 22:54:24 +0900 Subject: [PATCH 10/15] add token supply example --- .gitmodules | 8 ++-- sandbox/token-approve/lib/forge-std | 1 - .../token-approve/lib/openzeppelin-contracts | 1 - .../.gitignore | 0 .../{token-approve => token-supply}/LICENSE | 0 .../{token-approve => token-supply}/README.md | 0 .../foundry.toml | 0 .../remappings.txt | 0 .../src/MockERC20.sol | 2 +- sandbox/token-supply/src/TokenSupplyTrap.sol | 42 ++++++++++++++++++ sandbox/token-supply/test/TokenSupply.t.sol | 44 +++++++++++++++++++ 11 files changed, 91 insertions(+), 7 deletions(-) delete mode 160000 sandbox/token-approve/lib/forge-std delete mode 160000 sandbox/token-approve/lib/openzeppelin-contracts rename sandbox/{token-approve => token-supply}/.gitignore (100%) rename sandbox/{token-approve => token-supply}/LICENSE (100%) rename sandbox/{token-approve => token-supply}/README.md (100%) rename sandbox/{token-approve => token-supply}/foundry.toml (100%) rename sandbox/{token-approve => token-supply}/remappings.txt (100%) rename sandbox/{token-approve => token-supply}/src/MockERC20.sol (90%) create mode 100644 sandbox/token-supply/src/TokenSupplyTrap.sol create mode 100644 sandbox/token-supply/test/TokenSupply.t.sol diff --git a/.gitmodules b/.gitmodules index 1922754..5af5919 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,9 +31,9 @@ [submodule "sandbox/token-access-control/lib/forge-std"] path = sandbox/token-access-control/lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "sandbox/token-approve/lib/openzeppelin-contracts"] - path = sandbox/token-approve/lib/openzeppelin-contracts +[submodule "sandbox/token-supply/lib/openzeppelin-contracts"] + path = sandbox/token-supply/lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts -[submodule "sandbox/token-approve/lib/forge-std"] - path = sandbox/token-approve/lib/forge-std +[submodule "sandbox/token-supply/lib/forge-std"] + path = sandbox/token-supply/lib/forge-std url = https://github.com/foundry-rs/forge-std diff --git a/sandbox/token-approve/lib/forge-std b/sandbox/token-approve/lib/forge-std deleted file mode 160000 index 07263d1..0000000 --- a/sandbox/token-approve/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 07263d193d621c4b2b0ce8b4d54af58f6957d97d diff --git a/sandbox/token-approve/lib/openzeppelin-contracts b/sandbox/token-approve/lib/openzeppelin-contracts deleted file mode 160000 index dbb6104..0000000 --- a/sandbox/token-approve/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 diff --git a/sandbox/token-approve/.gitignore b/sandbox/token-supply/.gitignore similarity index 100% rename from sandbox/token-approve/.gitignore rename to sandbox/token-supply/.gitignore diff --git a/sandbox/token-approve/LICENSE b/sandbox/token-supply/LICENSE similarity index 100% rename from sandbox/token-approve/LICENSE rename to sandbox/token-supply/LICENSE diff --git a/sandbox/token-approve/README.md b/sandbox/token-supply/README.md similarity index 100% rename from sandbox/token-approve/README.md rename to sandbox/token-supply/README.md diff --git a/sandbox/token-approve/foundry.toml b/sandbox/token-supply/foundry.toml similarity index 100% rename from sandbox/token-approve/foundry.toml rename to sandbox/token-supply/foundry.toml diff --git a/sandbox/token-approve/remappings.txt b/sandbox/token-supply/remappings.txt similarity index 100% rename from sandbox/token-approve/remappings.txt rename to sandbox/token-supply/remappings.txt diff --git a/sandbox/token-approve/src/MockERC20.sol b/sandbox/token-supply/src/MockERC20.sol similarity index 90% rename from sandbox/token-approve/src/MockERC20.sol rename to sandbox/token-supply/src/MockERC20.sol index be4fd47..17138b2 100644 --- a/sandbox/token-approve/src/MockERC20.sol +++ b/sandbox/token-supply/src/MockERC20.sol @@ -7,7 +7,7 @@ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MockERC20 is ERC20, AccessControlEnumerable { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - constructor(address initialSupply) ERC20("MyToken", "TKN") { + constructor(uint256 initialSupply) ERC20("MyToken", "TKN") { _grantRole(MINTER_ROLE, msg.sender); _mint(msg.sender, initialSupply); } diff --git a/sandbox/token-supply/src/TokenSupplyTrap.sol b/sandbox/token-supply/src/TokenSupplyTrap.sol new file mode 100644 index 0000000..aa06f8a --- /dev/null +++ b/sandbox/token-supply/src/TokenSupplyTrap.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; +import {MockERC20} from "./MockERC20.sol"; +import {console} from "forge-std/console.sol"; + +contract TokenSupplyTrap { + MockERC20 public mockToken; + + struct CustomCollectStruct { + uint256 totalSupply; + } + + constructor(address _mockToken) { + mockToken = MockERC20(_mockToken); + } + + function collect() external view returns (CustomCollectStruct memory) { + uint256 totalSupply = mockToken.totalSupply(); + return CustomCollectStruct({totalSupply: totalSupply}); + } + + function isValid( + CustomCollectStruct[] calldata dataPoints, + uint16 allowedIncreasePercentage + ) external pure returns (bool) { + if (dataPoints.length == 0) { + revert("DataPoints length should be greater than 0"); + } + + for (uint256 i = 1; i < dataPoints.length; i++) { + uint256 previousSupply = dataPoints[i - 1].totalSupply; + uint256 currentSupply = dataPoints[i].totalSupply; + uint256 threshold = previousSupply + + ((previousSupply * allowedIncreasePercentage) / 100); + + if (currentSupply > threshold) { + return false; + } + } + return true; + } +} diff --git a/sandbox/token-supply/test/TokenSupply.t.sol b/sandbox/token-supply/test/TokenSupply.t.sol new file mode 100644 index 0000000..c9c0a4a --- /dev/null +++ b/sandbox/token-supply/test/TokenSupply.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {TokenSupplyTrap} from "../src/TokenSupplyTrap.sol"; +import {MockERC20} from "../src/MockERC20.sol"; + +/// @dev forge test --match-contract TokenSupplyTrapTest -vv +contract TokenSupplyTrapTest is Test { + MockERC20 public mockToken; + address public user; + + function setUp() public { + mockToken = new MockERC20(1000 * 10 ** 18); + user = address(0x123); + } + + function test_TokenSupplyTrap() external { + TokenSupplyTrap.CustomCollectStruct[] + memory data = new TokenSupplyTrap.CustomCollectStruct[](2); + TokenSupplyTrap tokenSupplyTrap = new TokenSupplyTrap( + address(mockToken) + ); + + data[0] = tokenSupplyTrap.collect(); + data[1] = tokenSupplyTrap.collect(); + // 10% threshold + bool isValid = tokenSupplyTrap.isValid(data, 10); + assert(isValid); + mockToken.mint(user, 200 * 10 ** 18); + + TokenSupplyTrap.CustomCollectStruct[] + memory newData = new TokenSupplyTrap.CustomCollectStruct[]( + data.length + 1 + ); + for (uint i = 0; i < data.length; i++) { + newData[i] = data[i]; + } + newData[data.length] = tokenSupplyTrap.collect(); + isValid = tokenSupplyTrap.isValid(newData, 10); + + assert(!isValid); + } +} From 8ddf2a890454c0dd21cbca8e3750b844822a772a Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sat, 20 Jul 2024 23:04:15 +0900 Subject: [PATCH 11/15] forge install: forge-std v1.9.1 --- sandbox/token-supply/lib/forge-std | 1 + 1 file changed, 1 insertion(+) create mode 160000 sandbox/token-supply/lib/forge-std diff --git a/sandbox/token-supply/lib/forge-std b/sandbox/token-supply/lib/forge-std new file mode 160000 index 0000000..07263d1 --- /dev/null +++ b/sandbox/token-supply/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 07263d193d621c4b2b0ce8b4d54af58f6957d97d From 08b9d160c5f7e6613d89bbd61297cb8827f39fdc Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sat, 20 Jul 2024 23:04:46 +0900 Subject: [PATCH 12/15] forge install: openzeppelin-contracts v5.0.2 --- sandbox/token-supply/lib/openzeppelin-contracts | 1 + 1 file changed, 1 insertion(+) create mode 160000 sandbox/token-supply/lib/openzeppelin-contracts diff --git a/sandbox/token-supply/lib/openzeppelin-contracts b/sandbox/token-supply/lib/openzeppelin-contracts new file mode 160000 index 0000000..dbb6104 --- /dev/null +++ b/sandbox/token-supply/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 From b2b21a5943c8b487ba2ddca3d471b3908f42ae54 Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sat, 20 Jul 2024 23:06:52 +0900 Subject: [PATCH 13/15] fix readme --- sandbox/token-supply/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sandbox/token-supply/README.md b/sandbox/token-supply/README.md index 997678a..1f4c030 100644 --- a/sandbox/token-supply/README.md +++ b/sandbox/token-supply/README.md @@ -1,6 +1,6 @@ # Token Access Control Example -- `TokenAccessControlTrap` monitors the list of addresses with the token minter role and checks for any changes. It is used in case of an access control exploit. +- `TokenSupplyTrapTest` monitors total token supply and checks if it is largely changed. It is used in case of mint abuse exploit. ## Running the Examples From 39999cd9aaa5eaec30f4a9402a94fd1167fd33b4 Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sat, 20 Jul 2024 23:12:05 +0900 Subject: [PATCH 14/15] add console log --- sandbox/token-access-control/src/TokenAccessControlTrap.sol | 1 + sandbox/token-supply/src/TokenSupplyTrap.sol | 1 + 2 files changed, 2 insertions(+) diff --git a/sandbox/token-access-control/src/TokenAccessControlTrap.sol b/sandbox/token-access-control/src/TokenAccessControlTrap.sol index 575c61f..32d956b 100644 --- a/sandbox/token-access-control/src/TokenAccessControlTrap.sol +++ b/sandbox/token-access-control/src/TokenAccessControlTrap.sol @@ -49,6 +49,7 @@ contract TokenAccessControlTrap { } } if (!matchFound) { + console.log("Address mismatch detected!"); return false; } } diff --git a/sandbox/token-supply/src/TokenSupplyTrap.sol b/sandbox/token-supply/src/TokenSupplyTrap.sol index aa06f8a..1f0b47a 100644 --- a/sandbox/token-supply/src/TokenSupplyTrap.sol +++ b/sandbox/token-supply/src/TokenSupplyTrap.sol @@ -34,6 +34,7 @@ contract TokenSupplyTrap { ((previousSupply * allowedIncreasePercentage) / 100); if (currentSupply > threshold) { + console.log("currentSupply increased by more than allowedIncreasePercentage: ", threshold); return false; } } From 03a204585bdf33c64cad2f2a350ddf06eded9c8c Mon Sep 17 00:00:00 2001 From: KeisukeFunatsu Date: Sun, 21 Jul 2024 00:29:52 +0900 Subject: [PATCH 15/15] fixed test --- .../token-access-control/src/MockERC20.sol | 9 +++- .../src/TokenAccessControlTrap.sol | 54 ++++++++++--------- .../test/TokenAccessControlTrap.t.sol | 39 ++++++++++---- sandbox/token-supply/src/TokenSupplyTrap.sol | 2 + 4 files changed, 68 insertions(+), 36 deletions(-) diff --git a/sandbox/token-access-control/src/MockERC20.sol b/sandbox/token-access-control/src/MockERC20.sol index d41cd90..5081efd 100644 --- a/sandbox/token-access-control/src/MockERC20.sol +++ b/sandbox/token-access-control/src/MockERC20.sol @@ -25,11 +25,16 @@ contract MockERC20 is ERC20, AccessControlEnumerable { _grantRole(MINTER_ROLE, account); } - function getRoleAddresses(bytes32 role) public view returns (address[] memory) { + function getRoleAddresses( + bytes32 role + ) public view returns (address[] memory) { uint256 count = getRoleMemberCount(role); address[] memory addresses = new address[](count); for (uint256 i = 0; i < count; ++i) { - addresses[i] = getRoleMember(role, i); + address account = getRoleMember(role, i); + if (hasRole(role, account)) { + addresses[i] = account; + } } return addresses; } diff --git a/sandbox/token-access-control/src/TokenAccessControlTrap.sol b/sandbox/token-access-control/src/TokenAccessControlTrap.sol index 32d956b..88f5efd 100644 --- a/sandbox/token-access-control/src/TokenAccessControlTrap.sol +++ b/sandbox/token-access-control/src/TokenAccessControlTrap.sol @@ -15,45 +15,49 @@ contract TokenAccessControlTrap { } function collect() external view returns (CustomCollectStruct memory) { - address[] memory minterAddresses = mockToken.getRoleAddresses(mockToken.MINTER_ROLE()); + address[] memory minterAddresses = mockToken.getRoleAddresses( + mockToken.MINTER_ROLE() + ); return CustomCollectStruct({minterAddresses: minterAddresses}); } function isValid( CustomCollectStruct[] calldata dataPoints ) external pure returns (bool) { - if (dataPoints.length == 0) { - revert("DataPoints length should be greater than 0"); + if (dataPoints.length < 2) { + revert("DataPoints length should be at least 2"); } - if (dataPoints.length == 1) { - return true; - } + for (uint256 i = 1; i < dataPoints.length; i++) { + address[] memory oldList = dataPoints[i - 1].minterAddresses; + address[] memory newList = dataPoints[i].minterAddresses; - address[] memory oldList = dataPoints[0].minterAddresses; - address[] memory newList = dataPoints[1].minterAddresses; - // all minter addresses should be same at each block. - if (oldList.length != newList.length) { - return false; - } + console.log("oldListLength: ", oldList.length); + console.log("newListLength: ", newList.length); + + if (oldList.length != newList.length) { + console.log("Address length mismatch detected!"); + return false; + } - bool[] memory found = new bool[](oldList.length); + bool[] memory found = new bool[](oldList.length); - for (uint256 i = 0; i < oldList.length; i++) { - bool matchFound = false; - for (uint256 j = 0; j < newList.length; j++) { - if (oldList[i] == newList[j] && !found[j]) { - found[j] = true; - matchFound = true; - break; + for (uint256 j = 0; j < oldList.length; j++) { + bool matchFound = false; + for (uint256 k = 0; k < newList.length; k++) { + if (oldList[j] == newList[k] && !found[k]) { + found[k] = true; + matchFound = true; + break; + } + } + if (!matchFound) { + console.log("Address mismatch detected!"); + return false; } - } - if (!matchFound) { - console.log("Address mismatch detected!"); - return false; } } - + console.log("No change."); return true; } } diff --git a/sandbox/token-access-control/test/TokenAccessControlTrap.t.sol b/sandbox/token-access-control/test/TokenAccessControlTrap.t.sol index d4731dc..6660045 100644 --- a/sandbox/token-access-control/test/TokenAccessControlTrap.t.sol +++ b/sandbox/token-access-control/test/TokenAccessControlTrap.t.sol @@ -11,42 +11,63 @@ contract TokenAccessControlTrapTest is Test { // Trap Config lives on chain while the Trap itself lives off chain address public trapConfig = address(0xDEADBEEF); address public user; + address public anotherUser; function setUp() public { mockToken = new MockERC20(); user = address(0x123); + anotherUser = address(0x456); } function test_TokenAccessControlTrap() external { TokenAccessControlTrap.CustomCollectStruct[] - memory data = new TokenAccessControlTrap.CustomCollectStruct[](1); + memory data = new TokenAccessControlTrap.CustomCollectStruct[](2); address tokenAccessControlTrap = address( new TokenAccessControlTrap(address(mockToken)) ); data[0] = TokenAccessControlTrap(tokenAccessControlTrap).collect(); + data[1] = data[0]; bool isValid = TokenAccessControlTrap(tokenAccessControlTrap).isValid( data ); assert(isValid); - // Perform exploit + // Perform exploit, add unknown minter vm.prank(user); mockToken.grantMinter(user); TokenAccessControlTrap.CustomCollectStruct[] memory newData = new TokenAccessControlTrap.CustomCollectStruct[]( - data.length + 1 + 2 ); - for (uint i = 0; i < data.length; i++) { - newData[i] = data[i]; - } - newData[data.length] = TokenAccessControlTrap(tokenAccessControlTrap) - .collect(); + + newData[0] = data[0]; + newData[1] = TokenAccessControlTrap(tokenAccessControlTrap).collect(); isValid = TokenAccessControlTrap(tokenAccessControlTrap).isValid( newData ); - // Minter list changed, so it should be invalid. + + // Reset and add another minter + vm.prank(address(this)); + mockToken.revokeRole(mockToken.MINTER_ROLE(), user); + + vm.prank(anotherUser); + mockToken.grantMinter(anotherUser); + + TokenAccessControlTrap.CustomCollectStruct[] + memory sameLengthDifferentContent = new TokenAccessControlTrap.CustomCollectStruct[]( + 2 + ); + sameLengthDifferentContent[0] = newData[1]; + sameLengthDifferentContent[1] = TokenAccessControlTrap( + tokenAccessControlTrap + ).collect(); + + isValid = TokenAccessControlTrap(tokenAccessControlTrap).isValid( + sameLengthDifferentContent + ); + assert(!isValid); } } diff --git a/sandbox/token-supply/src/TokenSupplyTrap.sol b/sandbox/token-supply/src/TokenSupplyTrap.sol index 1f0b47a..3deed1a 100644 --- a/sandbox/token-supply/src/TokenSupplyTrap.sol +++ b/sandbox/token-supply/src/TokenSupplyTrap.sol @@ -38,6 +38,8 @@ contract TokenSupplyTrap { return false; } } + + console.log("No change."); return true; } }