diff --git a/.gitmodules b/.gitmodules index c0faed2..5af5919 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,9 +19,21 @@ [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 url = https://github.com/foundry-rs/forge-std +[submodule "sandbox/token-access-control/lib/openzeppelin-contracts"] + path = sandbox/token-access-control/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts +[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-supply/lib/openzeppelin-contracts"] + path = sandbox/token-supply/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts +[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/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 diff --git a/sandbox/token-access-control/.gitignore b/sandbox/token-access-control/.gitignore new file mode 100644 index 0000000..631854d --- /dev/null +++ b/sandbox/token-access-control/.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-access-control/LICENSE b/sandbox/token-access-control/LICENSE new file mode 100644 index 0000000..be55ad7 --- /dev/null +++ b/sandbox/token-access-control/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-access-control/README.md b/sandbox/token-access-control/README.md new file mode 100644 index 0000000..997678a --- /dev/null +++ b/sandbox/token-access-control/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-access-control/foundry.toml b/sandbox/token-access-control/foundry.toml new file mode 100644 index 0000000..528ea36 --- /dev/null +++ b/sandbox/token-access-control/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-access-control/lib/forge-std b/sandbox/token-access-control/lib/forge-std new file mode 160000 index 0000000..07263d1 --- /dev/null +++ b/sandbox/token-access-control/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 07263d193d621c4b2b0ce8b4d54af58f6957d97d diff --git a/sandbox/token-access-control/lib/openzeppelin-contracts b/sandbox/token-access-control/lib/openzeppelin-contracts new file mode 160000 index 0000000..dbb6104 --- /dev/null +++ b/sandbox/token-access-control/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 diff --git a/sandbox/token-access-control/remappings.txt b/sandbox/token-access-control/remappings.txt new file mode 100644 index 0000000..2cfbfec --- /dev/null +++ b/sandbox/token-access-control/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-access-control/src/MockERC20.sol b/sandbox/token-access-control/src/MockERC20.sol new file mode 100644 index 0000000..5081efd --- /dev/null +++ b/sandbox/token-access-control/src/MockERC20.sol @@ -0,0 +1,41 @@ +// 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() ERC20("MyToken", "TKN") { + _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); + _grantRole(MINTER_ROLE, _msgSender()); + } + + 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) { + 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 new file mode 100644 index 0000000..88f5efd --- /dev/null +++ b/sandbox/token-access-control/src/TokenAccessControlTrap.sol @@ -0,0 +1,63 @@ +// 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 < 2) { + revert("DataPoints length should be at least 2"); + } + + for (uint256 i = 1; i < dataPoints.length; i++) { + address[] memory oldList = dataPoints[i - 1].minterAddresses; + address[] memory newList = dataPoints[i].minterAddresses; + + 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); + + 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; + } + } + } + console.log("No change."); + return true; + } +} diff --git a/sandbox/token-access-control/test/MockERC20.t.sol b/sandbox/token-access-control/test/MockERC20.t.sol new file mode 100644 index 0000000..c98b118 --- /dev/null +++ b/sandbox/token-access-control/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(); + } + + 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/sandbox/token-access-control/test/TokenAccessControlTrap.t.sol b/sandbox/token-access-control/test/TokenAccessControlTrap.t.sol new file mode 100644 index 0000000..6660045 --- /dev/null +++ b/sandbox/token-access-control/test/TokenAccessControlTrap.t.sol @@ -0,0 +1,73 @@ +// 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; + 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[](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, add unknown minter + vm.prank(user); + mockToken.grantMinter(user); + + TokenAccessControlTrap.CustomCollectStruct[] + memory newData = new TokenAccessControlTrap.CustomCollectStruct[]( + 2 + ); + + newData[0] = data[0]; + newData[1] = TokenAccessControlTrap(tokenAccessControlTrap).collect(); + isValid = TokenAccessControlTrap(tokenAccessControlTrap).isValid( + newData + ); + + // 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/.gitignore b/sandbox/token-supply/.gitignore new file mode 100644 index 0000000..631854d --- /dev/null +++ b/sandbox/token-supply/.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-supply/LICENSE b/sandbox/token-supply/LICENSE new file mode 100644 index 0000000..be55ad7 --- /dev/null +++ b/sandbox/token-supply/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-supply/README.md b/sandbox/token-supply/README.md new file mode 100644 index 0000000..1f4c030 --- /dev/null +++ b/sandbox/token-supply/README.md @@ -0,0 +1,9 @@ +# Token Access Control Example + +- `TokenSupplyTrapTest` monitors total token supply and checks if it is largely changed. It is used in case of mint abuse exploit. + +## Running the Examples + +```bash +forge test +``` diff --git a/sandbox/token-supply/foundry.toml b/sandbox/token-supply/foundry.toml new file mode 100644 index 0000000..528ea36 --- /dev/null +++ b/sandbox/token-supply/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-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 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 diff --git a/sandbox/token-supply/remappings.txt b/sandbox/token-supply/remappings.txt new file mode 100644 index 0000000..2cfbfec --- /dev/null +++ b/sandbox/token-supply/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-supply/src/MockERC20.sol b/sandbox/token-supply/src/MockERC20.sol new file mode 100644 index 0000000..17138b2 --- /dev/null +++ b/sandbox/token-supply/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(uint256 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); + } +} diff --git a/sandbox/token-supply/src/TokenSupplyTrap.sol b/sandbox/token-supply/src/TokenSupplyTrap.sol new file mode 100644 index 0000000..3deed1a --- /dev/null +++ b/sandbox/token-supply/src/TokenSupplyTrap.sol @@ -0,0 +1,45 @@ +// 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) { + console.log("currentSupply increased by more than allowedIncreasePercentage: ", threshold); + return false; + } + } + + console.log("No change."); + 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); + } +}